--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2004,16 +2004,17 @@ GK_ATOM(ondevicelight, "ondevicelight")
// MediaDevices device change event
GK_ATOM(ondevicechange, "ondevicechange")
// HTML element attributes that only exposed to XBL and chrome content
GK_ATOM(mozinputrangeignorepreventdefault, "mozinputrangeignorepreventdefault")
// WebExtensions
GK_ATOM(moz_extension, "moz-extension")
+GK_ATOM(all_urlsPermission, "<all_urls>")
GK_ATOM(http, "http")
GK_ATOM(https, "https")
//---------------------------------------------------------------------------
// Special atoms
//---------------------------------------------------------------------------
// Node types
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1100,16 +1100,20 @@ DOMInterfaces = {
'VTTRegion': {
'nativeType': 'mozilla::dom::TextTrackRegion',
},
'WebAuthentication': {
'implicitJSContext': 'makeCredential',
},
+'WebExtensionContentScript': {
+ 'nativeType': 'mozilla::extensions::WebExtensionContentScript',
+},
+
'WebExtensionPolicy': {
'nativeType': 'mozilla::extensions::WebExtensionPolicy',
},
'WindowClient': {
'nativeType': 'mozilla::dom::workers::ServiceWorkerWindowClient',
'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerWindowClient.h',
},
@@ -1712,16 +1716,18 @@ def addExternalIface(iface, nativeType=N
addExternalIface('ApplicationCache', nativeType='nsIDOMOfflineResourceList')
addExternalIface('Cookie', nativeType='nsICookie2',
headerFile='nsICookie2.h', notflattened=True)
addExternalIface('Counter')
addExternalIface('RTCDataChannel', nativeType='nsIDOMDataChannel')
addExternalIface('HitRegionOptions', nativeType='nsISupports')
addExternalIface('imgINotificationObserver', nativeType='imgINotificationObserver')
addExternalIface('imgIRequest', nativeType='imgIRequest', notflattened=True)
+addExternalIface('LoadInfo', nativeType='nsILoadInfo',
+ headerFile='nsILoadInfo.h', notflattened=True)
addExternalIface('MenuBuilder', nativeType='nsIMenuBuilder', notflattened=True)
addExternalIface('MozControllers', nativeType='nsIControllers')
addExternalIface('MozFrameLoader', nativeType='nsIFrameLoader', notflattened=True)
addExternalIface('MozObserver', nativeType='nsIObserver', notflattened=True)
addExternalIface('MozRDFCompositeDataSource', nativeType='nsIRDFCompositeDataSource',
notflattened=True)
addExternalIface('MozRDFResource', nativeType='nsIRDFResource', notflattened=True)
addExternalIface('MozTreeView', nativeType='nsITreeView',
new file mode 100644
--- /dev/null
+++ b/dom/webidl/WebExtensionContentScript.webidl
@@ -0,0 +1,161 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface LoadInfo;
+interface URI;
+interface WindowProxy;
+
+/**
+ * Describes the earliest point in the load cycle at which a script should
+ * run.
+ */
+enum ContentScriptRunAt {
+ /**
+ * The point in the load cycle just after the document element has been
+ * inserted, before any page scripts have been allowed to run.
+ */
+ "document_start",
+ /**
+ * The point after which the page DOM has fully loaded, but before all page
+ * resources have necessarily been loaded. Corresponds approximately to the
+ * DOMContentLoaded event.
+ */
+ "document_end",
+ /**
+ * The first point after the page and all of its resources has fully loaded
+ * when the event loop is idle, and can run scripts without delaying a paint
+ * event.
+ */
+ "document_idle",
+};
+
+[Constructor(WebExtensionPolicy extension, WebExtensionContentScriptInit options), ChromeOnly, Exposed=System]
+interface WebExtensionContentScript {
+ /**
+ * Returns true if the script's match and exclude patterns match the given
+ * URI, without reference to attributes such as `allFrames`.
+ */
+ boolean matchesURI(URI uri);
+
+ /**
+ * Returns true if the script matches the given URI and LoadInfo objects.
+ * This should be used to determine whether to begin pre-loading a content
+ * script based on network events.
+ */
+ boolean matchesLoadInfo(URI uri, LoadInfo loadInfo);
+
+ /**
+ * Returns true if the script matches the given window. This should be used
+ * to determine whether to run a script in a window at load time.
+ */
+ boolean matchesWindow(WindowProxy window);
+
+ /**
+ * The policy object for the extension that this script belongs to.
+ */
+ [Constant]
+ readonly attribute WebExtensionPolicy extension;
+
+ /**
+ * If true, this script runs in all frames. If false, it only runs in
+ * top-level frames.
+ */
+ [Constant]
+ readonly attribute boolean allFrames;
+
+ /**
+ * If true, this (misleadingly-names, but inherited from Chrome) attribute
+ * causes the script to run in frames with URLs which inherit a principal
+ * that matches one of the match patterns, such as about:blank or
+ * about:srcdoc. If false, the script only runs in frames with an explicit
+ * matching URL.
+ */
+ [Constant]
+ readonly attribute boolean matchAboutBlank;
+
+ /**
+ * The earliest point in the load cycle at which this script should run. For
+ * static content scripts, in extensions which were present at browser
+ * startup, the browser makes every effort to make sure that the script runs
+ * no later than this point in the load cycle. For dynamic content scripts,
+ * and scripts from extensions installed during this session, the scripts
+ * may run at a later point.
+ */
+ [Constant]
+ readonly attribute ContentScriptRunAt runAt;
+
+ /**
+ * The outer window ID of the frame in which to run the script, or 0 if it
+ * should run in the top-level frame. Should only be used for
+ * dynamically-injected scripts.
+ */
+ [Constant]
+ readonly attribute unsigned long long? frameID;
+
+ /**
+ * The set of match patterns for URIs of pages in which this script should
+ * run. This attribute is mandatory, and is a prerequisite for all other
+ * match patterns.
+ */
+ [Constant]
+ readonly attribute MatchPatternSet matches;
+
+ /**
+ * A set of match patterns for URLs in which this script should not run,
+ * even if they match other include patterns or globs.
+ */
+ [Constant]
+ readonly attribute MatchPatternSet? excludeMatches;
+
+ /**
+ * A set of glob matchers for URLs in which this script should run. If this
+ * list is present, the script will only run in URLs which match the
+ * `matches` pattern as well as one of these globs.
+ */
+ [Cached, Constant, Frozen]
+ readonly attribute sequence<MatchGlob>? includeGlobs;
+
+ /**
+ * A set of glob matchers for URLs in which this script should not run, even
+ * if they match other include patterns or globs.
+ */
+ [Cached, Constant, Frozen]
+ readonly attribute sequence<MatchGlob>? excludeGlobs;
+
+ /**
+ * A set of paths, relative to the extension root, of CSS sheets to inject
+ * into matching pages.
+ */
+ [Cached, Constant, Frozen]
+ readonly attribute sequence<DOMString> cssPaths;
+
+ /**
+ * A set of paths, relative to the extension root, of JavaScript scripts to
+ * execute in matching pages.
+ */
+ [Cached, Constant, Frozen]
+ readonly attribute sequence<DOMString> jsPaths;
+};
+
+dictionary WebExtensionContentScriptInit {
+ boolean allFrames = false;
+
+ boolean matchAboutBlank = false;
+
+ ContentScriptRunAt runAt = "document_idle";
+
+ unsigned long long? frameID = null;
+
+ required MatchPatternSet matches;
+
+ MatchPatternSet? excludeMatches = null;
+
+ sequence<MatchGlob>? includeGlobs = null;
+
+ sequence<MatchGlob>? excludeGlobs = null;
+
+ sequence<DOMString> cssPaths = [];
+
+ sequence<DOMString> jsPaths = [];
+};
--- a/dom/webidl/WebExtensionPolicy.webidl
+++ b/dom/webidl/WebExtensionPolicy.webidl
@@ -54,16 +54,22 @@ interface WebExtensionPolicy {
* Match patterns for the set of web origins to which the extension is
* currently allowed access. May be updated to reflect changes in the
* extension's optional permissions.
*/
[Pure]
attribute MatchPatternSet allowedOrigins;
/**
+ * The set of content scripts active for this extension.
+ */
+ [Cached, Constant, Frozen]
+ readonly attribute sequence<WebExtensionContentScript> contentScripts;
+
+ /**
* True if the extension is currently active, false otherwise. When active,
* the extension's moz-extension: protocol will point to the given baseURI,
* and the set of policies for this object will be active for its ID.
*
* Only one extension policy with a given ID or hostname may be active at a
* time. Attempting to activate a policy while a conflicting policy is
* active will raise an error.
*/
@@ -136,12 +142,14 @@ dictionary WebExtensionInit {
required WebExtensionLocalizeCallback localizeCallback;
required MatchPatternSet allowedOrigins;
sequence<DOMString> permissions = [];
sequence<MatchGlob> webAccessibleResources = [];
+ sequence<WebExtensionContentScriptInit> contentScripts = [];
+
DOMString? contentSecurityPolicy = null;
sequence<DOMString>? backgroundScripts = null;
};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -938,16 +938,17 @@ WEBIDL_FILES = [
'VRDisplay.webidl',
'VRDisplayEvent.webidl',
'VRServiceTest.webidl',
'VTTCue.webidl',
'VTTRegion.webidl',
'WaveShaperNode.webidl',
'WebAuthentication.webidl',
'WebComponents.webidl',
+ 'WebExtensionContentScript.webidl',
'WebExtensionPolicy.webidl',
'WebGL2RenderingContext.webidl',
'WebGLRenderingContext.webidl',
'WebKitCSSMatrix.webidl',
'WebSocket.webidl',
'WheelEvent.webidl',
'WidevineCDMManifest.webidl',
'WindowOrWorkerGlobalScope.webidl',
--- a/toolkit/components/extensions/.eslintrc.js
+++ b/toolkit/components/extensions/.eslintrc.js
@@ -8,17 +8,19 @@ module.exports = {
"Cr": true,
"Cu": true,
"TextDecoder": false,
"TextEncoder": false,
"MatchGlob": false,
"MatchPattern": true,
"MatchPatternSet": false,
+ "WebExtensionContentScript": false,
"WebExtensionPolicy": false,
+
// Specific to WebExtensions:
"AppConstants": true,
"Extension": true,
"ExtensionAPI": true,
"ExtensionManagement": true,
"ExtensionUtils": true,
"extensions": true,
"getContainerForCookieStoreId": true,
--- a/toolkit/components/extensions/ExtensionPolicyService.cpp
+++ b/toolkit/components/extensions/ExtensionPolicyService.cpp
@@ -1,13 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/extensions/WebExtensionContentScript.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
#include "nsEscape.h"
#include "nsGkAtoms.h"
namespace mozilla {
--- a/toolkit/components/extensions/MatchPattern.cpp
+++ b/toolkit/components/extensions/MatchPattern.cpp
@@ -156,20 +156,26 @@ URLInfo::URINoRef() const
}
return mURINoRef;
}
bool
URLInfo::InheritsPrincipal() const
{
if (!mInheritsPrincipal.isSome()) {
- bool inherits = false;
- nsresult rv = NS_URIChainHasFlags(mURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
- &inherits);
- Unused << NS_WARN_IF(NS_FAILED(rv));
+ // For our purposes, about:blank and about:srcdoc are treated as URIs that
+ // inherit principals.
+ bool inherits = Spec().EqualsLiteral("about:blank") || Spec().EqualsLiteral("about:srcdoc");
+
+ if (!inherits) {
+ nsresult rv = NS_URIChainHasFlags(mURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
+ &inherits);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+
mInheritsPrincipal.emplace(inherits);
}
return mInheritsPrincipal.ref();
}
/*****************************************************************************
* CookieInfo
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/WebExtensionContentScript.h
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_extensions_WebExtensionContentScript_h
+#define mozilla_extensions_WebExtensionContentScript_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/WebExtensionContentScriptBinding.h"
+
+#include "jsapi.h"
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Variant.h"
+#include "mozilla/extensions/MatchGlob.h"
+#include "mozilla/extensions/MatchPattern.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+class nsILoadInfo;
+class nsPIDOMWindowOuter;
+
+namespace mozilla {
+namespace extensions {
+
+using dom::Nullable;
+using ContentScriptInit = dom::WebExtensionContentScriptInit;;
+
+class WebExtensionPolicy;
+
+class MOZ_STACK_CLASS DocInfo final
+{
+public:
+ DocInfo(const URLInfo& aURL, nsILoadInfo* aLoadInfo);
+
+ MOZ_IMPLICIT DocInfo(nsPIDOMWindowOuter* aWindow);
+
+ const URLInfo& URL() const { return mURL; }
+
+ nsIPrincipal* Principal() const;
+
+ const URLInfo& PrincipalURL() const;
+
+ bool IsTopLevel() const;
+
+ uint64_t FrameID() const;
+
+private:
+ void SetURL(const URLInfo& aURL);
+
+ const URLInfo mURL;
+ mutable Maybe<const URLInfo> mPrincipalURL;
+
+ mutable Maybe<bool> mIsTopLevel;
+ mutable Maybe<nsCOMPtr<nsIPrincipal>> mPrincipal;
+ mutable Maybe<uint64_t> mFrameID;
+
+ using Window = nsPIDOMWindowOuter*;
+ using LoadInfo = nsILoadInfo*;
+
+ const Variant<LoadInfo, Window> mObj;
+};
+
+
+class WebExtensionContentScript final : public nsISupports
+ , public nsWrapperCache
+{
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebExtensionContentScript)
+
+
+ using MatchGlobArray = nsTArray<RefPtr<MatchGlob>>;
+ using RunAtEnum = dom::ContentScriptRunAt;
+
+ static already_AddRefed<WebExtensionContentScript>
+ Constructor(dom::GlobalObject& aGlobal,
+ WebExtensionPolicy& aExtension,
+ const ContentScriptInit& aInit,
+ ErrorResult& aRv);
+
+
+ bool Matches(const DocInfo& aDoc) const;
+ bool MatchesURI(const URLInfo& aURL) const;
+
+ bool MatchesLoadInfo(const URLInfo& aURL, nsILoadInfo* aLoadInfo) const
+ {
+ return Matches({aURL, aLoadInfo});
+ }
+ bool MatchesWindow(nsPIDOMWindowOuter* aWindow) const
+ {
+ return Matches(aWindow);
+ }
+
+
+ WebExtensionPolicy* Extension() { return mExtension; }
+ const WebExtensionPolicy* Extension() const { return mExtension; }
+
+ bool AllFrames() const { return mAllFrames; }
+ bool MatchAboutBlank() const { return mMatchAboutBlank; }
+ RunAtEnum RunAt() const { return mRunAt; }
+
+ Nullable<uint64_t> GetFrameID() const { return mFrameID; }
+
+ MatchPatternSet* Matches() { return mMatches; }
+ const MatchPatternSet* GetMatches() const { return mMatches; }
+
+ MatchPatternSet* GetExcludeMatches() { return mExcludeMatches; }
+ const MatchPatternSet* GetExcludeMatches() const { return mExcludeMatches; }
+
+ void GetIncludeGlobs(Nullable<MatchGlobArray>& aGlobs)
+ {
+ ToNullable(mExcludeGlobs, aGlobs);
+ }
+ void GetExcludeGlobs(Nullable<MatchGlobArray>& aGlobs)
+ {
+ ToNullable(mExcludeGlobs, aGlobs);
+ }
+
+ void GetCssPaths(nsTArray<nsString>& aPaths) const
+ {
+ aPaths.AppendElements(mCssPaths);
+ }
+ void GetJsPaths(nsTArray<nsString>& aPaths) const
+ {
+ aPaths.AppendElements(mJsPaths);
+ }
+
+
+ WebExtensionPolicy* GetParentObject() const { return mExtension; }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
+
+protected:
+ friend class WebExtensionPolicy;
+
+ virtual ~WebExtensionContentScript() = default;
+
+ WebExtensionContentScript(WebExtensionPolicy& aExtension,
+ const ContentScriptInit& aInit,
+ ErrorResult& aRv);
+
+private:
+ RefPtr<WebExtensionPolicy> mExtension;
+
+ RefPtr<MatchPatternSet> mMatches;
+ RefPtr<MatchPatternSet> mExcludeMatches;
+
+ Nullable<MatchGlobSet> mIncludeGlobs;
+ Nullable<MatchGlobSet> mExcludeGlobs;
+
+ nsTArray<nsString> mCssPaths;
+ nsTArray<nsString> mJsPaths;
+
+ RunAtEnum mRunAt;
+
+ bool mAllFrames;
+ Nullable<uint64_t> mFrameID;
+ bool mMatchAboutBlank;
+
+ template <typename T, typename U>
+ void
+ ToNullable(const Nullable<T>& aInput, Nullable<U>& aOutput)
+ {
+ if (aInput.IsNull()) {
+ aOutput.SetNull();
+ } else {
+ aOutput.SetValue(aInput.Value());
+ }
+ }
+};
+
+} // namespace extensions
+} // namespace mozilla
+
+#endif // mozilla_extensions_WebExtensionContentScript_h
--- a/toolkit/components/extensions/WebExtensionPolicy.cpp
+++ b/toolkit/components/extensions/WebExtensionPolicy.cpp
@@ -1,15 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/extensions/WebExtensionContentScript.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
+#include "mozilla/AddonManagerWebAPI.h"
#include "nsEscape.h"
#include "nsISubstitutingProtocolHandler.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
namespace mozilla {
namespace extensions {
@@ -105,16 +107,26 @@ WebExtensionPolicy::WebExtensionPolicy(G
if (!aInit.mBackgroundScripts.IsNull()) {
mBackgroundScripts.SetValue().AppendElements(aInit.mBackgroundScripts.Value());
}
if (mContentSecurityPolicy.IsVoid()) {
EPS().DefaultCSP(mContentSecurityPolicy);
}
+ mContentScripts.SetCapacity(aInit.mContentScripts.Length());
+ for (const auto& scriptInit : aInit.mContentScripts) {
+ RefPtr<WebExtensionContentScript> contentScript =
+ new WebExtensionContentScript(*this, scriptInit, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ mContentScripts.AppendElement(Move(contentScript));
+ }
+
nsresult rv = NS_NewURI(getter_AddRefs(mBaseURI), aInit.mBaseURL);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
}
}
already_AddRefed<WebExtensionPolicy>
WebExtensionPolicy::Constructor(GlobalObject& aGlobal,
@@ -259,24 +271,225 @@ WebExtensionPolicy::Localize(const nsASt
JSObject*
WebExtensionPolicy::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
{
return WebExtensionPolicyBinding::Wrap(aCx, this, aGivenProto);
}
+void
+WebExtensionPolicy::GetContentScripts(nsTArray<RefPtr<WebExtensionContentScript>>& aScripts) const
+{
+ aScripts.AppendElements(mContentScripts);
+}
+
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebExtensionPolicy, mParent,
mLocalizeCallback,
mHostPermissions,
mWebAccessiblePaths)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionPolicy)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionPolicy)
NS_IMPL_CYCLE_COLLECTING_RELEASE(WebExtensionPolicy)
+
+/*****************************************************************************
+ * WebExtensionContentScript
+ *****************************************************************************/
+
+/* static */ already_AddRefed<WebExtensionContentScript>
+WebExtensionContentScript::Constructor(GlobalObject& aGlobal,
+ WebExtensionPolicy& aExtension,
+ const ContentScriptInit& aInit,
+ ErrorResult& aRv)
+{
+ RefPtr<WebExtensionContentScript> script = new WebExtensionContentScript(aExtension, aInit, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ return script.forget();
+}
+
+WebExtensionContentScript::WebExtensionContentScript(WebExtensionPolicy& aExtension,
+ const ContentScriptInit& aInit,
+ ErrorResult& aRv)
+ : mExtension(&aExtension)
+ , mMatches(aInit.mMatches)
+ , mExcludeMatches(aInit.mExcludeMatches)
+ , mCssPaths(aInit.mCssPaths)
+ , mJsPaths(aInit.mJsPaths)
+ , mRunAt(aInit.mRunAt)
+ , mAllFrames(aInit.mAllFrames)
+ , mFrameID(aInit.mFrameID)
+ , mMatchAboutBlank(aInit.mMatchAboutBlank)
+{
+ if (!aInit.mIncludeGlobs.IsNull()) {
+ mIncludeGlobs.SetValue().AppendElements(aInit.mIncludeGlobs.Value());
+ }
+
+ if (!aInit.mExcludeGlobs.IsNull()) {
+ mExcludeGlobs.SetValue().AppendElements(aInit.mExcludeGlobs.Value());
+ }
+}
+
+
+bool
+WebExtensionContentScript::Matches(const DocInfo& aDoc) const
+{
+ if (!mFrameID.IsNull() && aDoc.FrameID() != mFrameID.Value()) {
+ return false;
+ }
+
+ if (!mAllFrames && !aDoc.IsTopLevel()) {
+ return false;
+ }
+
+ if (!mMatchAboutBlank && aDoc.URL().InheritsPrincipal()) {
+ return false;
+ }
+
+ if (!MatchesURI(aDoc.PrincipalURL())) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+WebExtensionContentScript::MatchesURI(const URLInfo& aURL) const
+{
+ if (!mMatches->Matches(aURL)) {
+ return false;
+ }
+
+ if (mExcludeMatches && mExcludeMatches->Matches(aURL)) {
+ return false;
+ }
+
+ if (!mIncludeGlobs.IsNull() && !mIncludeGlobs.Value().Matches(aURL.Spec())) {
+ return false;
+ }
+
+ if (!mExcludeGlobs.IsNull() && mExcludeGlobs.Value().Matches(aURL.Spec())) {
+ return false;
+ }
+
+ if (AddonManagerWebAPI::IsValidSite(aURL.URI())) {
+ return false;
+ }
+
+ return true;
+}
+
+
+JSObject*
+WebExtensionContentScript::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
+{
+ return WebExtensionContentScriptBinding::Wrap(aCx, this, aGivenProto);
+}
+
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebExtensionContentScript,
+ mMatches, mExcludeMatches,
+ mIncludeGlobs, mExcludeGlobs,
+ mExtension)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionContentScript)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionContentScript)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebExtensionContentScript)
+
+
+/*****************************************************************************
+ * DocInfo
+ *****************************************************************************/
+
+DocInfo::DocInfo(const URLInfo& aURL, nsILoadInfo* aLoadInfo)
+ : mURL(aURL)
+ , mObj(AsVariant(aLoadInfo))
+{}
+
+DocInfo::DocInfo(nsPIDOMWindowOuter* aWindow)
+ : mURL(aWindow->GetDocumentURI())
+ , mObj(AsVariant(aWindow))
+{}
+
+bool
+DocInfo::IsTopLevel() const
+{
+ if (mIsTopLevel.isNothing()) {
+ struct Matcher
+ {
+ bool match(Window aWin) { return aWin->IsTopLevelWindow(); }
+ bool match(LoadInfo aLoadInfo) { return aLoadInfo->GetIsTopLevelLoad(); }
+ };
+ mIsTopLevel.emplace(mObj.match(Matcher()));
+ }
+ return mIsTopLevel.ref();
+}
+
+uint64_t
+DocInfo::FrameID() const
+{
+ if (mFrameID.isNothing()) {
+ if (IsTopLevel()) {
+ mFrameID.emplace(0);
+ } else {
+ struct Matcher
+ {
+ uint64_t match(Window aWin) { return aWin->GetCurrentInnerWindow()->WindowID(); }
+ uint64_t match(LoadInfo aLoadInfo) { return aLoadInfo->GetInnerWindowID(); }
+ };
+ mFrameID.emplace(mObj.match(Matcher()));
+ }
+ }
+ return mFrameID.ref();
+}
+
+nsIPrincipal*
+DocInfo::Principal() const
+{
+ if (mPrincipal.isNothing()) {
+ struct Matcher
+ {
+ nsIPrincipal* match(Window aWin)
+ {
+ nsCOMPtr<nsIDocument> doc = aWin->GetDoc();
+ return doc->NodePrincipal();
+ }
+ nsIPrincipal* match(LoadInfo aLoadInfo) { return aLoadInfo->PrincipalToInherit(); }
+ };
+ mPrincipal.emplace(mObj.match(Matcher()));
+ }
+ return mPrincipal.ref();
+}
+
+const URLInfo&
+DocInfo::PrincipalURL() const
+{
+ if (!URL().InheritsPrincipal()) {
+ return URL();
+ }
+
+ if (mPrincipalURL.isNothing()) {
+ nsIPrincipal* prin = Principal();
+ nsCOMPtr<nsIURI> uri;
+ if (prin && NS_SUCCEEDED(prin->GetURI(getter_AddRefs(uri)))) {
+ mPrincipalURL.emplace(uri);
+ } else {
+ mPrincipalURL.emplace(URL());
+ }
+ }
+
+ return mPrincipalURL.ref();
+}
+
} // namespace extensions
} // namespace mozilla
--- a/toolkit/components/extensions/WebExtensionPolicy.h
+++ b/toolkit/components/extensions/WebExtensionPolicy.h
@@ -20,24 +20,28 @@
#include "nsWrapperCache.h"
namespace mozilla {
namespace extensions {
using dom::WebExtensionInit;
using dom::WebExtensionLocalizeCallback;
+class WebExtensionContentScript;
+
class WebExtensionPolicy final : public nsISupports
, public nsWrapperCache
, public SupportsWeakPtr<WebExtensionPolicy>
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebExtensionPolicy)
+ using ScriptArray = nsTArray<RefPtr<WebExtensionContentScript>>;
+
static already_AddRefed<WebExtensionPolicy>
Constructor(dom::GlobalObject& aGlobal, const WebExtensionInit& aInit, ErrorResult& aRv);
nsIAtom* Id() const { return mId; }
void GetId(nsAString& aId) const { aId = nsDependentAtomString(mId); };
const nsCString& MozExtensionHostname() const { return mHostname; }
void GetMozExtensionHostname(nsACString& aHostname) const
@@ -101,16 +105,19 @@ public:
{
mPermissions->Get(aResult);
}
void SetPermissions(const nsTArray<nsString>& aPermissions)
{
mPermissions = new AtomSet(aPermissions);
}
+ void GetContentScripts(ScriptArray& aScripts) const;
+ const ScriptArray& ContentScripts() const { return mContentScripts; }
+
bool Active() const { return mActive; }
void SetActive(bool aActive, ErrorResult& aRv);
static void
GetActiveExtensions(dom::GlobalObject& aGlobal, nsTArray<RefPtr<WebExtensionPolicy>>& aResults);
@@ -149,14 +156,16 @@ private:
RefPtr<WebExtensionLocalizeCallback> mLocalizeCallback;
RefPtr<AtomSet> mPermissions;
RefPtr<MatchPatternSet> mHostPermissions;
MatchGlobSet mWebAccessiblePaths;
Nullable<nsTArray<nsString>> mBackgroundScripts;
+
+ nsTArray<RefPtr<WebExtensionContentScript>> mContentScripts;
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_WebExtensionPolicy_h
--- a/toolkit/components/extensions/moz.build
+++ b/toolkit/components/extensions/moz.build
@@ -46,16 +46,17 @@ DIRS += [
EXPORTS.mozilla = [
'ExtensionPolicyService.h',
]
EXPORTS.mozilla.extensions = [
'MatchGlob.h',
'MatchPattern.h',
+ 'WebExtensionContentScript.h',
'WebExtensionPolicy.h',
]
UNIFIED_SOURCES += [
'ExtensionPolicyService.cpp',
'MatchPattern.cpp',
'WebExtensionPolicy.cpp',
]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/data/file_iframe.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Iframe document</title>
+</head>
+<body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/data/file_toplevel.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Top-level frame document</title>
+</head>
+<body>
+ <iframe src="file_iframe.html"></iframe>
+ <iframe src="about:blank"></iframe>
+ <iframe srcdoc="Iframe srcdoc"></iframe>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_WebExtensionContentScript.js
@@ -0,0 +1,157 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const {newURI} = Services.io;
+
+const server = createHttpServer();
+server.registerDirectory("/data/", do_get_file("data"));
+
+let policy = new WebExtensionPolicy({
+ id: "foo@bar.baz",
+ mozExtensionHostname: "88fb51cd-159f-4859-83db-7065485bc9b2",
+ baseURL: "file:///foo",
+
+ allowedOrigins: new MatchPatternSet([]),
+ localizeCallback() {},
+});
+
+add_task(async function test_WebExtensinonContentScript_url_matching() {
+ let contentScript = new WebExtensionContentScript(policy, {
+ matches: new MatchPatternSet(["http://foo.com/bar", "*://bar.com/baz/*"]),
+
+ excludeMatches: new MatchPatternSet(["*://bar.com/baz/quux"]),
+
+ includeGlobs: ["*flerg*", "*.com/bar", "*/quux"].map(glob => new MatchGlob(glob)),
+
+ excludeGlobs: ["*glorg*"].map(glob => new MatchGlob(glob)),
+ });
+
+ ok(contentScript.matchesURI(newURI("http://foo.com/bar")),
+ "Simple matches include should match");
+
+ ok(contentScript.matchesURI(newURI("https://bar.com/baz/xflergx")),
+ "Simple matches include should match");
+
+ ok(!contentScript.matchesURI(newURI("https://bar.com/baz/quux")),
+ "Excluded match pattern should not match");
+
+ ok(!contentScript.matchesURI(newURI("https://bar.com/baz/xflergxglorgx")),
+ "Excluded match glob should not match");
+});
+
+async function loadURL(url, {frameCount}) {
+ let windows = new Map();
+ let requests = new Map();
+
+ let resolveLoad;
+ let loadPromise = new Promise(resolve => { resolveLoad = resolve; });
+
+ function requestObserver(request) {
+ request.QueryInterface(Ci.nsIChannel);
+ if (request.isDocument) {
+ requests.set(request.name, request);
+ }
+ }
+ function loadObserver(window) {
+ windows.set(window.location.href, window);
+ if (windows.size == frameCount) {
+ resolveLoad();
+ }
+ }
+
+ Services.obs.addObserver(requestObserver, "http-on-examine-response");
+ Services.obs.addObserver(loadObserver, "content-document-global-created");
+
+ let webNav = Services.appShell.createWindowlessBrowser(false);
+ webNav.loadURI(url, 0, null, null, null);
+
+ await loadPromise;
+
+ Services.obs.removeObserver(requestObserver, "http-on-examine-response");
+ Services.obs.removeObserver(loadObserver, "content-document-global-created");
+
+ return {webNav, windows, requests};
+}
+
+add_task(async function test_WebExtensinonContentScript_frame_matching() {
+ let baseURL = `http://localhost:${server.identity.primaryPort}/data`;
+ let urls = {
+ topLevel: `${baseURL}/file_toplevel.html`,
+ iframe: `${baseURL}/file_iframe.html`,
+ srcdoc: "about:srcdoc",
+ aboutBlank: "about:blank",
+ };
+
+ let {webNav, windows, requests} = await loadURL(urls.topLevel, {frameCount: 4});
+
+ let tests = [
+ {
+ contentScript: {
+ matches: new MatchPatternSet(["http://localhost/data/*"]),
+ },
+ topLevel: true,
+ iframe: false,
+ aboutBlank: false,
+ srcdoc: false,
+ },
+
+ {
+ contentScript: {
+ matches: new MatchPatternSet(["http://localhost/data/*"]),
+ allFrames: true,
+ },
+ topLevel: true,
+ iframe: true,
+ aboutBlank: false,
+ srcdoc: false,
+ },
+
+ {
+ contentScript: {
+ matches: new MatchPatternSet(["http://localhost/data/*"]),
+ allFrames: true,
+ matchAboutBlank: true,
+ },
+ topLevel: true,
+ iframe: true,
+ aboutBlank: true,
+ srcdoc: true,
+ },
+
+ {
+ contentScript: {
+ matches: new MatchPatternSet(["http://foo.com/data/*"]),
+ allFrames: true,
+ matchAboutBlank: true,
+ },
+ topLevel: false,
+ iframe: false,
+ aboutBlank: false,
+ srcdoc: false,
+ },
+ ];
+
+ for (let [i, test] of tests.entries()) {
+ let contentScript = new WebExtensionContentScript(policy, test.contentScript);
+
+ for (let [frame, url] of Object.entries(urls)) {
+ let should = test[frame] ? "should" : "should not";
+
+ equal(contentScript.matchesWindow(windows.get(url)),
+ test[frame],
+ `Script ${i} ${should} match the ${frame} frame`);
+
+ if (url.startsWith("http")) {
+ let request = requests.get(url);
+
+ equal(contentScript.matchesLoadInfo(request.URI, request.loadInfo),
+ test[frame],
+ `Script ${i} ${should} match the request LoadInfo for ${frame} frame`);
+ }
+ }
+ }
+
+ webNav.close();
+ void requests;
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -5,16 +5,17 @@ skip-if = appname == "thunderbird"
dupe-manifest =
support-files =
data/**
head_sync.js
xpcshell-content.ini
tags = webextensions
[test_MatchPattern.js]
+[test_WebExtensionContentScript.js]
[test_WebExtensionPolicy.js]
[test_csp_custom_policies.js]
[test_csp_validator.js]
[test_ext_alarms.js]
[test_ext_alarms_does_not_fire.js]
[test_ext_alarms_periodic.js]
[test_ext_alarms_replaces.js]