Bug 1870926 [wpt PR 43734] - Remove experimental ::details-summary pseudo-element...
[gecko.git] / toolkit / components / extensions / WebExtensionPolicy.cpp
blob1b999ab9a3d273a0fa9764497db34f2fcb97a156
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"
19 #include "nsEscape.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"
27 namespace mozilla {
28 namespace extensions {
30 using namespace dom;
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[] =
39 "<!DOCTYPE html>\n\
40 <html>\n\
41 <head><meta charset=\"utf-8\"></head>\n\
42 <body>";
44 static const char kBackgroundPageHTMLScript[] =
45 "\n\
46 <script type=\"%s\" src=\"%s\"></script>";
48 static const char kBackgroundPageHTMLEnd[] =
49 "\n\
50 </body>\n\
51 </html>";
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' " \
57 "'unsafe-inline';"
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);
83 return 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());
92 } else {
93 RefPtr<MatchGlobCore> glob =
94 new MatchGlobCore(elem.GetAsUTF8String(), true, false, aRv);
95 if (aRv.Failed()) {
96 return false;
98 aResult.AppendElement(glob);
101 return true;
104 enum class ErrorBehavior {
105 CreateEmptyPattern,
106 Fail,
109 already_AddRefed<MatchPatternSet> ParseMatches(
110 GlobalObject& aGlobal,
111 const OwningMatchPatternSetOrStringSequence& aMatches,
112 const MatchPatternOptions& aOptions, ErrorBehavior aErrorBehavior,
113 ErrorResult& aRv) {
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);
123 return nullptr;
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,
145 ErrorResult& aRv) {
146 ParseGlobs(aGlobal, aInit.mResources, mWebAccessiblePaths, aRv);
147 if (aRv.Failed()) {
148 return;
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) {
168 return false;
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,
183 ErrorResult& aRv)
184 : mPolicy(aPolicy),
185 mId(NS_AtomizeMainThread(aInit.mId)),
186 mName(aInit.mName),
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);
202 if (NS_FAILED(rv)) {
203 mBaseCSP = NS_LITERAL_STRING_FROM_CSTRING(DEFAULT_BASE_CSP_V2);
205 } else {
206 nsresult rv = Preferences::GetString(BASE_CSP_PREF_V3, mBaseCSP);
207 if (NS_FAILED(rv)) {
208 mBaseCSP = NS_LITERAL_STRING_FROM_CSTRING(DEFAULT_BASE_CSP_V3);
212 if (mExtensionPageCSP.IsVoid()) {
213 if (mManifestVersion < 3) {
214 EPS().GetDefaultCSP(mExtensionPageCSP);
215 } else {
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);
224 if (aRv.Failed()) {
225 return;
227 mWebAccessibleResources.AppendElement(std::move(resource));
230 nsresult rv = NS_NewURI(getter_AddRefs(mBaseURI), aInit.mBaseURL);
231 if (NS_FAILED(rv)) {
232 aRv.Throw(rv);
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.
241 return true;
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)) {
255 return true;
258 return false;
261 bool WebExtensionPolicyCore::CanAccessURI(const URLInfo& aURI, bool aExplicit,
262 bool aCheckRestricted,
263 bool aAllowFilePermission) const {
264 if (aCheckRestricted && WebExtensionPolicy::IsRestrictedURI(aURI)) {
265 return false;
267 if (aCheckRestricted && QuarantinedFromURI(aURI)) {
268 return false;
270 if (!aAllowFilePermission && aURI.Scheme() == nsGkAtoms::file) {
271 return false;
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 /*****************************************************************************
287 * WebExtensionPolicy
288 *****************************************************************************/
290 WebExtensionPolicy::WebExtensionPolicy(GlobalObject& aGlobal,
291 const WebExtensionInit& aInit,
292 ErrorResult& aRv)
293 : mCore(new WebExtensionPolicyCore(aGlobal, this, aInit, aRv)),
294 mLocalizeCallback(aInit.mLocalizeCallback) {
295 if (aRv.Failed()) {
296 return;
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);
307 if (aRv.Failed()) {
308 return;
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);
325 return;
328 RefPtr<WebExtensionContentScript> contentScript =
329 new WebExtensionContentScript(aGlobal, *this, scriptInit, aRv);
330 if (aRv.Failed()) {
331 return;
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);
345 if (aRv.Failed()) {
346 return nullptr;
348 return policy.forget();
351 /* static */
352 void WebExtensionPolicy::GetActiveExtensions(
353 dom::GlobalObject& aGlobal,
354 nsTArray<RefPtr<WebExtensionPolicy>>& aResults) {
355 EPS().GetAll(aResults);
358 /* static */
359 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByID(
360 dom::GlobalObject& aGlobal, const nsAString& aID) {
361 return do_AddRef(EPS().GetByID(aID));
364 /* static */
365 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByHostname(
366 dom::GlobalObject& aGlobal, const nsACString& aHostname) {
367 return do_AddRef(EPS().GetByHost(aHostname));
370 /* static */
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) {
378 return;
381 bool ok = aActive ? Enable() : Disable();
383 if (!ok) {
384 aRv.Throw(NS_ERROR_UNEXPECTED);
388 bool WebExtensionPolicy::Enable() {
389 MOZ_ASSERT(!mActive);
391 if (!EPS().RegisterExtension(*this)) {
392 return false;
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());
403 mActive = true;
404 return true;
407 bool WebExtensionPolicy::Disable() {
408 MOZ_ASSERT(mActive);
409 MOZ_ASSERT(EPS().GetByID(Id()) == this);
411 if (!EPS().UnregisterExtension(*this)) {
412 return false;
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);
423 mActive = false;
424 return true;
427 void WebExtensionPolicy::GetURL(const nsAString& aPath, nsAString& aResult,
428 ErrorResult& aRv) const {
429 auto result = GetURL(aPath);
430 if (result.isOk()) {
431 aResult = result.unwrap();
432 } else {
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);
460 return;
463 RefPtr<WebExtensionContentScript> newScript = &script;
465 if (!mContentScripts.AppendElement(std::move(newScript), fallible)) {
466 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
467 return;
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);
477 return;
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);
494 if (NS_FAILED(rv)) {
495 aRv.Throw(rv);
499 /* static */
500 bool WebExtensionPolicy::UseRemoteWebExtensions(GlobalObject& aGlobal) {
501 return EPS().UseRemoteExtensions();
504 /* static */
505 bool WebExtensionPolicy::IsExtensionProcess(GlobalObject& aGlobal) {
506 return EPS().IsExtensionProcess();
509 /* static */
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();
518 /* static */
519 bool WebExtensionPolicy::QuarantinedDomainsEnabled(GlobalObject& aGlobal) {
520 return EPS().GetQuarantinedDomainsEnabled();
523 /* static */
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()) {
529 return true;
532 return IsRestrictedURI(aDoc.PrincipalURL());
535 /* static */
536 bool WebExtensionPolicy::IsRestrictedURI(const URLInfo& aURI) {
537 RefPtr<AtomSet> restrictedDomains =
538 ExtensionPolicyService::RestrictedDomains();
540 if (restrictedDomains && restrictedDomains->Contains(aURI.HostAtom())) {
541 return true;
544 if (AddonManagerWebAPI::IsValidSite(aURI.URI())) {
545 return true;
548 return false;
551 /* static */
552 bool WebExtensionPolicy::IsQuarantinedDoc(const DocInfo& aDoc) {
553 return IsQuarantinedURI(aDoc.PrincipalURL());
556 /* static */
557 bool WebExtensionPolicy::IsQuarantinedURI(const URLInfo& aURI) {
558 // Ensure EPS is initialized before asking it about quarantined domains.
559 Unused << EPS();
561 RefPtr<AtomSet> quarantinedDomains =
562 ExtensionPolicyService::QuarantinedDomains();
564 return quarantinedDomains && quarantinedDomains->Contains(aURI.HostAtom());
567 nsCString WebExtensionPolicy::BackgroundPageHTML() const {
568 nsCString result;
570 if (mBackgroundScripts.IsNull()) {
571 result.SetIsVoid(true);
572 return result;
575 result.AppendLiteral(kBackgroundPageHTMLStart);
577 const char* scriptType = mBackgroundTypeModule ? kBackgroundScriptTypeModule
578 : kBackgroundScriptTypeDefault;
580 for (auto& script : mBackgroundScripts.Value()) {
581 nsCString escaped;
582 nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(script), escaped);
583 result.AppendPrintf(kBackgroundPageHTMLScript, scriptType, escaped.get());
586 result.AppendLiteral(kBackgroundPageHTMLEnd);
587 return result;
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()) {
618 return true;
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 {
628 if (mReadyPromise) {
629 aResult.set(mReadyPromise->PromiseObj());
630 } else {
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 "
646 "parent process");
647 return 0;
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)
674 NS_INTERFACE_MAP_END
676 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionPolicy)
677 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebExtensionPolicy)
679 /*****************************************************************************
680 * WebExtensionContentScript / MozDocumentMatcher
681 *****************************************************************************/
683 /* static */
684 already_AddRefed<MozDocumentMatcher> MozDocumentMatcher::Constructor(
685 GlobalObject& aGlobal, const dom::MozDocumentMatcherInit& aInit,
686 ErrorResult& aRv) {
687 RefPtr<MozDocumentMatcher> matcher =
688 new MozDocumentMatcher(aGlobal, aInit, false, aRv);
689 if (aRv.Failed()) {
690 return nullptr;
692 return matcher.forget();
695 /* static */
696 already_AddRefed<WebExtensionContentScript>
697 WebExtensionContentScript::Constructor(GlobalObject& aGlobal,
698 WebExtensionPolicy& aExtension,
699 const ContentScriptInit& aInit,
700 ErrorResult& aRv) {
701 RefPtr<WebExtensionContentScript> script =
702 new WebExtensionContentScript(aGlobal, aExtension, aInit, aRv);
703 if (aRv.Failed()) {
704 return nullptr;
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);
723 if (aRv.Failed()) {
724 return;
727 if (!aInit.mExcludeMatches.IsNull()) {
728 mExcludeMatches =
729 ParseMatches(aGlobal, aInit.mExcludeMatches.Value(), options,
730 ErrorBehavior::CreateEmptyPattern, aRv);
731 if (aRv.Failed()) {
732 return;
736 if (!aInit.mIncludeGlobs.IsNull()) {
737 if (!ParseGlobs(aGlobal, aInit.mIncludeGlobs.Value(),
738 mIncludeGlobs.SetValue(), aRv)) {
739 return;
743 if (!aInit.mExcludeGlobs.IsNull()) {
744 if (!ParseGlobs(aGlobal, aInit.mExcludeGlobs.Value(),
745 mExcludeGlobs.SetValue(), aRv)) {
746 return;
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);
756 return;
762 WebExtensionContentScript::WebExtensionContentScript(
763 GlobalObject& aGlobal, WebExtensionPolicy& aExtension,
764 const ContentScriptInit& aInit, ErrorResult& aRv)
765 : MozDocumentMatcher(aGlobal, aInit,
766 !aExtension.HasPermission(nsGkAtoms::mozillaAddons),
767 aRv),
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()) {
783 return false;
785 } else {
786 if (!mAllFrames && !aDoc.IsTopLevel()) {
787 return false;
791 // match browsing mode with policy
792 nsCOMPtr<nsILoadContext> loadContext = aDoc.GetLoadContext();
793 if (loadContext && mExtension && !mExtension->CanAccessContext(loadContext)) {
794 return false;
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)) {
803 patternMatch = true;
804 break;
807 if (!patternMatch) {
808 return false;
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()) {
815 return false;
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()) {
828 return true;
830 if (mHasActiveTabPermission) {
831 return true;
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.
837 return true;
839 // Null principal is never going to match, so we may as well return now.
840 return false;
843 if (mRestricted && WebExtensionPolicy::IsRestrictedDoc(aDoc)) {
844 return false;
847 if (mRestricted && mExtension && mExtension->QuarantinedFromDoc(aDoc)) {
848 return false;
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)) {
856 return true;
858 } else {
859 if (mHasActiveTabPermission && aDoc.ShouldMatchActiveTabPermission() &&
860 MatchPattern::MatchesAllURLs(urlinfo)) {
861 return true;
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)) {
873 return false;
876 if (mExcludeMatches && mExcludeMatches->Matches(aURL)) {
877 return false;
880 if (!mIncludeGlobs.IsNull() && !mIncludeGlobs.Value().Matches(aURL.CSpec())) {
881 return false;
884 if (!mExcludeGlobs.IsNull() && mExcludeGlobs.Value().Matches(aURL.CSpec())) {
885 return false;
888 if (mRestricted && WebExtensionPolicy::IsRestrictedURI(aURL)) {
889 return false;
892 if (mRestricted && mExtension->QuarantinedFromURI(aURL)) {
893 return false;
896 if (mCheckPermissions && !aIgnorePermissions &&
897 !mExtension->CanAccessURI(aURL, false, false, true)) {
898 return false;
901 return true;
904 bool MozDocumentMatcher::MatchesWindowGlobal(WindowGlobalChild& aWindow,
905 bool aIgnorePermissions) const {
906 if (aWindow.IsClosed() || !aWindow.IsCurrentGlobal()) {
907 return false;
909 nsGlobalWindowInner* inner = aWindow.GetWindowGlobal();
910 if (!inner || !inner->GetDocShell()) {
911 return false;
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)
940 NS_INTERFACE_MAP_END
942 NS_IMPL_CYCLE_COLLECTING_ADDREF(MozDocumentMatcher)
943 NS_IMPL_CYCLE_COLLECTING_RELEASE(MozDocumentMatcher)
945 /*****************************************************************************
946 * MozDocumentObserver
947 *****************************************************************************/
949 /* static */
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,
959 ErrorResult& aRv) {
960 if (!EPS().RegisterObserver(*this)) {
961 aRv.Throw(NS_ERROR_FAILURE);
962 return;
964 mMatchers.Clear();
965 for (auto& matcher : matchers) {
966 if (!mMatchers.AppendElement(matcher, fallible)) {
967 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
968 return;
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,
996 mParent)
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 /*****************************************************************************
1007 * DocInfo
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()) {
1018 struct Matcher {
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.
1036 return true;
1038 for (; wc; wc = wc->GetParentWindowContext()) {
1039 BrowsingContext* bc = wc->GetBrowsingContext();
1040 if (bc->IsTopContent()) {
1041 return true;
1044 if (bc->CreatedDynamically() || !wc->GetIsOriginalFrameSource()) {
1045 return false;
1048 MOZ_ASSERT_UNREACHABLE("Should reach top content before end of loop");
1049 return false;
1052 bool DocInfo::ShouldMatchActiveTabPermission() const {
1053 struct Matcher {
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 {
1061 struct Matcher {
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()) {
1073 if (IsTopLevel()) {
1074 mFrameID.emplace(0);
1075 } else {
1076 struct Matcher {
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()) {
1092 struct Matcher {
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())) {
1103 return nullptr;
1105 if (auto principal = aLoadInfo->PrincipalToInherit()) {
1106 return principal;
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())) {
1118 return URL();
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);
1128 } else {
1129 mPrincipalURL.emplace(URL());
1133 return mPrincipalURL.ref();
1136 } // namespace extensions
1137 } // namespace mozilla