Bug 1484373: Part 4 - Move more content script injection logic into policy service. r=mixedpuppy

Differential Revision: https://phabricator.services.mozilla.com/D3694

--HG--
extra : rebase_source : 8816204962b8f1ee843ca526c99aaa8175ca22c8
extra : histedit_source : ede9809d9f9e9bac5e37cfa79c80b899b349a91b
This commit is contained in:
Kris Maglione 2018-08-17 22:09:23 -07:00
parent 376fa824ca
commit 984c4ecad1
7 changed files with 204 additions and 59 deletions

View File

@ -134,6 +134,12 @@ interface WebExtensionPolicy {
[Throws]
void unregisterContentScript(WebExtensionContentScript script);
/**
* Injects the extension's content script into all existing matching windows.
*/
[Throws]
void injectContentScripts();
/**
* Returns the list of currently active extension policies.
*/

View File

@ -12,24 +12,34 @@
#include "mozilla/Preferences.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Services.h"
#include "mozilla/SimpleEnumerator.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentFrameMessageManager.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozIExtensionProcessScript.h"
#include "nsEscape.h"
#include "nsGkAtoms.h"
#include "nsIChannel.h"
#include "nsIContentPolicy.h"
#include "nsIDocShell.h"
#include "nsIDocument.h"
#include "nsILoadInfo.h"
#include "nsIXULRuntime.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "nsXULAppAPI.h"
#include "nsQueryObject.h"
namespace mozilla {
using namespace extensions;
using dom::AutoJSAPI;
using dom::ContentFrameMessageManager;
using dom::Promise;
#define DEFAULT_BASE_CSP \
"script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; " \
"object-src 'self' https://* moz-extension: blob: filesystem:;"
@ -251,6 +261,7 @@ ExtensionPolicyService::RegisterObservers()
{
mObs->AddObserver(this, "content-document-global-created", false);
mObs->AddObserver(this, "document-element-inserted", false);
mObs->AddObserver(this, "tab-content-frameloader-created", false);
if (XRE_IsContentProcess()) {
mObs->AddObserver(this, "http-on-opening-request", false);
}
@ -261,6 +272,7 @@ ExtensionPolicyService::UnregisterObservers()
{
mObs->RemoveObserver(this, "content-document-global-created");
mObs->RemoveObserver(this, "document-element-inserted");
mObs->RemoveObserver(this, "tab-content-frameloader-created");
if (XRE_IsContentProcess()) {
mObs->RemoveObserver(this, "http-on-opening-request");
}
@ -284,6 +296,129 @@ ExtensionPolicyService::Observe(nsISupports* aSubject, const char* aTopic, const
if (chan) {
CheckRequest(chan);
}
} else if (!strcmp(aTopic, "tab-content-frameloader-created")) {
RefPtr<ContentFrameMessageManager> mm = do_QueryObject(aSubject);
NS_ENSURE_TRUE(mm, NS_ERROR_UNEXPECTED);
mMessageManagers.PutEntry(mm);
mm->AddSystemEventListener(NS_LITERAL_STRING("unload"), this,
false, false);
}
return NS_OK;
}
nsresult
ExtensionPolicyService::HandleEvent(dom::Event* aEvent)
{
RefPtr<ContentFrameMessageManager> mm = do_QueryObject(aEvent->GetTarget());
MOZ_ASSERT(mm);
if (mm) {
mMessageManagers.RemoveEntry(mm);
}
return NS_OK;
}
nsresult
ForEachDocShell(nsIDocShell* aDocShell,
const std::function<nsresult(nsIDocShell*)>& aCallback)
{
nsCOMPtr<nsISimpleEnumerator> iter;
MOZ_TRY(aDocShell->GetDocShellEnumerator(nsIDocShell::typeContent,
nsIDocShell::ENUMERATE_FORWARDS,
getter_AddRefs(iter)));
for (nsIDocShell& docShell : SimpleEnumerator<nsIDocShell>(iter)) {
MOZ_TRY(aCallback(&docShell));
}
return NS_OK;
}
already_AddRefed<Promise>
ExtensionPolicyService::ExecuteContentScript(nsPIDOMWindowInner* aWindow,
WebExtensionContentScript& aScript)
{
if (!aWindow->IsCurrentInnerWindow()) {
return nullptr;
}
RefPtr<Promise> promise;
ProcessScript().LoadContentScript(&aScript, aWindow, getter_AddRefs(promise));
return promise.forget();
}
RefPtr<Promise>
ExtensionPolicyService::ExecuteContentScripts(JSContext* aCx, nsPIDOMWindowInner* aWindow,
const nsTArray<RefPtr<WebExtensionContentScript>>& aScripts)
{
AutoTArray<RefPtr<Promise>, 8> promises;
for (auto& script : aScripts) {
promises.AppendElement(ExecuteContentScript(aWindow, *script));
}
RefPtr<Promise> promise = Promise::All(aCx, promises, IgnoreErrors());
MOZ_RELEASE_ASSERT(promise);
return promise;
}
nsresult
ExtensionPolicyService::InjectContentScripts(WebExtensionPolicy* aExtension)
{
AutoJSAPI jsapi;
MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
for (auto iter = mMessageManagers.ConstIter(); !iter.Done(); iter.Next()) {
ContentFrameMessageManager* mm = iter.Get()->GetKey();
nsCOMPtr<nsIDocShell> docShell = mm->GetDocShell(IgnoreErrors());
NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED);
auto result = ForEachDocShell(docShell, [&](nsIDocShell* aDocShell) -> nsresult {
nsCOMPtr<nsPIDOMWindowOuter> win = aDocShell->GetWindow();
DocInfo docInfo(win);
using RunAt = dom::ContentScriptRunAt;
using Scripts = AutoTArray<RefPtr<WebExtensionContentScript>, 8>;
constexpr uint8_t n = uint8_t(RunAt::EndGuard_);
Scripts scripts[n];
auto GetScripts = [&](RunAt aRunAt) -> Scripts&& {
return std::move(scripts[uint8_t(aRunAt)]);
};
for (const auto& script : aExtension->ContentScripts()) {
if (script->Matches(docInfo)) {
GetScripts(script->RunAt()).AppendElement(script);
}
}
nsCOMPtr<nsPIDOMWindowInner> inner = win->GetCurrentInnerWindow();
MOZ_TRY(ExecuteContentScripts(jsapi.cx(), inner, GetScripts(RunAt::Document_start))
->ThenWithCycleCollectedArgs([](JSContext* aCx, JS::HandleValue aValue,
ExtensionPolicyService* aSelf,
nsPIDOMWindowInner* aInner,
Scripts&& aScripts) {
return aSelf->ExecuteContentScripts(aCx, aInner, aScripts).forget();
},
this, inner, GetScripts(RunAt::Document_end))
.andThen([&](auto aPromise) {
return aPromise->ThenWithCycleCollectedArgs([](JSContext* aCx,
JS::HandleValue aValue,
ExtensionPolicyService* aSelf,
nsPIDOMWindowInner* aInner,
Scripts&& aScripts) {
return aSelf->ExecuteContentScripts(aCx, aInner, aScripts).forget();
},
this, inner, GetScripts(RunAt::Document_idle));
}));
return NS_OK;
});
MOZ_TRY(result);
}
return NS_OK;
}
@ -320,6 +455,12 @@ ExtensionPolicyService::CheckDocument(nsIDocument* aDocument)
{
nsCOMPtr<nsPIDOMWindowOuter> win = aDocument->GetWindow();
if (win) {
nsIDocShell* docShell = win->GetDocShell();
RefPtr<ContentFrameMessageManager> mm = docShell->GetMessageManager();
if (!mm || !mMessageManagers.Contains(mm)) {
return;
}
if (win->GetDocumentURI()) {
CheckContentScripts(win.get(), false);
}
@ -355,13 +496,24 @@ ExtensionPolicyService::CheckWindow(nsPIDOMWindowOuter* aWindow)
return;
}
CheckContentScripts(aWindow, false);
nsIDocShell* docShell = aWindow->GetDocShell();
if (RefPtr<ContentFrameMessageManager> mm = docShell->GetMessageManager()) {
if (mMessageManagers.Contains(mm)) {
CheckContentScripts(aWindow, false);
}
}
}
void
ExtensionPolicyService::CheckContentScripts(const DocInfo& aDocInfo, bool aIsPreload)
{
nsCOMPtr<nsPIDOMWindowInner> win = aDocInfo.GetWindow()->GetCurrentInnerWindow();
for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) {
if (!win->IsCurrentInnerWindow()) {
break;
}
RefPtr<WebExtensionPolicy> policy = iter.Data();
for (auto& script : policy->ContentScripts()) {
@ -369,7 +521,8 @@ ExtensionPolicyService::CheckContentScripts(const DocInfo& aDocInfo, bool aIsPre
if (aIsPreload) {
ProcessScript().PreloadContentScript(script);
} else {
ProcessScript().LoadContentScript(script, aDocInfo.GetWindow());
RefPtr<Promise> promise;
ProcessScript().LoadContentScript(script, win, getter_AddRefs(promise));
}
}
}
@ -503,6 +656,7 @@ NS_IMPL_CYCLE_COLLECTION(ExtensionPolicyService, mExtensions, mExtensionHosts,
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionPolicyService)
NS_INTERFACE_MAP_ENTRY(nsIAddonPolicyService)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_ENTRY(nsIMemoryReporter)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAddonPolicyService)
NS_INTERFACE_MAP_END

View File

@ -13,22 +13,30 @@
#include "nsHashKeys.h"
#include "nsIAddonPolicyService.h"
#include "nsAtom.h"
#include "nsIDOMEventListener.h"
#include "nsIMemoryReporter.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsISupports.h"
#include "nsPointerHashKeys.h"
#include "nsRefPtrHashtable.h"
#include "nsTHashtable.h"
class nsIChannel;
class nsIObserverService;
class nsIDocument;
class nsIPIDOMWindowInner;
class nsIPIDOMWindowOuter;
namespace mozilla {
namespace dom {
class ContentFrameMessageManager;
class Promise;
}
namespace extensions {
class DocInfo;
class DocumentObserver;
class WebExtensionContentScript;
}
using extensions::DocInfo;
@ -36,6 +44,7 @@ using extensions::WebExtensionPolicy;
class ExtensionPolicyService final : public nsIAddonPolicyService
, public nsIObserver
, public nsIDOMEventListener
, public nsIMemoryReporter
{
public:
@ -44,6 +53,7 @@ public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSIADDONPOLICYSERVICE
NS_DECL_NSIOBSERVER
NS_DECL_NSIDOMEVENTLISTENER
NS_DECL_NSIMEMORYREPORTER
static ExtensionPolicyService& GetSingleton();
@ -86,6 +96,8 @@ public:
bool UseRemoteExtensions() const;
bool IsExtensionProcess() const;
nsresult InjectContentScripts(WebExtensionPolicy* aExtension);
protected:
virtual ~ExtensionPolicyService();
@ -101,9 +113,19 @@ private:
void CheckContentScripts(const DocInfo& aDocInfo, bool aIsPreload);
already_AddRefed<dom::Promise>
ExecuteContentScript(nsPIDOMWindowInner* aWindow,
extensions::WebExtensionContentScript& aScript);
RefPtr<dom::Promise>
ExecuteContentScripts(JSContext* aCx, nsPIDOMWindowInner* aWindow,
const nsTArray<RefPtr<extensions::WebExtensionContentScript>>& aScripts);
nsRefPtrHashtable<nsPtrHashKey<const nsAtom>, WebExtensionPolicy> mExtensions;
nsRefPtrHashtable<nsCStringHashKey, WebExtensionPolicy> mExtensionHosts;
nsTHashtable<nsRefPtrHashKey<dom::ContentFrameMessageManager>> mMessageManagers;
nsRefPtrHashtable<nsPtrHashKey<const extensions::DocumentObserver>,
extensions::DocumentObserver> mObservers;

View File

@ -258,6 +258,15 @@ WebExtensionPolicy::UnregisterContentScript(const WebExtensionContentScript& scr
WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this);
}
void
WebExtensionPolicy::InjectContentScripts(ErrorResult& aRv)
{
nsresult rv = EPS().InjectContentScripts(this);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
}
}
/* static */ bool
WebExtensionPolicy::UseRemoteWebExtensions(GlobalObject& aGlobal)
{

View File

@ -67,6 +67,8 @@ public:
void UnregisterContentScript(const WebExtensionContentScript& script,
ErrorResult& aRv);
void InjectContentScripts(ErrorResult& aRv);
bool CanAccessURI(const URLInfo& aURI, bool aExplicit = false, bool aCheckRestricted = true) const
{
return (!aCheckRestricted || !IsRestrictedURI(aURI)) &&

View File

@ -172,7 +172,9 @@ DocumentManager = {
// Initialize listeners that we need regardless of whether extensions are
// enabled.
earlyInit() {
Services.obs.addObserver(this, "tab-content-frameloader-created"); // eslint-disable-line mozilla/balanced-listeners
// eslint-disable-next-line mozilla/balanced-listeners
Services.obs.addObserver((subject) => this.initGlobal(subject),
"tab-content-frameloader-created");
},
// Initialize a frame script global which extension contexts may be loaded
@ -185,41 +187,8 @@ DocumentManager = {
});
},
initExtension(policy) {
this.injectExtensionScripts(policy);
},
// Listeners
observe(subject, topic, data) {
if (topic == "tab-content-frameloader-created") {
this.initGlobal(subject);
}
},
// Script loading
injectExtensionScripts(policy) {
for (let window of this.enumerateWindows()) {
let runAt = {document_start: [], document_end: [], document_idle: []};
for (let script of policy.contentScripts) {
if (script.matchesWindow(window)) {
runAt[script.runAt].push(script);
}
}
let inject = matcher => contentScripts.get(matcher).injectInto(window);
let injectAll = matchers => Promise.all(matchers.map(inject));
// Intentionally using `.then` instead of `await`, we only need to
// chain injecting other scripts into *this* window, not all windows.
injectAll(runAt.document_start)
.then(() => injectAll(runAt.document_end))
.then(() => injectAll(runAt.document_idle));
}
},
/**
* Checks that all parent frames for the given withdow either have the
* same add-on ID, or are special chrome-privileged documents such as
@ -265,23 +234,6 @@ DocumentManager = {
ExtensionContent.initExtensionContext(extension, window);
}
},
// Helpers
* enumerateWindows(docShell) {
if (docShell) {
let enum_ = docShell.getDocShellEnumerator(docShell.typeContent,
docShell.ENUMERATE_FORWARDS);
for (let docShell of XPCOMUtils.IterSimpleEnumerator(enum_, Ci.nsIDocShell)) {
yield docShell.domWindow;
}
} else {
for (let global of this.globals.keys()) {
yield* this.enumerateWindows(global.docShell);
}
}
},
};
ExtensionManager = {
@ -370,7 +322,7 @@ ExtensionManager = {
}
let policy = this.initExtensionPolicy(data);
DocumentManager.initExtension(policy);
policy.injectContentScripts();
},
receiveMessage({name, data}) {
@ -497,9 +449,7 @@ ExtensionProcessScript.prototype = {
},
loadContentScript(contentScript, window) {
if (DocumentManager.globals.has(window.docShell.messageManager)) {
contentScripts.get(contentScript).injectInto(window);
}
return contentScripts.get(contentScript).injectInto(window);
},
};

View File

@ -4,15 +4,17 @@
#include "nsISupports.idl"
interface mozIDOMWindowProxy;
interface mozIDOMWindow;
webidl Document;
webidl WebExtensionContentScript;
[scriptable,uuid(6b09dc51-6caa-4ca7-9d6d-30c87258a630)]
interface mozIExtensionProcessScript : nsISupports
{
void preloadContentScript(in nsISupports contentScript);
void loadContentScript(in nsISupports contentScript, in mozIDOMWindowProxy window);
Promise loadContentScript(in WebExtensionContentScript contentScript,
in mozIDOMWindow window);
void initExtensionDocument(in nsISupports extension, in Document doc);
};