mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-13 05:15:45 +00:00
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:
parent
376fa824ca
commit
984c4ecad1
@ -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.
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)) &&
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user