1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "MainThreadUtils.h"
7 #include "mozilla/ExtensionPolicyService.h"
8 #include "mozilla/extensions/DocumentObserver.h"
9 #include "mozilla/extensions/WebExtensionContentScript.h"
10 #include "mozilla/extensions/WebExtensionPolicy.h"
12 #include "mozilla/AddonManagerWebAPI.h"
13 #include "mozilla/BasePrincipal.h"
14 #include "mozilla/dom/WindowGlobalChild.h"
15 #include "mozilla/ResultExtensions.h"
16 #include "mozilla/StaticPrefs_extensions.h"
17 #include "mozilla/Try.h"
18 #include "nsContentUtils.h"
20 #include "nsGlobalWindowInner.h"
21 #include "nsIObserver.h"
22 #include "nsISubstitutingProtocolHandler.h"
23 #include "nsLiteralString.h"
24 #include "nsNetUtil.h"
25 #include "nsPrintfCString.h"
28 namespace extensions
{
32 static const char kProto
[] = "moz-extension";
34 static const char kBackgroundScriptTypeDefault
[] = "text/javascript";
36 static const char kBackgroundScriptTypeModule
[] = "module";
38 static const char kBackgroundPageHTMLStart
[] =
41 <head><meta charset=\"utf-8\"></head>\n\
44 static const char kBackgroundPageHTMLScript
[] =
46 <script type=\"%s\" src=\"%s\"></script>";
48 static const char kBackgroundPageHTMLEnd
[] =
53 #define BASE_CSP_PREF_V2 "extensions.webextensions.base-content-security-policy"
54 #define DEFAULT_BASE_CSP_V2 \
55 "script-src 'self' https://* http://localhost:* http://127.0.0.1:* " \
56 "moz-extension: blob: filesystem: 'unsafe-eval' 'wasm-unsafe-eval' " \
59 #define BASE_CSP_PREF_V3 \
60 "extensions.webextensions.base-content-security-policy.v3"
61 #define DEFAULT_BASE_CSP_V3 "script-src 'self' 'wasm-unsafe-eval';"
63 static inline ExtensionPolicyService
& EPS() {
64 return ExtensionPolicyService::GetSingleton();
67 static nsISubstitutingProtocolHandler
* Proto() {
68 static nsCOMPtr
<nsISubstitutingProtocolHandler
> sHandler
;
70 if (MOZ_UNLIKELY(!sHandler
)) {
71 nsCOMPtr
<nsIIOService
> ios
= do_GetIOService();
72 MOZ_RELEASE_ASSERT(ios
);
74 nsCOMPtr
<nsIProtocolHandler
> handler
;
75 ios
->GetProtocolHandler(kProto
, getter_AddRefs(handler
));
77 sHandler
= do_QueryInterface(handler
);
78 MOZ_RELEASE_ASSERT(sHandler
);
80 ClearOnShutdown(&sHandler
);
86 bool ParseGlobs(GlobalObject
& aGlobal
,
87 Sequence
<OwningMatchGlobOrUTF8String
> aGlobs
,
88 nsTArray
<RefPtr
<MatchGlobCore
>>& aResult
, ErrorResult
& aRv
) {
89 for (auto& elem
: aGlobs
) {
90 if (elem
.IsMatchGlob()) {
91 aResult
.AppendElement(elem
.GetAsMatchGlob()->Core());
93 RefPtr
<MatchGlobCore
> glob
=
94 new MatchGlobCore(elem
.GetAsUTF8String(), true, false, aRv
);
98 aResult
.AppendElement(glob
);
104 enum class ErrorBehavior
{
109 already_AddRefed
<MatchPatternSet
> ParseMatches(
110 GlobalObject
& aGlobal
,
111 const OwningMatchPatternSetOrStringSequence
& aMatches
,
112 const MatchPatternOptions
& aOptions
, ErrorBehavior aErrorBehavior
,
114 if (aMatches
.IsMatchPatternSet()) {
115 return do_AddRef(aMatches
.GetAsMatchPatternSet().get());
118 const auto& strings
= aMatches
.GetAsStringSequence();
120 nsTArray
<OwningStringOrMatchPattern
> patterns
;
121 if (!patterns
.SetCapacity(strings
.Length(), fallible
)) {
122 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
126 for (auto& string
: strings
) {
127 OwningStringOrMatchPattern elt
;
128 elt
.SetAsString() = string
;
129 patterns
.AppendElement(elt
);
132 RefPtr
<MatchPatternSet
> result
=
133 MatchPatternSet::Constructor(aGlobal
, patterns
, aOptions
, aRv
);
135 if (aRv
.Failed() && aErrorBehavior
== ErrorBehavior::CreateEmptyPattern
) {
136 aRv
.SuppressException();
137 result
= MatchPatternSet::Constructor(aGlobal
, {}, aOptions
, aRv
);
140 return result
.forget();
143 WebAccessibleResource::WebAccessibleResource(
144 GlobalObject
& aGlobal
, const WebAccessibleResourceInit
& aInit
,
146 ParseGlobs(aGlobal
, aInit
.mResources
, mWebAccessiblePaths
, aRv
);
151 if (!aInit
.mMatches
.IsNull()) {
152 MatchPatternOptions options
;
153 options
.mRestrictSchemes
= true;
154 RefPtr
<MatchPatternSet
> matches
=
155 ParseMatches(aGlobal
, aInit
.mMatches
.Value(), options
,
156 ErrorBehavior::CreateEmptyPattern
, aRv
);
157 MOZ_DIAGNOSTIC_ASSERT(!aRv
.Failed());
158 mMatches
= matches
->Core();
161 if (!aInit
.mExtension_ids
.IsNull()) {
162 mExtensionIDs
= new AtomSet(aInit
.mExtension_ids
.Value());
166 bool WebAccessibleResource::IsExtensionMatch(const URLInfo
& aURI
) {
167 if (!mExtensionIDs
) {
170 RefPtr
<WebExtensionPolicyCore
> policy
=
171 ExtensionPolicyService::GetCoreByHost(aURI
.Host());
172 return policy
&& (mExtensionIDs
->Contains(nsGkAtoms::_asterisk
) ||
173 mExtensionIDs
->Contains(policy
->Id()));
176 /*****************************************************************************
177 * WebExtensionPolicyCore
178 *****************************************************************************/
180 WebExtensionPolicyCore::WebExtensionPolicyCore(GlobalObject
& aGlobal
,
181 WebExtensionPolicy
* aPolicy
,
182 const WebExtensionInit
& aInit
,
185 mId(NS_AtomizeMainThread(aInit
.mId
)),
187 mType(NS_AtomizeMainThread(aInit
.mType
)),
188 mManifestVersion(aInit
.mManifestVersion
),
189 mExtensionPageCSP(aInit
.mExtensionPageCSP
),
190 mIsPrivileged(aInit
.mIsPrivileged
),
191 mTemporarilyInstalled(aInit
.mTemporarilyInstalled
),
192 mBackgroundWorkerScript(aInit
.mBackgroundWorkerScript
),
193 mIgnoreQuarantine(aInit
.mIsPrivileged
|| aInit
.mIgnoreQuarantine
),
194 mPermissions(new AtomSet(aInit
.mPermissions
)) {
195 // In practice this is not necessary, but in tests where the uuid
196 // passed in is not lowercased various tests can fail.
197 ToLowerCase(aInit
.mMozExtensionHostname
, mHostname
);
199 // Initialize the base CSP and extension page CSP
200 if (mManifestVersion
< 3) {
201 nsresult rv
= Preferences::GetString(BASE_CSP_PREF_V2
, mBaseCSP
);
203 mBaseCSP
= NS_LITERAL_STRING_FROM_CSTRING(DEFAULT_BASE_CSP_V2
);
206 nsresult rv
= Preferences::GetString(BASE_CSP_PREF_V3
, mBaseCSP
);
208 mBaseCSP
= NS_LITERAL_STRING_FROM_CSTRING(DEFAULT_BASE_CSP_V3
);
212 if (mExtensionPageCSP
.IsVoid()) {
213 if (mManifestVersion
< 3) {
214 EPS().GetDefaultCSP(mExtensionPageCSP
);
216 EPS().GetDefaultCSPV3(mExtensionPageCSP
);
220 mWebAccessibleResources
.SetCapacity(aInit
.mWebAccessibleResources
.Length());
221 for (const auto& resourceInit
: aInit
.mWebAccessibleResources
) {
222 RefPtr
<WebAccessibleResource
> resource
=
223 new WebAccessibleResource(aGlobal
, resourceInit
, aRv
);
227 mWebAccessibleResources
.AppendElement(std::move(resource
));
230 nsresult rv
= NS_NewURI(getter_AddRefs(mBaseURI
), aInit
.mBaseURL
);
236 bool WebExtensionPolicyCore::SourceMayAccessPath(
237 const URLInfo
& aURI
, const nsACString
& aPath
) const {
238 if (aURI
.Scheme() == nsGkAtoms::moz_extension
&&
239 MozExtensionHostname().Equals(aURI
.Host())) {
240 // An extension can always access it's own paths.
243 // Bug 1786564 Static themes need to allow access to theme resources.
244 if (Type() == nsGkAtoms::theme
) {
245 RefPtr
<WebExtensionPolicyCore
> policyCore
=
246 ExtensionPolicyService::GetCoreByHost(aURI
.Host());
247 return policyCore
!= nullptr;
250 if (ManifestVersion() < 3) {
251 return IsWebAccessiblePath(aPath
);
253 for (const auto& resource
: mWebAccessibleResources
) {
254 if (resource
->SourceMayAccessPath(aURI
, aPath
)) {
261 bool WebExtensionPolicyCore::CanAccessURI(const URLInfo
& aURI
, bool aExplicit
,
262 bool aCheckRestricted
,
263 bool aAllowFilePermission
) const {
264 if (aCheckRestricted
&& WebExtensionPolicy::IsRestrictedURI(aURI
)) {
267 if (aCheckRestricted
&& QuarantinedFromURI(aURI
)) {
270 if (!aAllowFilePermission
&& aURI
.Scheme() == nsGkAtoms::file
) {
274 AutoReadLock
lock(mLock
);
275 return mHostPermissions
&& mHostPermissions
->Matches(aURI
, aExplicit
);
278 bool WebExtensionPolicyCore::QuarantinedFromDoc(const DocInfo
& aDoc
) const {
279 return QuarantinedFromURI(aDoc
.PrincipalURL());
282 bool WebExtensionPolicyCore::QuarantinedFromURI(const URLInfo
& aURI
) const {
283 return !IgnoreQuarantine() && WebExtensionPolicy::IsQuarantinedURI(aURI
);
286 /*****************************************************************************
288 *****************************************************************************/
290 WebExtensionPolicy::WebExtensionPolicy(GlobalObject
& aGlobal
,
291 const WebExtensionInit
& aInit
,
293 : mCore(new WebExtensionPolicyCore(aGlobal
, this, aInit
, aRv
)),
294 mLocalizeCallback(aInit
.mLocalizeCallback
) {
299 MatchPatternOptions options
;
300 options
.mRestrictSchemes
= !HasPermission(nsGkAtoms::mozillaAddons
);
302 // Set host permissions with SetAllowedOrigins to make sure the copy in core
303 // and WebExtensionPolicy stay in sync.
304 RefPtr
<MatchPatternSet
> hostPermissions
=
305 ParseMatches(aGlobal
, aInit
.mAllowedOrigins
, options
,
306 ErrorBehavior::CreateEmptyPattern
, aRv
);
310 SetAllowedOrigins(*hostPermissions
);
312 if (!aInit
.mBackgroundScripts
.IsNull()) {
313 mBackgroundScripts
.SetValue().AppendElements(
314 aInit
.mBackgroundScripts
.Value());
317 mBackgroundTypeModule
= aInit
.mBackgroundTypeModule
;
319 mContentScripts
.SetCapacity(aInit
.mContentScripts
.Length());
320 for (const auto& scriptInit
: aInit
.mContentScripts
) {
321 // The activeTab permission is only for dynamically injected scripts,
322 // it cannot be used for declarative content scripts.
323 if (scriptInit
.mHasActiveTabPermission
) {
324 aRv
.Throw(NS_ERROR_INVALID_ARG
);
328 RefPtr
<WebExtensionContentScript
> contentScript
=
329 new WebExtensionContentScript(aGlobal
, *this, scriptInit
, aRv
);
333 mContentScripts
.AppendElement(std::move(contentScript
));
336 if (aInit
.mReadyPromise
.WasPassed()) {
337 mReadyPromise
= &aInit
.mReadyPromise
.Value();
341 already_AddRefed
<WebExtensionPolicy
> WebExtensionPolicy::Constructor(
342 GlobalObject
& aGlobal
, const WebExtensionInit
& aInit
, ErrorResult
& aRv
) {
343 RefPtr
<WebExtensionPolicy
> policy
=
344 new WebExtensionPolicy(aGlobal
, aInit
, aRv
);
348 return policy
.forget();
352 void WebExtensionPolicy::GetActiveExtensions(
353 dom::GlobalObject
& aGlobal
,
354 nsTArray
<RefPtr
<WebExtensionPolicy
>>& aResults
) {
355 EPS().GetAll(aResults
);
359 already_AddRefed
<WebExtensionPolicy
> WebExtensionPolicy::GetByID(
360 dom::GlobalObject
& aGlobal
, const nsAString
& aID
) {
361 return do_AddRef(EPS().GetByID(aID
));
365 already_AddRefed
<WebExtensionPolicy
> WebExtensionPolicy::GetByHostname(
366 dom::GlobalObject
& aGlobal
, const nsACString
& aHostname
) {
367 return do_AddRef(EPS().GetByHost(aHostname
));
371 already_AddRefed
<WebExtensionPolicy
> WebExtensionPolicy::GetByURI(
372 dom::GlobalObject
& aGlobal
, nsIURI
* aURI
) {
373 return do_AddRef(EPS().GetByURL(aURI
));
376 void WebExtensionPolicy::SetActive(bool aActive
, ErrorResult
& aRv
) {
377 if (aActive
== mActive
) {
381 bool ok
= aActive
? Enable() : Disable();
384 aRv
.Throw(NS_ERROR_UNEXPECTED
);
388 bool WebExtensionPolicy::Enable() {
389 MOZ_ASSERT(!mActive
);
391 if (!EPS().RegisterExtension(*this)) {
395 if (XRE_IsParentProcess()) {
396 // Reserve a BrowsingContextGroup for use by this WebExtensionPolicy.
397 RefPtr
<BrowsingContextGroup
> group
= BrowsingContextGroup::Create();
398 mBrowsingContextGroup
= group
->MakeKeepAlivePtr();
401 Unused
<< Proto()->SetSubstitution(MozExtensionHostname(), BaseURI());
407 bool WebExtensionPolicy::Disable() {
409 MOZ_ASSERT(EPS().GetByID(Id()) == this);
411 if (!EPS().UnregisterExtension(*this)) {
415 if (XRE_IsParentProcess()) {
416 // Clear our BrowsingContextGroup reference. A new instance will be created
417 // when the extension is next activated.
418 mBrowsingContextGroup
= nullptr;
421 Unused
<< Proto()->SetSubstitution(MozExtensionHostname(), nullptr);
427 void WebExtensionPolicy::GetURL(const nsAString
& aPath
, nsAString
& aResult
,
428 ErrorResult
& aRv
) const {
429 auto result
= GetURL(aPath
);
431 aResult
= result
.unwrap();
433 aRv
.Throw(result
.unwrapErr());
437 Result
<nsString
, nsresult
> WebExtensionPolicy::GetURL(
438 const nsAString
& aPath
) const {
439 nsPrintfCString
spec("%s://%s/", kProto
, MozExtensionHostname().get());
441 nsCOMPtr
<nsIURI
> uri
;
442 MOZ_TRY(NS_NewURI(getter_AddRefs(uri
), spec
));
444 MOZ_TRY(uri
->Resolve(NS_ConvertUTF16toUTF8(aPath
), spec
));
446 return NS_ConvertUTF8toUTF16(spec
);
449 void WebExtensionPolicy::SetIgnoreQuarantine(bool aIgnore
) {
450 WebExtensionPolicy_Binding::ClearCachedIgnoreQuarantineValue(this);
451 mCore
->SetIgnoreQuarantine(aIgnore
);
454 void WebExtensionPolicy::RegisterContentScript(
455 WebExtensionContentScript
& script
, ErrorResult
& aRv
) {
456 // Raise an "invalid argument" error if the script is not related to
457 // the expected extension or if it is already registered.
458 if (script
.mExtension
!= this || mContentScripts
.Contains(&script
)) {
459 aRv
.Throw(NS_ERROR_INVALID_ARG
);
463 RefPtr
<WebExtensionContentScript
> newScript
= &script
;
465 if (!mContentScripts
.AppendElement(std::move(newScript
), fallible
)) {
466 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
470 WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this);
473 void WebExtensionPolicy::UnregisterContentScript(
474 const WebExtensionContentScript
& script
, ErrorResult
& aRv
) {
475 if (script
.mExtension
!= this || !mContentScripts
.RemoveElement(&script
)) {
476 aRv
.Throw(NS_ERROR_INVALID_ARG
);
480 WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this);
483 void WebExtensionPolicy::SetAllowedOrigins(MatchPatternSet
& aAllowedOrigins
) {
484 // Make sure to keep the version in `WebExtensionPolicy` (which can be exposed
485 // back to script using AllowedOrigins()), and the version in
486 // `WebExtensionPolicyCore` (which is threadsafe) in sync.
487 AutoWriteLock
lock(mCore
->mLock
);
488 mHostPermissions
= &aAllowedOrigins
;
489 mCore
->mHostPermissions
= aAllowedOrigins
.Core();
492 void WebExtensionPolicy::InjectContentScripts(ErrorResult
& aRv
) {
493 nsresult rv
= EPS().InjectContentScripts(this);
500 bool WebExtensionPolicy::UseRemoteWebExtensions(GlobalObject
& aGlobal
) {
501 return EPS().UseRemoteExtensions();
505 bool WebExtensionPolicy::IsExtensionProcess(GlobalObject
& aGlobal
) {
506 return EPS().IsExtensionProcess();
510 bool WebExtensionPolicy::BackgroundServiceWorkerEnabled(GlobalObject
& aGlobal
) {
511 // When MOZ_WEBEXT_WEBIDL_ENABLED is not set at compile time, extension APIs
512 // are not available to extension service workers. To avoid confusion, the
513 // extensions.backgroundServiceWorkerEnabled.enabled pref is locked to false
514 // in modules/libpref/init/all.js when MOZ_WEBEXT_WEBIDL_ENABLED is not set.
515 return StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup();
519 bool WebExtensionPolicy::QuarantinedDomainsEnabled(GlobalObject
& aGlobal
) {
520 return EPS().GetQuarantinedDomainsEnabled();
524 bool WebExtensionPolicy::IsRestrictedDoc(const DocInfo
& aDoc
) {
525 // With the exception of top-level about:blank documents with null
526 // principals, we never match documents that have non-content principals,
527 // including those with null principals or system principals.
528 if (aDoc
.Principal() && !aDoc
.Principal()->GetIsContentPrincipal()) {
532 return IsRestrictedURI(aDoc
.PrincipalURL());
536 bool WebExtensionPolicy::IsRestrictedURI(const URLInfo
& aURI
) {
537 RefPtr
<AtomSet
> restrictedDomains
=
538 ExtensionPolicyService::RestrictedDomains();
540 if (restrictedDomains
&& restrictedDomains
->Contains(aURI
.HostAtom())) {
544 if (AddonManagerWebAPI::IsValidSite(aURI
.URI())) {
552 bool WebExtensionPolicy::IsQuarantinedDoc(const DocInfo
& aDoc
) {
553 return IsQuarantinedURI(aDoc
.PrincipalURL());
557 bool WebExtensionPolicy::IsQuarantinedURI(const URLInfo
& aURI
) {
558 // Ensure EPS is initialized before asking it about quarantined domains.
561 RefPtr
<AtomSet
> quarantinedDomains
=
562 ExtensionPolicyService::QuarantinedDomains();
564 return quarantinedDomains
&& quarantinedDomains
->Contains(aURI
.HostAtom());
567 nsCString
WebExtensionPolicy::BackgroundPageHTML() const {
570 if (mBackgroundScripts
.IsNull()) {
571 result
.SetIsVoid(true);
575 result
.AppendLiteral(kBackgroundPageHTMLStart
);
577 const char* scriptType
= mBackgroundTypeModule
? kBackgroundScriptTypeModule
578 : kBackgroundScriptTypeDefault
;
580 for (auto& script
: mBackgroundScripts
.Value()) {
582 nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(script
), escaped
);
583 result
.AppendPrintf(kBackgroundPageHTMLScript
, scriptType
, escaped
.get());
586 result
.AppendLiteral(kBackgroundPageHTMLEnd
);
590 void WebExtensionPolicy::Localize(const nsAString
& aInput
,
591 nsString
& aOutput
) const {
592 RefPtr
<WebExtensionLocalizeCallback
> callback(mLocalizeCallback
);
593 callback
->Call(aInput
, aOutput
);
596 JSObject
* WebExtensionPolicy::WrapObject(JSContext
* aCx
,
597 JS::Handle
<JSObject
*> aGivenProto
) {
598 return WebExtensionPolicy_Binding::Wrap(aCx
, this, aGivenProto
);
601 void WebExtensionPolicy::GetContentScripts(
602 nsTArray
<RefPtr
<WebExtensionContentScript
>>& aScripts
) const {
603 aScripts
.AppendElements(mContentScripts
);
606 bool WebExtensionPolicy::PrivateBrowsingAllowed() const {
607 return HasPermission(nsGkAtoms::privateBrowsingAllowedPermission
);
610 bool WebExtensionPolicy::CanAccessContext(nsILoadContext
* aContext
) const {
611 MOZ_ASSERT(aContext
);
612 return PrivateBrowsingAllowed() || !aContext
->UsePrivateBrowsing();
615 bool WebExtensionPolicy::CanAccessWindow(
616 const dom::WindowProxyHolder
& aWindow
) const {
617 if (PrivateBrowsingAllowed()) {
620 // match browsing mode with policy
621 nsIDocShell
* docShell
= aWindow
.get()->GetDocShell();
622 nsCOMPtr
<nsILoadContext
> loadContext
= do_QueryInterface(docShell
);
623 return !(loadContext
&& loadContext
->UsePrivateBrowsing());
626 void WebExtensionPolicy::GetReadyPromise(
627 JSContext
* aCx
, JS::MutableHandle
<JSObject
*> aResult
) const {
629 aResult
.set(mReadyPromise
->PromiseObj());
631 aResult
.set(nullptr);
635 uint64_t WebExtensionPolicy::GetBrowsingContextGroupId() const {
636 MOZ_ASSERT(XRE_IsParentProcess() && mActive
);
637 return mBrowsingContextGroup
? mBrowsingContextGroup
->Id() : 0;
640 uint64_t WebExtensionPolicy::GetBrowsingContextGroupId(ErrorResult
& aRv
) {
641 if (XRE_IsParentProcess() && mActive
) {
642 return GetBrowsingContextGroupId();
644 aRv
.ThrowInvalidAccessError(
645 "browsingContextGroupId only available for active policies in the "
650 WebExtensionPolicy::~WebExtensionPolicy() { mCore
->ClearPolicyWeakRef(); }
652 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WebExtensionPolicy
)
653 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebExtensionPolicy
)
654 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent
)
655 NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContextGroup
)
656 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalizeCallback
)
657 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHostPermissions
)
658 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentScripts
)
659 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
660 AssertIsOnMainThread();
661 tmp
->mCore
->ClearPolicyWeakRef();
662 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
663 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebExtensionPolicy
)
664 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent
)
665 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContextGroup
)
666 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalizeCallback
)
667 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHostPermissions
)
668 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentScripts
)
669 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
671 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionPolicy
)
672 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
673 NS_INTERFACE_MAP_ENTRY(nsISupports
)
676 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionPolicy
)
677 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebExtensionPolicy
)
679 /*****************************************************************************
680 * WebExtensionContentScript / MozDocumentMatcher
681 *****************************************************************************/
684 already_AddRefed
<MozDocumentMatcher
> MozDocumentMatcher::Constructor(
685 GlobalObject
& aGlobal
, const dom::MozDocumentMatcherInit
& aInit
,
687 RefPtr
<MozDocumentMatcher
> matcher
=
688 new MozDocumentMatcher(aGlobal
, aInit
, false, aRv
);
692 return matcher
.forget();
696 already_AddRefed
<WebExtensionContentScript
>
697 WebExtensionContentScript::Constructor(GlobalObject
& aGlobal
,
698 WebExtensionPolicy
& aExtension
,
699 const ContentScriptInit
& aInit
,
701 RefPtr
<WebExtensionContentScript
> script
=
702 new WebExtensionContentScript(aGlobal
, aExtension
, aInit
, aRv
);
706 return script
.forget();
709 MozDocumentMatcher::MozDocumentMatcher(GlobalObject
& aGlobal
,
710 const dom::MozDocumentMatcherInit
& aInit
,
711 bool aRestricted
, ErrorResult
& aRv
)
712 : mHasActiveTabPermission(aInit
.mHasActiveTabPermission
),
713 mRestricted(aRestricted
),
714 mAllFrames(aInit
.mAllFrames
),
715 mCheckPermissions(aInit
.mCheckPermissions
),
716 mFrameID(aInit
.mFrameID
),
717 mMatchAboutBlank(aInit
.mMatchAboutBlank
) {
718 MatchPatternOptions options
;
719 options
.mRestrictSchemes
= mRestricted
;
721 mMatches
= ParseMatches(aGlobal
, aInit
.mMatches
, options
,
722 ErrorBehavior::CreateEmptyPattern
, aRv
);
727 if (!aInit
.mExcludeMatches
.IsNull()) {
729 ParseMatches(aGlobal
, aInit
.mExcludeMatches
.Value(), options
,
730 ErrorBehavior::CreateEmptyPattern
, aRv
);
736 if (!aInit
.mIncludeGlobs
.IsNull()) {
737 if (!ParseGlobs(aGlobal
, aInit
.mIncludeGlobs
.Value(),
738 mIncludeGlobs
.SetValue(), aRv
)) {
743 if (!aInit
.mExcludeGlobs
.IsNull()) {
744 if (!ParseGlobs(aGlobal
, aInit
.mExcludeGlobs
.Value(),
745 mExcludeGlobs
.SetValue(), aRv
)) {
750 if (!aInit
.mOriginAttributesPatterns
.IsNull()) {
751 Sequence
<OriginAttributesPattern
>& arr
=
752 mOriginAttributesPatterns
.SetValue();
753 for (const auto& pattern
: aInit
.mOriginAttributesPatterns
.Value()) {
754 if (!arr
.AppendElement(OriginAttributesPattern(pattern
), fallible
)) {
755 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
762 WebExtensionContentScript::WebExtensionContentScript(
763 GlobalObject
& aGlobal
, WebExtensionPolicy
& aExtension
,
764 const ContentScriptInit
& aInit
, ErrorResult
& aRv
)
765 : MozDocumentMatcher(aGlobal
, aInit
,
766 !aExtension
.HasPermission(nsGkAtoms::mozillaAddons
),
768 mRunAt(aInit
.mRunAt
) {
769 mCssPaths
.Assign(aInit
.mCssPaths
);
770 mJsPaths
.Assign(aInit
.mJsPaths
);
771 mExtension
= &aExtension
;
773 // Origin permissions are optional in mv3, so always check them at runtime.
774 if (mExtension
->ManifestVersion() >= 3) {
775 mCheckPermissions
= true;
779 bool MozDocumentMatcher::Matches(const DocInfo
& aDoc
,
780 bool aIgnorePermissions
) const {
781 if (!mFrameID
.IsNull()) {
782 if (aDoc
.FrameID() != mFrameID
.Value()) {
786 if (!mAllFrames
&& !aDoc
.IsTopLevel()) {
791 // match browsing mode with policy
792 nsCOMPtr
<nsILoadContext
> loadContext
= aDoc
.GetLoadContext();
793 if (loadContext
&& mExtension
&& !mExtension
->CanAccessContext(loadContext
)) {
797 if (loadContext
&& !mOriginAttributesPatterns
.IsNull()) {
798 OriginAttributes docShellAttrs
;
799 loadContext
->GetOriginAttributes(docShellAttrs
);
800 bool patternMatch
= false;
801 for (const auto& pattern
: mOriginAttributesPatterns
.Value()) {
802 if (pattern
.Matches(docShellAttrs
)) {
812 // TODO bug 1411641: we should account for precursorPrincipal if
813 // match_origin_as_fallback is specified (see also bug 1853411).
814 if (!mMatchAboutBlank
&& aDoc
.URL().InheritsPrincipal()) {
818 // Top-level about:blank is a special case. Unlike about:blank frames/windows
819 // opened by web pages, these do not have an origin that could be matched by
820 // a match pattern (they have a null principal instead). To allow extensions
821 // that intend to run scripts "everywhere", consider the document matched if
822 // the match pattern describe a very broad pattern (such as "<all_urls>").
823 if (mMatchAboutBlank
&& aDoc
.IsTopLevel() &&
824 (aDoc
.URL().Spec().EqualsLiteral("about:blank") ||
825 aDoc
.URL().Scheme() == nsGkAtoms::data
) &&
826 aDoc
.Principal() && aDoc
.Principal()->GetIsNullPrincipal()) {
827 if (StaticPrefs::extensions_script_about_blank_without_permission()) {
830 if (mHasActiveTabPermission
) {
833 if (mMatches
->MatchesAllWebUrls() && mIncludeGlobs
.IsNull()) {
834 // When mIncludeGlobs is present, mMatches does not necessarily match
835 // everything (except possibly if include_globs is just ["*"]). So we
836 // only match if mMatches is present without mIncludeGlobs.
839 // Null principal is never going to match, so we may as well return now.
843 if (mRestricted
&& WebExtensionPolicy::IsRestrictedDoc(aDoc
)) {
847 if (mRestricted
&& mExtension
&& mExtension
->QuarantinedFromDoc(aDoc
)) {
851 auto& urlinfo
= aDoc
.PrincipalURL();
852 if (mExtension
&& mExtension
->ManifestVersion() >= 3) {
853 // In MV3, activeTab only allows access to same-origin iframes.
854 if (mHasActiveTabPermission
&& aDoc
.IsSameOriginWithTop() &&
855 MatchPattern::MatchesAllURLs(urlinfo
)) {
859 if (mHasActiveTabPermission
&& aDoc
.ShouldMatchActiveTabPermission() &&
860 MatchPattern::MatchesAllURLs(urlinfo
)) {
865 return MatchesURI(urlinfo
, aIgnorePermissions
);
868 bool MozDocumentMatcher::MatchesURI(const URLInfo
& aURL
,
869 bool aIgnorePermissions
) const {
870 MOZ_ASSERT((!mRestricted
&& !mCheckPermissions
) || mExtension
);
872 if (!mMatches
->Matches(aURL
)) {
876 if (mExcludeMatches
&& mExcludeMatches
->Matches(aURL
)) {
880 if (!mIncludeGlobs
.IsNull() && !mIncludeGlobs
.Value().Matches(aURL
.CSpec())) {
884 if (!mExcludeGlobs
.IsNull() && mExcludeGlobs
.Value().Matches(aURL
.CSpec())) {
888 if (mRestricted
&& WebExtensionPolicy::IsRestrictedURI(aURL
)) {
892 if (mRestricted
&& mExtension
->QuarantinedFromURI(aURL
)) {
896 if (mCheckPermissions
&& !aIgnorePermissions
&&
897 !mExtension
->CanAccessURI(aURL
, false, false, true)) {
904 bool MozDocumentMatcher::MatchesWindowGlobal(WindowGlobalChild
& aWindow
,
905 bool aIgnorePermissions
) const {
906 if (aWindow
.IsClosed() || !aWindow
.IsCurrentGlobal()) {
909 nsGlobalWindowInner
* inner
= aWindow
.GetWindowGlobal();
910 if (!inner
|| !inner
->GetDocShell()) {
913 return Matches(inner
->GetOuterWindow(), aIgnorePermissions
);
916 void MozDocumentMatcher::GetOriginAttributesPatterns(
917 JSContext
* aCx
, JS::MutableHandle
<JS::Value
> aVal
,
918 ErrorResult
& aError
) const {
919 if (!ToJSValue(aCx
, mOriginAttributesPatterns
, aVal
)) {
920 aError
.NoteJSContextException(aCx
);
924 JSObject
* MozDocumentMatcher::WrapObject(JSContext
* aCx
,
925 JS::Handle
<JSObject
*> aGivenProto
) {
926 return MozDocumentMatcher_Binding::Wrap(aCx
, this, aGivenProto
);
929 JSObject
* WebExtensionContentScript::WrapObject(
930 JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
) {
931 return WebExtensionContentScript_Binding::Wrap(aCx
, this, aGivenProto
);
934 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MozDocumentMatcher
, mMatches
,
935 mExcludeMatches
, mExtension
)
937 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MozDocumentMatcher
)
938 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
939 NS_INTERFACE_MAP_ENTRY(nsISupports
)
942 NS_IMPL_CYCLE_COLLECTING_ADDREF(MozDocumentMatcher
)
943 NS_IMPL_CYCLE_COLLECTING_RELEASE(MozDocumentMatcher
)
945 /*****************************************************************************
946 * MozDocumentObserver
947 *****************************************************************************/
950 already_AddRefed
<DocumentObserver
> DocumentObserver::Constructor(
951 GlobalObject
& aGlobal
, dom::MozDocumentCallback
& aCallbacks
) {
952 RefPtr
<DocumentObserver
> matcher
=
953 new DocumentObserver(aGlobal
.GetAsSupports(), aCallbacks
);
954 return matcher
.forget();
957 void DocumentObserver::Observe(
958 const dom::Sequence
<OwningNonNull
<MozDocumentMatcher
>>& matchers
,
960 if (!EPS().RegisterObserver(*this)) {
961 aRv
.Throw(NS_ERROR_FAILURE
);
965 for (auto& matcher
: matchers
) {
966 if (!mMatchers
.AppendElement(matcher
, fallible
)) {
967 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
973 void DocumentObserver::Disconnect() {
974 Unused
<< EPS().UnregisterObserver(*this);
977 void DocumentObserver::NotifyMatch(MozDocumentMatcher
& aMatcher
,
978 nsPIDOMWindowOuter
* aWindow
) {
979 IgnoredErrorResult rv
;
980 mCallbacks
->OnNewDocument(
981 aMatcher
, WindowProxyHolder(aWindow
->GetBrowsingContext()), rv
);
984 void DocumentObserver::NotifyMatch(MozDocumentMatcher
& aMatcher
,
985 nsILoadInfo
* aLoadInfo
) {
986 IgnoredErrorResult rv
;
987 mCallbacks
->OnPreloadDocument(aMatcher
, aLoadInfo
, rv
);
990 JSObject
* DocumentObserver::WrapObject(JSContext
* aCx
,
991 JS::Handle
<JSObject
*> aGivenProto
) {
992 return MozDocumentObserver_Binding::Wrap(aCx
, this, aGivenProto
);
995 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DocumentObserver
, mCallbacks
, mMatchers
,
998 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentObserver
)
999 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
1000 NS_INTERFACE_MAP_ENTRY(nsISupports
)
1001 NS_INTERFACE_MAP_END
1003 NS_IMPL_CYCLE_COLLECTING_ADDREF(DocumentObserver
)
1004 NS_IMPL_CYCLE_COLLECTING_RELEASE(DocumentObserver
)
1006 /*****************************************************************************
1008 *****************************************************************************/
1010 DocInfo::DocInfo(const URLInfo
& aURL
, nsILoadInfo
* aLoadInfo
)
1011 : mURL(aURL
), mObj(AsVariant(aLoadInfo
)) {}
1013 DocInfo::DocInfo(nsPIDOMWindowOuter
* aWindow
)
1014 : mURL(aWindow
->GetDocumentURI()), mObj(AsVariant(aWindow
)) {}
1016 bool DocInfo::IsTopLevel() const {
1017 if (mIsTopLevel
.isNothing()) {
1019 bool operator()(Window aWin
) {
1020 return aWin
->GetBrowsingContext()->IsTop();
1022 bool operator()(LoadInfo aLoadInfo
) {
1023 return aLoadInfo
->GetIsTopLevelLoad();
1026 mIsTopLevel
.emplace(mObj
.match(Matcher()));
1028 return mIsTopLevel
.ref();
1031 bool WindowShouldMatchActiveTab(nsPIDOMWindowOuter
* aWin
) {
1032 WindowContext
* wc
= aWin
->GetCurrentInnerWindow()->GetWindowContext();
1033 if (wc
&& wc
->SameOriginWithTop()) {
1034 // If the frame is same-origin to top, accept the match regardless of
1035 // whether the frame was populated dynamically.
1038 for (; wc
; wc
= wc
->GetParentWindowContext()) {
1039 BrowsingContext
* bc
= wc
->GetBrowsingContext();
1040 if (bc
->IsTopContent()) {
1044 if (bc
->CreatedDynamically() || !wc
->GetIsOriginalFrameSource()) {
1048 MOZ_ASSERT_UNREACHABLE("Should reach top content before end of loop");
1052 bool DocInfo::ShouldMatchActiveTabPermission() const {
1054 bool operator()(Window aWin
) { return WindowShouldMatchActiveTab(aWin
); }
1055 bool operator()(LoadInfo aLoadInfo
) { return false; }
1057 return mObj
.match(Matcher());
1060 bool DocInfo::IsSameOriginWithTop() const {
1062 bool operator()(Window aWin
) {
1063 WindowContext
* wc
= aWin
->GetCurrentInnerWindow()->GetWindowContext();
1064 return wc
&& wc
->SameOriginWithTop();
1066 bool operator()(LoadInfo aLoadInfo
) { return false; }
1068 return mObj
.match(Matcher());
1071 uint64_t DocInfo::FrameID() const {
1072 if (mFrameID
.isNothing()) {
1074 mFrameID
.emplace(0);
1077 uint64_t operator()(Window aWin
) {
1078 return aWin
->GetBrowsingContext()->Id();
1080 uint64_t operator()(LoadInfo aLoadInfo
) {
1081 return aLoadInfo
->GetBrowsingContextID();
1084 mFrameID
.emplace(mObj
.match(Matcher()));
1087 return mFrameID
.ref();
1090 nsIPrincipal
* DocInfo::Principal() const {
1091 if (mPrincipal
.isNothing()) {
1093 explicit Matcher(const DocInfo
& aThis
) : mThis(aThis
) {}
1094 const DocInfo
& mThis
;
1096 nsIPrincipal
* operator()(Window aWin
) {
1097 RefPtr
<Document
> doc
= aWin
->GetDoc();
1098 return doc
->NodePrincipal();
1100 nsIPrincipal
* operator()(LoadInfo aLoadInfo
) {
1101 if (!(mThis
.URL().InheritsPrincipal() ||
1102 aLoadInfo
->GetForceInheritPrincipal())) {
1105 if (auto principal
= aLoadInfo
->PrincipalToInherit()) {
1108 return aLoadInfo
->TriggeringPrincipal();
1111 mPrincipal
.emplace(mObj
.match(Matcher(*this)));
1113 return mPrincipal
.ref();
1116 const URLInfo
& DocInfo::PrincipalURL() const {
1117 if (!(Principal() && Principal()->GetIsContentPrincipal())) {
1121 if (mPrincipalURL
.isNothing()) {
1122 nsIPrincipal
* prin
= Principal();
1123 auto* basePrin
= BasePrincipal::Cast(prin
);
1124 nsCOMPtr
<nsIURI
> uri
;
1125 if (NS_SUCCEEDED(basePrin
->GetURI(getter_AddRefs(uri
)))) {
1126 MOZ_DIAGNOSTIC_ASSERT(uri
);
1127 mPrincipalURL
.emplace(uri
);
1129 mPrincipalURL
.emplace(URL());
1133 return mPrincipalURL
.ref();
1136 } // namespace extensions
1137 } // namespace mozilla