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/. */
5
6 #include "mozilla/ExtensionPolicyService.h"
7 #include "mozilla/extensions/DocumentObserver.h"
8 #include "mozilla/extensions/WebExtensionContentScript.h"
9 #include "mozilla/extensions/WebExtensionPolicy.h"
10
11 #include "mozilla/AddonManagerWebAPI.h"
12 #include "mozilla/dom/WindowGlobalChild.h"
13 #include "mozilla/ResultExtensions.h"
14 #include "mozilla/StaticPrefs_extensions.h"
15 #include "nsContentUtils.h"
16 #include "nsEscape.h"
17 #include "nsIObserver.h"
18 #include "nsISubstitutingProtocolHandler.h"
19 #include "nsNetUtil.h"
20 #include "nsPrintfCString.h"
21
22 namespace mozilla {
23 namespace extensions {
24
25 using namespace dom;
26
27 static const char kProto[] = "moz-extension";
28
29 static const char kBackgroundPageHTMLStart[] =
30 "<!DOCTYPE html>\n\
31 <html>\n\
32 <head><meta charset=\"utf-8\"></head>\n\
33 <body>";
34
35 static const char kBackgroundPageHTMLScript[] =
36 "\n\
37 <script type=\"text/javascript\" src=\"%s\"></script>";
38
39 static const char kBackgroundPageHTMLEnd[] =
40 "\n\
41 </body>\n\
42 </html>";
43
44 #define BASE_CSP_PREF_V2 "extensions.webextensions.base-content-security-policy"
45 #define DEFAULT_BASE_CSP_V2 \
46 "script-src 'self' https://* moz-extension: blob: filesystem: " \
47 "'unsafe-eval' 'unsafe-inline'; " \
48 "object-src 'self' https://* moz-extension: blob: filesystem:;"
49
50 #define BASE_CSP_PREF_V3 \
51 "extensions.webextensions.base-content-security-policy.v3"
52 #define DEFAULT_BASE_CSP_V3 \
53 "script-src 'self'; object-src 'self'; " \
54 "style-src 'self'; worker-src 'self';"
55
56 static const char kRestrictedDomainPref[] =
57 "extensions.webextensions.restrictedDomains";
58
EPS()59 static inline ExtensionPolicyService& EPS() {
60 return ExtensionPolicyService::GetSingleton();
61 }
62
Proto()63 static nsISubstitutingProtocolHandler* Proto() {
64 static nsCOMPtr<nsISubstitutingProtocolHandler> sHandler;
65
66 if (MOZ_UNLIKELY(!sHandler)) {
67 nsCOMPtr<nsIIOService> ios = do_GetIOService();
68 MOZ_RELEASE_ASSERT(ios);
69
70 nsCOMPtr<nsIProtocolHandler> handler;
71 ios->GetProtocolHandler(kProto, getter_AddRefs(handler));
72
73 sHandler = do_QueryInterface(handler);
74 MOZ_RELEASE_ASSERT(sHandler);
75
76 ClearOnShutdown(&sHandler);
77 }
78
79 return sHandler;
80 }
81
ParseGlobs(GlobalObject & aGlobal,Sequence<OwningMatchGlobOrString> aGlobs,nsTArray<RefPtr<MatchGlob>> & aResult,ErrorResult & aRv)82 bool ParseGlobs(GlobalObject& aGlobal, Sequence<OwningMatchGlobOrString> aGlobs,
83 nsTArray<RefPtr<MatchGlob>>& aResult, ErrorResult& aRv) {
84 for (auto& elem : aGlobs) {
85 if (elem.IsMatchGlob()) {
86 aResult.AppendElement(elem.GetAsMatchGlob());
87 } else {
88 RefPtr<MatchGlob> glob =
89 MatchGlob::Constructor(aGlobal, elem.GetAsString(), true, aRv);
90 if (aRv.Failed()) {
91 return false;
92 }
93 aResult.AppendElement(glob);
94 }
95 }
96 return true;
97 }
98
99 enum class ErrorBehavior {
100 CreateEmptyPattern,
101 Fail,
102 };
103
ParseMatches(GlobalObject & aGlobal,const OwningMatchPatternSetOrStringSequence & aMatches,const MatchPatternOptions & aOptions,ErrorBehavior aErrorBehavior,ErrorResult & aRv)104 already_AddRefed<MatchPatternSet> ParseMatches(
105 GlobalObject& aGlobal,
106 const OwningMatchPatternSetOrStringSequence& aMatches,
107 const MatchPatternOptions& aOptions, ErrorBehavior aErrorBehavior,
108 ErrorResult& aRv) {
109 if (aMatches.IsMatchPatternSet()) {
110 return do_AddRef(aMatches.GetAsMatchPatternSet().get());
111 }
112
113 const auto& strings = aMatches.GetAsStringSequence();
114
115 nsTArray<OwningStringOrMatchPattern> patterns;
116 if (!patterns.SetCapacity(strings.Length(), fallible)) {
117 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
118 return nullptr;
119 }
120
121 for (auto& string : strings) {
122 OwningStringOrMatchPattern elt;
123 elt.SetAsString() = string;
124 patterns.AppendElement(elt);
125 }
126
127 RefPtr<MatchPatternSet> result =
128 MatchPatternSet::Constructor(aGlobal, patterns, aOptions, aRv);
129
130 if (aRv.Failed() && aErrorBehavior == ErrorBehavior::CreateEmptyPattern) {
131 aRv.SuppressException();
132 result = MatchPatternSet::Constructor(aGlobal, {}, aOptions, aRv);
133 }
134
135 return result.forget();
136 }
137
WebAccessibleResource(GlobalObject & aGlobal,const WebAccessibleResourceInit & aInit,ErrorResult & aRv)138 WebAccessibleResource::WebAccessibleResource(
139 GlobalObject& aGlobal, const WebAccessibleResourceInit& aInit,
140 ErrorResult& aRv) {
141 ParseGlobs(aGlobal, aInit.mResources, mWebAccessiblePaths, aRv);
142 if (aRv.Failed()) {
143 return;
144 }
145
146 if (aInit.mMatches.WasPassed()) {
147 MatchPatternOptions options;
148 options.mRestrictSchemes = true;
149 mMatches = ParseMatches(aGlobal, aInit.mMatches.Value(), options,
150 ErrorBehavior::CreateEmptyPattern, aRv);
151 }
152 }
153
154 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAccessibleResource)
NS_INTERFACE_MAP_ENTRY(nsISupports)155 NS_INTERFACE_MAP_ENTRY(nsISupports)
156 NS_INTERFACE_MAP_END
157
158 NS_IMPL_CYCLE_COLLECTION(WebAccessibleResource)
159 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAccessibleResource)
160 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAccessibleResource)
161
162 /*****************************************************************************
163 * WebExtensionPolicy
164 *****************************************************************************/
165
166 WebExtensionPolicy::WebExtensionPolicy(GlobalObject& aGlobal,
167 const WebExtensionInit& aInit,
168 ErrorResult& aRv)
169 : mId(NS_AtomizeMainThread(aInit.mId)),
170 mHostname(aInit.mMozExtensionHostname),
171 mName(aInit.mName),
172 mManifestVersion(aInit.mManifestVersion),
173 mExtensionPageCSP(aInit.mExtensionPageCSP),
174 mLocalizeCallback(aInit.mLocalizeCallback),
175 mIsPrivileged(aInit.mIsPrivileged),
176 mPermissions(new AtomSet(aInit.mPermissions)) {
177 MatchPatternOptions options;
178 options.mRestrictSchemes = !HasPermission(nsGkAtoms::mozillaAddons);
179
180 mHostPermissions = ParseMatches(aGlobal, aInit.mAllowedOrigins, options,
181 ErrorBehavior::CreateEmptyPattern, aRv);
182 if (aRv.Failed()) {
183 return;
184 }
185
186 if (!aInit.mBackgroundScripts.IsNull()) {
187 mBackgroundScripts.SetValue().AppendElements(
188 aInit.mBackgroundScripts.Value());
189 }
190
191 if (!aInit.mBackgroundWorkerScript.IsEmpty()) {
192 mBackgroundWorkerScript.Assign(aInit.mBackgroundWorkerScript);
193 }
194
195 InitializeBaseCSP();
196
197 if (mExtensionPageCSP.IsVoid()) {
198 EPS().GetDefaultCSP(mExtensionPageCSP);
199 }
200
201 mWebAccessibleResources.SetCapacity(aInit.mWebAccessibleResources.Length());
202 for (const auto& resourceInit : aInit.mWebAccessibleResources) {
203 RefPtr<WebAccessibleResource> resource =
204 new WebAccessibleResource(aGlobal, resourceInit, aRv);
205 if (aRv.Failed()) {
206 return;
207 }
208 mWebAccessibleResources.AppendElement(std::move(resource));
209 }
210
211 mContentScripts.SetCapacity(aInit.mContentScripts.Length());
212 for (const auto& scriptInit : aInit.mContentScripts) {
213 // The activeTab permission is only for dynamically injected scripts,
214 // it cannot be used for declarative content scripts.
215 if (scriptInit.mHasActiveTabPermission) {
216 aRv.Throw(NS_ERROR_INVALID_ARG);
217 return;
218 }
219
220 RefPtr<WebExtensionContentScript> contentScript =
221 new WebExtensionContentScript(aGlobal, *this, scriptInit, aRv);
222 if (aRv.Failed()) {
223 return;
224 }
225 mContentScripts.AppendElement(std::move(contentScript));
226 }
227
228 if (aInit.mReadyPromise.WasPassed()) {
229 mReadyPromise = &aInit.mReadyPromise.Value();
230 }
231
232 nsresult rv = NS_NewURI(getter_AddRefs(mBaseURI), aInit.mBaseURL);
233 if (NS_FAILED(rv)) {
234 aRv.Throw(rv);
235 }
236 }
237
Constructor(GlobalObject & aGlobal,const WebExtensionInit & aInit,ErrorResult & aRv)238 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::Constructor(
239 GlobalObject& aGlobal, const WebExtensionInit& aInit, ErrorResult& aRv) {
240 RefPtr<WebExtensionPolicy> policy =
241 new WebExtensionPolicy(aGlobal, aInit, aRv);
242 if (aRv.Failed()) {
243 return nullptr;
244 }
245 return policy.forget();
246 }
247
InitializeBaseCSP()248 void WebExtensionPolicy::InitializeBaseCSP() {
249 if (mManifestVersion < 3) {
250 nsresult rv = Preferences::GetString(BASE_CSP_PREF_V2, mBaseCSP);
251 if (NS_FAILED(rv)) {
252 mBaseCSP.AssignLiteral(DEFAULT_BASE_CSP_V2);
253 }
254 return;
255 }
256 // Version 3 or higher.
257 nsresult rv = Preferences::GetString(BASE_CSP_PREF_V3, mBaseCSP);
258 if (NS_FAILED(rv)) {
259 mBaseCSP.AssignLiteral(DEFAULT_BASE_CSP_V3);
260 }
261 }
262
263 /* static */
GetActiveExtensions(dom::GlobalObject & aGlobal,nsTArray<RefPtr<WebExtensionPolicy>> & aResults)264 void WebExtensionPolicy::GetActiveExtensions(
265 dom::GlobalObject& aGlobal,
266 nsTArray<RefPtr<WebExtensionPolicy>>& aResults) {
267 EPS().GetAll(aResults);
268 }
269
270 /* static */
GetByID(dom::GlobalObject & aGlobal,const nsAString & aID)271 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByID(
272 dom::GlobalObject& aGlobal, const nsAString& aID) {
273 return do_AddRef(EPS().GetByID(aID));
274 }
275
276 /* static */
GetByHostname(dom::GlobalObject & aGlobal,const nsACString & aHostname)277 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByHostname(
278 dom::GlobalObject& aGlobal, const nsACString& aHostname) {
279 return do_AddRef(EPS().GetByHost(aHostname));
280 }
281
282 /* static */
GetByURI(dom::GlobalObject & aGlobal,nsIURI * aURI)283 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByURI(
284 dom::GlobalObject& aGlobal, nsIURI* aURI) {
285 return do_AddRef(EPS().GetByURL(aURI));
286 }
287
SetActive(bool aActive,ErrorResult & aRv)288 void WebExtensionPolicy::SetActive(bool aActive, ErrorResult& aRv) {
289 if (aActive == mActive) {
290 return;
291 }
292
293 bool ok = aActive ? Enable() : Disable();
294
295 if (!ok) {
296 aRv.Throw(NS_ERROR_UNEXPECTED);
297 }
298 }
299
Enable()300 bool WebExtensionPolicy::Enable() {
301 MOZ_ASSERT(!mActive);
302
303 if (!EPS().RegisterExtension(*this)) {
304 return false;
305 }
306
307 if (XRE_IsParentProcess()) {
308 // Reserve a BrowsingContextGroup for use by this WebExtensionPolicy.
309 RefPtr<BrowsingContextGroup> group = BrowsingContextGroup::Create();
310 mBrowsingContextGroup = group->MakeKeepAlivePtr();
311 }
312
313 Unused << Proto()->SetSubstitution(MozExtensionHostname(), mBaseURI);
314
315 mActive = true;
316 return true;
317 }
318
Disable()319 bool WebExtensionPolicy::Disable() {
320 MOZ_ASSERT(mActive);
321 MOZ_ASSERT(EPS().GetByID(Id()) == this);
322
323 if (!EPS().UnregisterExtension(*this)) {
324 return false;
325 }
326
327 if (XRE_IsParentProcess()) {
328 // Clear our BrowsingContextGroup reference. A new instance will be created
329 // when the extension is next activated.
330 mBrowsingContextGroup = nullptr;
331 }
332
333 Unused << Proto()->SetSubstitution(MozExtensionHostname(), nullptr);
334
335 mActive = false;
336 return true;
337 }
338
GetURL(const nsAString & aPath,nsAString & aResult,ErrorResult & aRv) const339 void WebExtensionPolicy::GetURL(const nsAString& aPath, nsAString& aResult,
340 ErrorResult& aRv) const {
341 auto result = GetURL(aPath);
342 if (result.isOk()) {
343 aResult = result.unwrap();
344 } else {
345 aRv.Throw(result.unwrapErr());
346 }
347 }
348
GetURL(const nsAString & aPath) const349 Result<nsString, nsresult> WebExtensionPolicy::GetURL(
350 const nsAString& aPath) const {
351 nsPrintfCString spec("%s://%s/", kProto, mHostname.get());
352
353 nsCOMPtr<nsIURI> uri;
354 MOZ_TRY(NS_NewURI(getter_AddRefs(uri), spec));
355
356 MOZ_TRY(uri->Resolve(NS_ConvertUTF16toUTF8(aPath), spec));
357
358 return NS_ConvertUTF8toUTF16(spec);
359 }
360
RegisterContentScript(WebExtensionContentScript & script,ErrorResult & aRv)361 void WebExtensionPolicy::RegisterContentScript(
362 WebExtensionContentScript& script, ErrorResult& aRv) {
363 // Raise an "invalid argument" error if the script is not related to
364 // the expected extension or if it is already registered.
365 if (script.mExtension != this || mContentScripts.Contains(&script)) {
366 aRv.Throw(NS_ERROR_INVALID_ARG);
367 return;
368 }
369
370 RefPtr<WebExtensionContentScript> newScript = &script;
371
372 if (!mContentScripts.AppendElement(std::move(newScript), fallible)) {
373 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
374 return;
375 }
376
377 WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this);
378 }
379
UnregisterContentScript(const WebExtensionContentScript & script,ErrorResult & aRv)380 void WebExtensionPolicy::UnregisterContentScript(
381 const WebExtensionContentScript& script, ErrorResult& aRv) {
382 if (script.mExtension != this || !mContentScripts.RemoveElement(&script)) {
383 aRv.Throw(NS_ERROR_INVALID_ARG);
384 return;
385 }
386
387 WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this);
388 }
389
CanAccessURI(const URLInfo & aURI,bool aExplicit,bool aCheckRestricted,bool aAllowFilePermission) const390 bool WebExtensionPolicy::CanAccessURI(const URLInfo& aURI, bool aExplicit,
391 bool aCheckRestricted,
392 bool aAllowFilePermission) const {
393 return (!aCheckRestricted || !IsRestrictedURI(aURI)) && mHostPermissions &&
394 mHostPermissions->Matches(aURI, aExplicit) &&
395 (aURI.Scheme() != nsGkAtoms::file || aAllowFilePermission);
396 }
397
InjectContentScripts(ErrorResult & aRv)398 void WebExtensionPolicy::InjectContentScripts(ErrorResult& aRv) {
399 nsresult rv = EPS().InjectContentScripts(this);
400 if (NS_FAILED(rv)) {
401 aRv.Throw(rv);
402 }
403 }
404
405 /* static */
UseRemoteWebExtensions(GlobalObject & aGlobal)406 bool WebExtensionPolicy::UseRemoteWebExtensions(GlobalObject& aGlobal) {
407 return EPS().UseRemoteExtensions();
408 }
409
410 /* static */
IsExtensionProcess(GlobalObject & aGlobal)411 bool WebExtensionPolicy::IsExtensionProcess(GlobalObject& aGlobal) {
412 return EPS().IsExtensionProcess();
413 }
414
415 /* static */
BackgroundServiceWorkerEnabled(GlobalObject & aGlobal)416 bool WebExtensionPolicy::BackgroundServiceWorkerEnabled(GlobalObject& aGlobal) {
417 return StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup();
418 }
419
420 namespace {
421 /**
422 * Maintains a dynamically updated AtomSet based on the comma-separated
423 * values in the given string pref.
424 */
425 class AtomSetPref : public nsIObserver, public nsSupportsWeakReference {
426 public:
427 NS_DECL_ISUPPORTS
428 NS_DECL_NSIOBSERVER
429
Create(const nsCString & aPref)430 static already_AddRefed<AtomSetPref> Create(const nsCString& aPref) {
431 RefPtr<AtomSetPref> self = new AtomSetPref(aPref.get());
432 Preferences::AddWeakObserver(self, aPref);
433 return self.forget();
434 }
435
436 const AtomSet& Get() const;
437
Contains(const nsAtom * aAtom) const438 bool Contains(const nsAtom* aAtom) const { return Get().Contains(aAtom); }
439
440 protected:
441 virtual ~AtomSetPref() = default;
442
AtomSetPref(const char * aPref)443 explicit AtomSetPref(const char* aPref) : mPref(aPref) {}
444
445 private:
446 mutable RefPtr<AtomSet> mAtomSet;
447 const char* mPref;
448 };
449
Get() const450 const AtomSet& AtomSetPref::Get() const {
451 if (!mAtomSet) {
452 nsAutoCString eltsString;
453 Unused << Preferences::GetCString(mPref, eltsString);
454
455 AutoTArray<nsString, 32> elts;
456 for (const nsACString& elt : eltsString.Split(',')) {
457 elts.AppendElement(NS_ConvertUTF8toUTF16(elt));
458 elts.LastElement().StripWhitespace();
459 }
460 mAtomSet = new AtomSet(elts);
461 }
462
463 return *mAtomSet;
464 }
465
466 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)467 AtomSetPref::Observe(nsISupports* aSubject, const char* aTopic,
468 const char16_t* aData) {
469 mAtomSet = nullptr;
470 return NS_OK;
471 }
472
473 NS_IMPL_ISUPPORTS(AtomSetPref, nsIObserver, nsISupportsWeakReference)
474 }; // namespace
475
476 /* static */
IsRestrictedDoc(const DocInfo & aDoc)477 bool WebExtensionPolicy::IsRestrictedDoc(const DocInfo& aDoc) {
478 // With the exception of top-level about:blank documents with null
479 // principals, we never match documents that have non-content principals,
480 // including those with null principals or system principals.
481 if (aDoc.Principal() && !aDoc.Principal()->GetIsContentPrincipal()) {
482 return true;
483 }
484
485 return IsRestrictedURI(aDoc.PrincipalURL());
486 }
487
488 /* static */
IsRestrictedURI(const URLInfo & aURI)489 bool WebExtensionPolicy::IsRestrictedURI(const URLInfo& aURI) {
490 static RefPtr<AtomSetPref> domains;
491 if (!domains) {
492 domains = AtomSetPref::Create(nsLiteralCString(kRestrictedDomainPref));
493 ClearOnShutdown(&domains);
494 }
495
496 if (domains->Contains(aURI.HostAtom())) {
497 return true;
498 }
499
500 if (AddonManagerWebAPI::IsValidSite(aURI.URI())) {
501 return true;
502 }
503
504 return false;
505 }
506
BackgroundPageHTML() const507 nsCString WebExtensionPolicy::BackgroundPageHTML() const {
508 nsCString result;
509
510 if (mBackgroundScripts.IsNull()) {
511 result.SetIsVoid(true);
512 return result;
513 }
514
515 result.AppendLiteral(kBackgroundPageHTMLStart);
516
517 for (auto& script : mBackgroundScripts.Value()) {
518 nsCString escaped;
519 nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(script), escaped);
520
521 result.AppendPrintf(kBackgroundPageHTMLScript, escaped.get());
522 }
523
524 result.AppendLiteral(kBackgroundPageHTMLEnd);
525 return result;
526 }
527
Localize(const nsAString & aInput,nsString & aOutput) const528 void WebExtensionPolicy::Localize(const nsAString& aInput,
529 nsString& aOutput) const {
530 RefPtr<WebExtensionLocalizeCallback> callback(mLocalizeCallback);
531 callback->Call(aInput, aOutput);
532 }
533
WrapObject(JSContext * aCx,JS::HandleObject aGivenProto)534 JSObject* WebExtensionPolicy::WrapObject(JSContext* aCx,
535 JS::HandleObject aGivenProto) {
536 return WebExtensionPolicy_Binding::Wrap(aCx, this, aGivenProto);
537 }
538
GetContentScripts(nsTArray<RefPtr<WebExtensionContentScript>> & aScripts) const539 void WebExtensionPolicy::GetContentScripts(
540 nsTArray<RefPtr<WebExtensionContentScript>>& aScripts) const {
541 aScripts.AppendElements(mContentScripts);
542 }
543
PrivateBrowsingAllowed() const544 bool WebExtensionPolicy::PrivateBrowsingAllowed() const {
545 return HasPermission(nsGkAtoms::privateBrowsingAllowedPermission);
546 }
547
CanAccessContext(nsILoadContext * aContext) const548 bool WebExtensionPolicy::CanAccessContext(nsILoadContext* aContext) const {
549 MOZ_ASSERT(aContext);
550 return PrivateBrowsingAllowed() || !aContext->UsePrivateBrowsing();
551 }
552
CanAccessWindow(const dom::WindowProxyHolder & aWindow) const553 bool WebExtensionPolicy::CanAccessWindow(
554 const dom::WindowProxyHolder& aWindow) const {
555 if (PrivateBrowsingAllowed()) {
556 return true;
557 }
558 // match browsing mode with policy
559 nsIDocShell* docShell = aWindow.get()->GetDocShell();
560 nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
561 return !(loadContext && loadContext->UsePrivateBrowsing());
562 }
563
GetReadyPromise(JSContext * aCx,JS::MutableHandleObject aResult) const564 void WebExtensionPolicy::GetReadyPromise(
565 JSContext* aCx, JS::MutableHandleObject aResult) const {
566 if (mReadyPromise) {
567 aResult.set(mReadyPromise->PromiseObj());
568 } else {
569 aResult.set(nullptr);
570 }
571 }
572
GetBrowsingContextGroupId() const573 uint64_t WebExtensionPolicy::GetBrowsingContextGroupId() const {
574 MOZ_ASSERT(XRE_IsParentProcess() && mActive);
575 return mBrowsingContextGroup ? mBrowsingContextGroup->Id() : 0;
576 }
577
GetBrowsingContextGroupId(ErrorResult & aRv)578 uint64_t WebExtensionPolicy::GetBrowsingContextGroupId(ErrorResult& aRv) {
579 if (XRE_IsParentProcess() && mActive) {
580 return GetBrowsingContextGroupId();
581 }
582 aRv.ThrowInvalidAccessError(
583 "browsingContextGroupId only available for active policies in the "
584 "parent process");
585 return 0;
586 }
587
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(WebExtensionPolicy,mParent,mBrowsingContextGroup,mLocalizeCallback,mHostPermissions,mWebAccessibleResources,mContentScripts)588 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(
589 WebExtensionPolicy, mParent, mBrowsingContextGroup, mLocalizeCallback,
590 mHostPermissions, mWebAccessibleResources, mContentScripts)
591
592 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionPolicy)
593 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
594 NS_INTERFACE_MAP_ENTRY(nsISupports)
595 NS_INTERFACE_MAP_END
596
597 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionPolicy)
598 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebExtensionPolicy)
599
600 /*****************************************************************************
601 * WebExtensionContentScript / MozDocumentMatcher
602 *****************************************************************************/
603
604 /* static */
605 already_AddRefed<MozDocumentMatcher> MozDocumentMatcher::Constructor(
606 GlobalObject& aGlobal, const dom::MozDocumentMatcherInit& aInit,
607 ErrorResult& aRv) {
608 RefPtr<MozDocumentMatcher> matcher =
609 new MozDocumentMatcher(aGlobal, aInit, false, aRv);
610 if (aRv.Failed()) {
611 return nullptr;
612 }
613 return matcher.forget();
614 }
615
616 /* static */
617 already_AddRefed<WebExtensionContentScript>
Constructor(GlobalObject & aGlobal,WebExtensionPolicy & aExtension,const ContentScriptInit & aInit,ErrorResult & aRv)618 WebExtensionContentScript::Constructor(GlobalObject& aGlobal,
619 WebExtensionPolicy& aExtension,
620 const ContentScriptInit& aInit,
621 ErrorResult& aRv) {
622 RefPtr<WebExtensionContentScript> script =
623 new WebExtensionContentScript(aGlobal, aExtension, aInit, aRv);
624 if (aRv.Failed()) {
625 return nullptr;
626 }
627 return script.forget();
628 }
629
MozDocumentMatcher(GlobalObject & aGlobal,const dom::MozDocumentMatcherInit & aInit,bool aRestricted,ErrorResult & aRv)630 MozDocumentMatcher::MozDocumentMatcher(GlobalObject& aGlobal,
631 const dom::MozDocumentMatcherInit& aInit,
632 bool aRestricted, ErrorResult& aRv)
633 : mHasActiveTabPermission(aInit.mHasActiveTabPermission),
634 mRestricted(aRestricted),
635 mAllFrames(aInit.mAllFrames),
636 mFrameID(aInit.mFrameID),
637 mMatchAboutBlank(aInit.mMatchAboutBlank) {
638 MatchPatternOptions options;
639 options.mRestrictSchemes = mRestricted;
640
641 mMatches = ParseMatches(aGlobal, aInit.mMatches, options,
642 ErrorBehavior::CreateEmptyPattern, aRv);
643 if (aRv.Failed()) {
644 return;
645 }
646
647 if (!aInit.mExcludeMatches.IsNull()) {
648 mExcludeMatches =
649 ParseMatches(aGlobal, aInit.mExcludeMatches.Value(), options,
650 ErrorBehavior::CreateEmptyPattern, aRv);
651 if (aRv.Failed()) {
652 return;
653 }
654 }
655
656 if (!aInit.mIncludeGlobs.IsNull()) {
657 if (!ParseGlobs(aGlobal, aInit.mIncludeGlobs.Value(),
658 mIncludeGlobs.SetValue(), aRv)) {
659 return;
660 }
661 }
662
663 if (!aInit.mExcludeGlobs.IsNull()) {
664 if (!ParseGlobs(aGlobal, aInit.mExcludeGlobs.Value(),
665 mExcludeGlobs.SetValue(), aRv)) {
666 return;
667 }
668 }
669 }
670
WebExtensionContentScript(GlobalObject & aGlobal,WebExtensionPolicy & aExtension,const ContentScriptInit & aInit,ErrorResult & aRv)671 WebExtensionContentScript::WebExtensionContentScript(
672 GlobalObject& aGlobal, WebExtensionPolicy& aExtension,
673 const ContentScriptInit& aInit, ErrorResult& aRv)
674 : MozDocumentMatcher(aGlobal, aInit,
675 !aExtension.HasPermission(nsGkAtoms::mozillaAddons),
676 aRv),
677 mRunAt(aInit.mRunAt) {
678 mCssPaths.Assign(aInit.mCssPaths);
679 mJsPaths.Assign(aInit.mJsPaths);
680 mExtension = &aExtension;
681 }
682
Matches(const DocInfo & aDoc) const683 bool MozDocumentMatcher::Matches(const DocInfo& aDoc) const {
684 if (!mFrameID.IsNull()) {
685 if (aDoc.FrameID() != mFrameID.Value()) {
686 return false;
687 }
688 } else {
689 if (!mAllFrames && !aDoc.IsTopLevel()) {
690 return false;
691 }
692 }
693
694 // match browsing mode with policy
695 nsCOMPtr<nsILoadContext> loadContext = aDoc.GetLoadContext();
696 if (loadContext && mExtension && !mExtension->CanAccessContext(loadContext)) {
697 return false;
698 }
699
700 if (!mMatchAboutBlank && aDoc.URL().InheritsPrincipal()) {
701 return false;
702 }
703
704 // Top-level about:blank is a special case. We treat it as a match if
705 // matchAboutBlank is true and it has the null principal. In all other
706 // cases, we test the URL of the principal that it inherits.
707 if (mMatchAboutBlank && aDoc.IsTopLevel() &&
708 (aDoc.URL().Spec().EqualsLiteral("about:blank") ||
709 aDoc.URL().Scheme() == nsGkAtoms::data) &&
710 aDoc.Principal() && aDoc.Principal()->GetIsNullPrincipal()) {
711 return true;
712 }
713
714 if (mRestricted && mExtension->IsRestrictedDoc(aDoc)) {
715 return false;
716 }
717
718 auto& urlinfo = aDoc.PrincipalURL();
719 if (mHasActiveTabPermission && aDoc.ShouldMatchActiveTabPermission() &&
720 MatchPattern::MatchesAllURLs(urlinfo)) {
721 return true;
722 }
723
724 return MatchesURI(urlinfo);
725 }
726
MatchesURI(const URLInfo & aURL) const727 bool MozDocumentMatcher::MatchesURI(const URLInfo& aURL) const {
728 if (!mMatches->Matches(aURL)) {
729 return false;
730 }
731
732 if (mExcludeMatches && mExcludeMatches->Matches(aURL)) {
733 return false;
734 }
735
736 if (!mIncludeGlobs.IsNull() && !mIncludeGlobs.Value().Matches(aURL.Spec())) {
737 return false;
738 }
739
740 if (!mExcludeGlobs.IsNull() && mExcludeGlobs.Value().Matches(aURL.Spec())) {
741 return false;
742 }
743
744 if (mRestricted && mExtension->IsRestrictedURI(aURL)) {
745 return false;
746 }
747
748 return true;
749 }
750
MatchesWindowGlobal(WindowGlobalChild & aWindow) const751 bool MozDocumentMatcher::MatchesWindowGlobal(WindowGlobalChild& aWindow) const {
752 if (aWindow.IsClosed() || !aWindow.IsCurrentGlobal()) {
753 return false;
754 }
755 nsGlobalWindowInner* inner = aWindow.GetWindowGlobal();
756 if (!inner || !inner->GetDocShell()) {
757 return false;
758 }
759 return Matches(inner->GetOuterWindow());
760 }
761
WrapObject(JSContext * aCx,JS::HandleObject aGivenProto)762 JSObject* MozDocumentMatcher::WrapObject(JSContext* aCx,
763 JS::HandleObject aGivenProto) {
764 return MozDocumentMatcher_Binding::Wrap(aCx, this, aGivenProto);
765 }
766
WrapObject(JSContext * aCx,JS::HandleObject aGivenProto)767 JSObject* WebExtensionContentScript::WrapObject(JSContext* aCx,
768 JS::HandleObject aGivenProto) {
769 return WebExtensionContentScript_Binding::Wrap(aCx, this, aGivenProto);
770 }
771
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MozDocumentMatcher,mMatches,mExcludeMatches,mIncludeGlobs,mExcludeGlobs,mExtension)772 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MozDocumentMatcher, mMatches,
773 mExcludeMatches, mIncludeGlobs,
774 mExcludeGlobs, mExtension)
775
776 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MozDocumentMatcher)
777 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
778 NS_INTERFACE_MAP_ENTRY(nsISupports)
779 NS_INTERFACE_MAP_END
780
781 NS_IMPL_CYCLE_COLLECTING_ADDREF(MozDocumentMatcher)
782 NS_IMPL_CYCLE_COLLECTING_RELEASE(MozDocumentMatcher)
783
784 /*****************************************************************************
785 * MozDocumentObserver
786 *****************************************************************************/
787
788 /* static */
789 already_AddRefed<DocumentObserver> DocumentObserver::Constructor(
790 GlobalObject& aGlobal, dom::MozDocumentCallback& aCallbacks) {
791 RefPtr<DocumentObserver> matcher =
792 new DocumentObserver(aGlobal.GetAsSupports(), aCallbacks);
793 return matcher.forget();
794 }
795
Observe(const dom::Sequence<OwningNonNull<MozDocumentMatcher>> & matchers,ErrorResult & aRv)796 void DocumentObserver::Observe(
797 const dom::Sequence<OwningNonNull<MozDocumentMatcher>>& matchers,
798 ErrorResult& aRv) {
799 if (!EPS().RegisterObserver(*this)) {
800 aRv.Throw(NS_ERROR_FAILURE);
801 return;
802 }
803 mMatchers.Clear();
804 for (auto& matcher : matchers) {
805 if (!mMatchers.AppendElement(matcher, fallible)) {
806 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
807 return;
808 }
809 }
810 }
811
Disconnect()812 void DocumentObserver::Disconnect() {
813 Unused << EPS().UnregisterObserver(*this);
814 }
815
NotifyMatch(MozDocumentMatcher & aMatcher,nsPIDOMWindowOuter * aWindow)816 void DocumentObserver::NotifyMatch(MozDocumentMatcher& aMatcher,
817 nsPIDOMWindowOuter* aWindow) {
818 IgnoredErrorResult rv;
819 mCallbacks->OnNewDocument(
820 aMatcher, WindowProxyHolder(aWindow->GetBrowsingContext()), rv);
821 }
822
NotifyMatch(MozDocumentMatcher & aMatcher,nsILoadInfo * aLoadInfo)823 void DocumentObserver::NotifyMatch(MozDocumentMatcher& aMatcher,
824 nsILoadInfo* aLoadInfo) {
825 IgnoredErrorResult rv;
826 mCallbacks->OnPreloadDocument(aMatcher, aLoadInfo, rv);
827 }
828
WrapObject(JSContext * aCx,JS::HandleObject aGivenProto)829 JSObject* DocumentObserver::WrapObject(JSContext* aCx,
830 JS::HandleObject aGivenProto) {
831 return MozDocumentObserver_Binding::Wrap(aCx, this, aGivenProto);
832 }
833
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DocumentObserver,mCallbacks,mMatchers,mParent)834 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DocumentObserver, mCallbacks, mMatchers,
835 mParent)
836
837 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentObserver)
838 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
839 NS_INTERFACE_MAP_ENTRY(nsISupports)
840 NS_INTERFACE_MAP_END
841
842 NS_IMPL_CYCLE_COLLECTING_ADDREF(DocumentObserver)
843 NS_IMPL_CYCLE_COLLECTING_RELEASE(DocumentObserver)
844
845 /*****************************************************************************
846 * DocInfo
847 *****************************************************************************/
848
849 DocInfo::DocInfo(const URLInfo& aURL, nsILoadInfo* aLoadInfo)
850 : mURL(aURL), mObj(AsVariant(aLoadInfo)) {}
851
DocInfo(nsPIDOMWindowOuter * aWindow)852 DocInfo::DocInfo(nsPIDOMWindowOuter* aWindow)
853 : mURL(aWindow->GetDocumentURI()), mObj(AsVariant(aWindow)) {}
854
IsTopLevel() const855 bool DocInfo::IsTopLevel() const {
856 if (mIsTopLevel.isNothing()) {
857 struct Matcher {
858 bool operator()(Window aWin) {
859 return aWin->GetBrowsingContext()->IsTop();
860 }
861 bool operator()(LoadInfo aLoadInfo) {
862 return aLoadInfo->GetIsTopLevelLoad();
863 }
864 };
865 mIsTopLevel.emplace(mObj.match(Matcher()));
866 }
867 return mIsTopLevel.ref();
868 }
869
WindowShouldMatchActiveTab(nsPIDOMWindowOuter * aWin)870 bool WindowShouldMatchActiveTab(nsPIDOMWindowOuter* aWin) {
871 for (WindowContext* wc = aWin->GetCurrentInnerWindow()->GetWindowContext();
872 wc; wc = wc->GetParentWindowContext()) {
873 BrowsingContext* bc = wc->GetBrowsingContext();
874 if (bc->IsTopContent()) {
875 return true;
876 }
877
878 if (bc->CreatedDynamically() || !wc->GetIsOriginalFrameSource()) {
879 return false;
880 }
881 }
882 MOZ_ASSERT_UNREACHABLE("Should reach top content before end of loop");
883 return false;
884 }
885
ShouldMatchActiveTabPermission() const886 bool DocInfo::ShouldMatchActiveTabPermission() const {
887 struct Matcher {
888 bool operator()(Window aWin) { return WindowShouldMatchActiveTab(aWin); }
889 bool operator()(LoadInfo aLoadInfo) { return false; }
890 };
891 return mObj.match(Matcher());
892 }
893
FrameID() const894 uint64_t DocInfo::FrameID() const {
895 if (mFrameID.isNothing()) {
896 if (IsTopLevel()) {
897 mFrameID.emplace(0);
898 } else {
899 struct Matcher {
900 uint64_t operator()(Window aWin) {
901 return aWin->GetBrowsingContext()->Id();
902 }
903 uint64_t operator()(LoadInfo aLoadInfo) {
904 return aLoadInfo->GetBrowsingContextID();
905 }
906 };
907 mFrameID.emplace(mObj.match(Matcher()));
908 }
909 }
910 return mFrameID.ref();
911 }
912
Principal() const913 nsIPrincipal* DocInfo::Principal() const {
914 if (mPrincipal.isNothing()) {
915 struct Matcher {
916 explicit Matcher(const DocInfo& aThis) : mThis(aThis) {}
917 const DocInfo& mThis;
918
919 nsIPrincipal* operator()(Window aWin) {
920 RefPtr<Document> doc = aWin->GetDoc();
921 return doc->NodePrincipal();
922 }
923 nsIPrincipal* operator()(LoadInfo aLoadInfo) {
924 if (!(mThis.URL().InheritsPrincipal() ||
925 aLoadInfo->GetForceInheritPrincipal())) {
926 return nullptr;
927 }
928 if (auto principal = aLoadInfo->PrincipalToInherit()) {
929 return principal;
930 }
931 return aLoadInfo->TriggeringPrincipal();
932 }
933 };
934 mPrincipal.emplace(mObj.match(Matcher(*this)));
935 }
936 return mPrincipal.ref();
937 }
938
PrincipalURL() const939 const URLInfo& DocInfo::PrincipalURL() const {
940 if (!(Principal() && Principal()->GetIsContentPrincipal())) {
941 return URL();
942 }
943
944 if (mPrincipalURL.isNothing()) {
945 nsIPrincipal* prin = Principal();
946 auto* basePrin = BasePrincipal::Cast(prin);
947 nsCOMPtr<nsIURI> uri;
948 if (NS_SUCCEEDED(basePrin->GetURI(getter_AddRefs(uri)))) {
949 MOZ_DIAGNOSTIC_ASSERT(uri);
950 mPrincipalURL.emplace(uri);
951 } else {
952 mPrincipalURL.emplace(URL());
953 }
954 }
955
956 return mPrincipalURL.ref();
957 }
958
959 } // namespace extensions
960 } // namespace mozilla
961