Bug 1368102: Part 2 - Add WebExtensionContentScript bindings. r=billm,mixedpuppy

Bill, can you please review the binding code? Shane and zombie, can you please
review the content script matching?

MozReview-Commit-ID: IJB5s0a7r7S
This commit is contained in:
Kris Maglione
2017-06-03 22:03:19 -07:00
parent 29d72d873d
commit eb1e83769a
16 changed files with 793 additions and 5 deletions

View File

@@ -2011,6 +2011,7 @@ GK_ATOM(mozinputrangeignorepreventdefault, "mozinputrangeignorepreventdefault")
// WebExtensions // WebExtensions
GK_ATOM(moz_extension, "moz-extension") GK_ATOM(moz_extension, "moz-extension")
GK_ATOM(all_urlsPermission, "<all_urls>")
GK_ATOM(http, "http") GK_ATOM(http, "http")
GK_ATOM(https, "https") GK_ATOM(https, "https")

View File

@@ -1101,6 +1101,10 @@ DOMInterfaces = {
'implicitJSContext': 'makeCredential', 'implicitJSContext': 'makeCredential',
}, },
'WebExtensionContentScript': {
'nativeType': 'mozilla::extensions::WebExtensionContentScript',
},
'WebExtensionPolicy': { 'WebExtensionPolicy': {
'nativeType': 'mozilla::extensions::WebExtensionPolicy', 'nativeType': 'mozilla::extensions::WebExtensionPolicy',
}, },
@@ -1713,6 +1717,8 @@ addExternalIface('RTCDataChannel', nativeType='nsIDOMDataChannel')
addExternalIface('HitRegionOptions', nativeType='nsISupports') addExternalIface('HitRegionOptions', nativeType='nsISupports')
addExternalIface('imgINotificationObserver', nativeType='imgINotificationObserver') addExternalIface('imgINotificationObserver', nativeType='imgINotificationObserver')
addExternalIface('imgIRequest', nativeType='imgIRequest', notflattened=True) addExternalIface('imgIRequest', nativeType='imgIRequest', notflattened=True)
addExternalIface('LoadInfo', nativeType='nsILoadInfo',
headerFile='nsILoadInfo.h', notflattened=True)
addExternalIface('MenuBuilder', nativeType='nsIMenuBuilder', notflattened=True) addExternalIface('MenuBuilder', nativeType='nsIMenuBuilder', notflattened=True)
addExternalIface('MozControllers', nativeType='nsIControllers') addExternalIface('MozControllers', nativeType='nsIControllers')
addExternalIface('MozFrameLoader', nativeType='nsIFrameLoader', notflattened=True) addExternalIface('MozFrameLoader', nativeType='nsIFrameLoader', notflattened=True)

View File

@@ -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-named, 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 = [];
};

View File

@@ -58,6 +58,12 @@ interface WebExtensionPolicy {
[Pure] [Pure]
attribute MatchPatternSet allowedOrigins; 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, * True if the extension is currently active, false otherwise. When active,
* the extension's moz-extension: protocol will point to the given baseURI, * the extension's moz-extension: protocol will point to the given baseURI,
@@ -141,6 +147,8 @@ dictionary WebExtensionInit {
sequence<MatchGlob> webAccessibleResources = []; sequence<MatchGlob> webAccessibleResources = [];
sequence<WebExtensionContentScriptInit> contentScripts = [];
DOMString? contentSecurityPolicy = null; DOMString? contentSecurityPolicy = null;
sequence<DOMString>? backgroundScripts = null; sequence<DOMString>? backgroundScripts = null;

View File

@@ -944,6 +944,7 @@ WEBIDL_FILES = [
'WaveShaperNode.webidl', 'WaveShaperNode.webidl',
'WebAuthentication.webidl', 'WebAuthentication.webidl',
'WebComponents.webidl', 'WebComponents.webidl',
'WebExtensionContentScript.webidl',
'WebExtensionPolicy.webidl', 'WebExtensionPolicy.webidl',
'WebGL2RenderingContext.webidl', 'WebGL2RenderingContext.webidl',
'WebGLRenderingContext.webidl', 'WebGLRenderingContext.webidl',

View File

@@ -13,7 +13,9 @@ module.exports = {
"MatchGlob": false, "MatchGlob": false,
"MatchPattern": true, "MatchPattern": true,
"MatchPatternSet": false, "MatchPatternSet": false,
"WebExtensionContentScript": false,
"WebExtensionPolicy": false, "WebExtensionPolicy": false,
// Specific to WebExtensions: // Specific to WebExtensions:
"AppConstants": true, "AppConstants": true,
"Extension": true, "Extension": true,

View File

@@ -4,6 +4,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ExtensionPolicyService.h" #include "mozilla/ExtensionPolicyService.h"
#include "mozilla/extensions/WebExtensionContentScript.h"
#include "mozilla/extensions/WebExtensionPolicy.h" #include "mozilla/extensions/WebExtensionPolicy.h"
#include "mozilla/ClearOnShutdown.h" #include "mozilla/ClearOnShutdown.h"

View File

@@ -185,10 +185,16 @@ bool
URLInfo::InheritsPrincipal() const URLInfo::InheritsPrincipal() const
{ {
if (!mInheritsPrincipal.isSome()) { if (!mInheritsPrincipal.isSome()) {
bool inherits = false; // For our purposes, about:blank and about:srcdoc are treated as URIs that
nsresult rv = NS_URIChainHasFlags(mURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, // inherit principals.
&inherits); bool inherits = Spec().EqualsLiteral("about:blank") || Spec().EqualsLiteral("about:srcdoc");
Unused << NS_WARN_IF(NS_FAILED(rv));
if (!inherits) {
nsresult rv = NS_URIChainHasFlags(mURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
&inherits);
Unused << NS_WARN_IF(NS_FAILED(rv));
}
mInheritsPrincipal.emplace(inherits); mInheritsPrincipal.emplace(inherits);
} }
return mInheritsPrincipal.ref(); return mInheritsPrincipal.ref();

View File

@@ -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 "jspubtd.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

View File

@@ -4,8 +4,10 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ExtensionPolicyService.h" #include "mozilla/ExtensionPolicyService.h"
#include "mozilla/extensions/WebExtensionContentScript.h"
#include "mozilla/extensions/WebExtensionPolicy.h" #include "mozilla/extensions/WebExtensionPolicy.h"
#include "mozilla/AddonManagerWebAPI.h"
#include "nsEscape.h" #include "nsEscape.h"
#include "nsISubstitutingProtocolHandler.h" #include "nsISubstitutingProtocolHandler.h"
#include "nsNetUtil.h" #include "nsNetUtil.h"
@@ -111,6 +113,16 @@ WebExtensionPolicy::WebExtensionPolicy(GlobalObject& aGlobal,
EPS().DefaultCSP(mContentSecurityPolicy); 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); nsresult rv = NS_NewURI(getter_AddRefs(mBaseURI), aInit.mBaseURL);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
aRv.Throw(rv); aRv.Throw(rv);
@@ -262,11 +274,18 @@ WebExtensionPolicy::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
return WebExtensionPolicyBinding::Wrap(aCx, this, 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, NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebExtensionPolicy, mParent,
mLocalizeCallback, mLocalizeCallback,
mHostPermissions, mHostPermissions,
mWebAccessiblePaths) mWebAccessiblePaths,
mContentScripts)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionPolicy) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionPolicy)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
@@ -276,5 +295,202 @@ NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionPolicy) NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionPolicy)
NS_IMPL_CYCLE_COLLECTING_RELEASE(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()) {
if (aDoc.FrameID() != mFrameID.Value()) {
return false;
}
} else {
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 extensions
} // namespace mozilla } // namespace mozilla

View File

@@ -25,6 +25,8 @@ namespace extensions {
using dom::WebExtensionInit; using dom::WebExtensionInit;
using dom::WebExtensionLocalizeCallback; using dom::WebExtensionLocalizeCallback;
class WebExtensionContentScript;
class WebExtensionPolicy final : public nsISupports class WebExtensionPolicy final : public nsISupports
, public nsWrapperCache , public nsWrapperCache
, public SupportsWeakPtr<WebExtensionPolicy> , public SupportsWeakPtr<WebExtensionPolicy>
@@ -34,6 +36,8 @@ public:
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebExtensionPolicy) NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebExtensionPolicy)
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebExtensionPolicy) MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebExtensionPolicy)
using ScriptArray = nsTArray<RefPtr<WebExtensionContentScript>>;
static already_AddRefed<WebExtensionPolicy> static already_AddRefed<WebExtensionPolicy>
Constructor(dom::GlobalObject& aGlobal, const WebExtensionInit& aInit, ErrorResult& aRv); Constructor(dom::GlobalObject& aGlobal, const WebExtensionInit& aInit, ErrorResult& aRv);
@@ -106,6 +110,9 @@ public:
mPermissions = new AtomSet(aPermissions); mPermissions = new AtomSet(aPermissions);
} }
void GetContentScripts(ScriptArray& aScripts) const;
const ScriptArray& ContentScripts() const { return mContentScripts; }
bool Active() const { return mActive; } bool Active() const { return mActive; }
void SetActive(bool aActive, ErrorResult& aRv); void SetActive(bool aActive, ErrorResult& aRv);
@@ -154,6 +161,8 @@ private:
MatchGlobSet mWebAccessiblePaths; MatchGlobSet mWebAccessiblePaths;
Nullable<nsTArray<nsString>> mBackgroundScripts; Nullable<nsTArray<nsString>> mBackgroundScripts;
nsTArray<RefPtr<WebExtensionContentScript>> mContentScripts;
}; };
} // namespace extensions } // namespace extensions

View File

@@ -52,6 +52,7 @@ EXPORTS.mozilla = [
EXPORTS.mozilla.extensions = [ EXPORTS.mozilla.extensions = [
'MatchGlob.h', 'MatchGlob.h',
'MatchPattern.h', 'MatchPattern.h',
'WebExtensionContentScript.h',
'WebExtensionPolicy.h', 'WebExtensionPolicy.h',
] ]

View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Iframe document</title>
</head>
<body>
</body>
</html>

View File

@@ -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>

View File

@@ -0,0 +1,176 @@
/* -*- 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/xx")),
"Failed includeGlobs match pattern should not 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() {
if (AppConstants.platform == "linux") {
// The windowless browser currently does not load correctly on Linux on
// infra.
return;
}
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/*"]),
frameID: 0,
},
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();
});

View File

@@ -10,6 +10,7 @@ support-files =
tags = webextensions tags = webextensions
[test_MatchPattern.js] [test_MatchPattern.js]
[test_WebExtensionContentScript.js]
[test_WebExtensionPolicy.js] [test_WebExtensionPolicy.js]
[test_csp_custom_policies.js] [test_csp_custom_policies.js]