/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/ExtensionPolicyService.h" #include "mozilla/extensions/DocumentObserver.h" #include "mozilla/extensions/WebExtensionContentScript.h" #include "mozilla/extensions/WebExtensionPolicy.h" #include "mozilla/BasePrincipal.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Preferences.h" #include "mozilla/ResultExtensions.h" #include "mozilla/Services.h" #include "mozilla/SimpleEnumerator.h" #include "mozilla/StaticPrefs_extensions.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/Promise-inl.h" #include "mozIExtensionProcessScript.h" #include "nsEscape.h" #include "nsGkAtoms.h" #include "nsHashKeys.h" #include "nsIChannel.h" #include "nsIContentPolicy.h" #include "mozilla/dom/Document.h" #include "nsGlobalWindowOuter.h" #include "nsILoadInfo.h" #include "nsIXULRuntime.h" #include "nsImportModule.h" #include "nsNetUtil.h" #include "nsPrintfCString.h" #include "nsPIDOMWindow.h" #include "nsXULAppAPI.h" #include "nsQueryObject.h" namespace mozilla { using namespace extensions; using dom::AutoJSAPI; using dom::Document; using dom::Promise; #define DEFAULT_CSP_PREF \ "extensions.webextensions.default-content-security-policy" #define DEFAULT_DEFAULT_CSP "script-src 'self' 'wasm-unsafe-eval';" #define DEFAULT_CSP_PREF_V3 \ "extensions.webextensions.default-content-security-policy.v3" #define DEFAULT_DEFAULT_CSP_V3 "script-src 'self'; upgrade-insecure-requests;" #define RESTRICTED_DOMAINS_PREF "extensions.webextensions.restrictedDomains" #define QUARANTINED_DOMAINS_PREF "extensions.quarantinedDomains.list" #define QUARANTINED_DOMAINS_ENABLED "extensions.quarantinedDomains.enabled" #define OBS_TOPIC_PRELOAD_SCRIPT "web-extension-preload-content-script" #define OBS_TOPIC_LOAD_SCRIPT "web-extension-load-content-script" static const char kDocElementInserted[] = "initial-document-element-inserted"; /***************************************************************************** * ExtensionPolicyService *****************************************************************************/ using CoreByHostMap = nsTHashMap>; static StaticRWLock sEPSLock; static StaticAutoPtr sCoreByHost MOZ_GUARDED_BY(sEPSLock); static StaticRefPtr sRestrictedDomains MOZ_GUARDED_BY(sEPSLock); static StaticRefPtr sQuarantinedDomains MOZ_GUARDED_BY(sEPSLock); /* static */ mozIExtensionProcessScript& ExtensionPolicyService::ProcessScript() { static nsCOMPtr sProcessScript; MOZ_ASSERT(NS_IsMainThread()); if (MOZ_UNLIKELY(!sProcessScript)) { sProcessScript = do_ImportModule("resource://gre/modules/ExtensionProcessScript.jsm", "ExtensionProcessScript"); ClearOnShutdown(&sProcessScript); } return *sProcessScript; } /* static */ ExtensionPolicyService& ExtensionPolicyService::GetSingleton() { MOZ_ASSERT(NS_IsMainThread()); static RefPtr sExtensionPolicyService; if (MOZ_UNLIKELY(!sExtensionPolicyService)) { sExtensionPolicyService = new ExtensionPolicyService(); RegisterWeakMemoryReporter(sExtensionPolicyService); ClearOnShutdown(&sExtensionPolicyService); } return *sExtensionPolicyService.get(); } /* static */ RefPtr ExtensionPolicyService::GetCoreByHost(const nsACString& aHost) { StaticAutoReadLock lock(sEPSLock); return sCoreByHost ? sCoreByHost->Get(aHost) : nullptr; } ExtensionPolicyService::ExtensionPolicyService() { mObs = services::GetObserverService(); MOZ_RELEASE_ASSERT(mObs); mDefaultCSP.SetIsVoid(true); mDefaultCSPV3.SetIsVoid(true); RegisterObservers(); { StaticAutoWriteLock lock(sEPSLock); MOZ_DIAGNOSTIC_ASSERT(!sCoreByHost, "ExtensionPolicyService created twice?"); sCoreByHost = new CoreByHostMap(); } UpdateRestrictedDomains(); UpdateQuarantinedDomains(); } ExtensionPolicyService::~ExtensionPolicyService() { UnregisterWeakMemoryReporter(this); { StaticAutoWriteLock lock(sEPSLock); sCoreByHost = nullptr; sRestrictedDomains = nullptr; sQuarantinedDomains = nullptr; } } bool ExtensionPolicyService::UseRemoteExtensions() const { static Maybe sRemoteExtensions; if (MOZ_UNLIKELY(sRemoteExtensions.isNothing())) { sRemoteExtensions = Some(StaticPrefs::extensions_webextensions_remote()); } return sRemoteExtensions.value() && BrowserTabsRemoteAutostart(); } bool ExtensionPolicyService::IsExtensionProcess() const { bool isRemote = UseRemoteExtensions(); if (isRemote && XRE_IsContentProcess()) { auto& remoteType = dom::ContentChild::GetSingleton()->GetRemoteType(); return remoteType == EXTENSION_REMOTE_TYPE; } return !isRemote && XRE_IsParentProcess(); } bool ExtensionPolicyService::GetQuarantinedDomainsEnabled() const { return Preferences::GetBool(QUARANTINED_DOMAINS_ENABLED); } WebExtensionPolicy* ExtensionPolicyService::GetByURL(const URLInfo& aURL) { if (aURL.Scheme() == nsGkAtoms::moz_extension) { return GetByHost(aURL.Host()); } return nullptr; } WebExtensionPolicy* ExtensionPolicyService::GetByHost( const nsACString& aHost) const { AssertIsOnMainThread(); RefPtr core = GetCoreByHost(aHost); return core ? core->GetMainThreadPolicy() : nullptr; } void ExtensionPolicyService::GetAll( nsTArray>& aResult) { AppendToArray(aResult, mExtensions.Values()); } bool ExtensionPolicyService::RegisterExtension(WebExtensionPolicy& aPolicy) { bool ok = (!GetByID(aPolicy.Id()) && !GetByHost(aPolicy.MozExtensionHostname())); MOZ_ASSERT(ok); if (!ok) { return false; } mExtensions.InsertOrUpdate(aPolicy.Id(), RefPtr{&aPolicy}); { StaticAutoWriteLock lock(sEPSLock); sCoreByHost->InsertOrUpdate(aPolicy.MozExtensionHostname(), aPolicy.Core()); } return true; } bool ExtensionPolicyService::UnregisterExtension(WebExtensionPolicy& aPolicy) { bool ok = (GetByID(aPolicy.Id()) == &aPolicy && GetByHost(aPolicy.MozExtensionHostname()) == &aPolicy); MOZ_ASSERT(ok); if (!ok) { return false; } mExtensions.Remove(aPolicy.Id()); { StaticAutoWriteLock lock(sEPSLock); sCoreByHost->Remove(aPolicy.MozExtensionHostname()); } return true; } bool ExtensionPolicyService::RegisterObserver(DocumentObserver& aObserver) { bool inserted = false; mObservers.LookupOrInsertWith(&aObserver, [&] { inserted = true; return RefPtr{&aObserver}; }); return inserted; } bool ExtensionPolicyService::UnregisterObserver(DocumentObserver& aObserver) { return mObservers.Remove(&aObserver); } /***************************************************************************** * nsIMemoryReporter *****************************************************************************/ NS_IMETHODIMP ExtensionPolicyService::CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) { for (const auto& ext : mExtensions.Values()) { nsAtomCString id(ext->Id()); NS_ConvertUTF16toUTF8 name(ext->Name()); name.ReplaceSubstring("\"", ""); name.ReplaceSubstring("\\", ""); nsString url; MOZ_TRY_VAR(url, ext->GetURL(u""_ns)); nsPrintfCString desc("Extension(id=%s, name=\"%s\", baseURL=%s)", id.get(), name.get(), NS_ConvertUTF16toUTF8(url).get()); desc.ReplaceChar('/', '\\'); nsCString path("extensions/"); path.Append(desc); aHandleReport->Callback(""_ns, path, KIND_NONHEAP, UNITS_COUNT, 1, "WebExtensions that are active in this session"_ns, aData); } return NS_OK; } /***************************************************************************** * Content script management *****************************************************************************/ void ExtensionPolicyService::RegisterObservers() { mObs->AddObserver(this, kDocElementInserted, false); if (XRE_IsContentProcess()) { mObs->AddObserver(this, "http-on-opening-request", false); mObs->AddObserver(this, "document-on-opening-request", false); } Preferences::AddStrongObserver(this, DEFAULT_CSP_PREF); Preferences::AddStrongObserver(this, DEFAULT_CSP_PREF_V3); Preferences::AddStrongObserver(this, RESTRICTED_DOMAINS_PREF); Preferences::AddStrongObserver(this, QUARANTINED_DOMAINS_PREF); Preferences::AddStrongObserver(this, QUARANTINED_DOMAINS_ENABLED); } void ExtensionPolicyService::UnregisterObservers() { mObs->RemoveObserver(this, kDocElementInserted); if (XRE_IsContentProcess()) { mObs->RemoveObserver(this, "http-on-opening-request"); mObs->RemoveObserver(this, "document-on-opening-request"); } Preferences::RemoveObserver(this, DEFAULT_CSP_PREF); Preferences::RemoveObserver(this, DEFAULT_CSP_PREF_V3); Preferences::RemoveObserver(this, RESTRICTED_DOMAINS_PREF); Preferences::RemoveObserver(this, QUARANTINED_DOMAINS_PREF); Preferences::RemoveObserver(this, QUARANTINED_DOMAINS_ENABLED); } nsresult ExtensionPolicyService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { if (!strcmp(aTopic, kDocElementInserted)) { nsCOMPtr doc = do_QueryInterface(aSubject); if (doc) { CheckDocument(doc); } } else if (!strcmp(aTopic, "http-on-opening-request") || !strcmp(aTopic, "document-on-opening-request")) { nsCOMPtr chan = do_QueryInterface(aSubject); if (chan) { CheckRequest(chan); } } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { const nsCString converted = NS_ConvertUTF16toUTF8(aData); const char* pref = converted.get(); if (!strcmp(pref, DEFAULT_CSP_PREF)) { mDefaultCSP.SetIsVoid(true); } else if (!strcmp(pref, DEFAULT_CSP_PREF_V3)) { mDefaultCSPV3.SetIsVoid(true); } else if (!strcmp(pref, RESTRICTED_DOMAINS_PREF)) { UpdateRestrictedDomains(); } else if (!strcmp(pref, QUARANTINED_DOMAINS_PREF) || !strcmp(pref, QUARANTINED_DOMAINS_ENABLED)) { UpdateQuarantinedDomains(); } } return NS_OK; } already_AddRefed ExtensionPolicyService::ExecuteContentScript( nsPIDOMWindowInner* aWindow, WebExtensionContentScript& aScript) { if (!aWindow->IsCurrentInnerWindow()) { return nullptr; } RefPtr promise; ProcessScript().LoadContentScript(&aScript, aWindow, getter_AddRefs(promise)); return promise.forget(); } RefPtr ExtensionPolicyService::ExecuteContentScripts( JSContext* aCx, nsPIDOMWindowInner* aWindow, const nsTArray>& aScripts) { AutoTArray, 8> promises; for (auto& script : aScripts) { if (RefPtr promise = ExecuteContentScript(aWindow, *script)) { promises.AppendElement(std::move(promise)); } } RefPtr promise = Promise::All(aCx, promises, IgnoreErrors()); MOZ_RELEASE_ASSERT(promise); return promise; } // Use browser's MessageManagerGroup to decide if we care about it, to inject // extension APIs or content scripts. Tabs use "browsers", and all custom // extension browsers use "webext-browsers", including popups & sidebars, // background & options pages, and xpcshell tests. static bool IsTabOrExtensionBrowser(dom::BrowsingContext* aBC) { const auto& group = aBC->Top()->GetMessageManagerGroup(); bool rv = group == u"browsers"_ns || group == u"webext-browsers"_ns; #ifdef MOZ_THUNDERBIRD // ...unless it's Thunderbird, which has extra groups for unrelated reasons. rv = rv || group == u"single-site"_ns || group == u"single-page"_ns; #endif return rv; } static nsTArray> GetAllInProcessContentBCs() { nsTArray> contentBCs; nsTArray> groups; dom::BrowsingContextGroup::GetAllGroups(groups); for (const auto& group : groups) { for (const auto& toplevel : group->Toplevels()) { if (!toplevel->IsContent() || toplevel->IsDiscarded() || !IsTabOrExtensionBrowser(toplevel)) { continue; } toplevel->PreOrderWalk([&](dom::BrowsingContext* aContext) { contentBCs.AppendElement(aContext); }); } } return contentBCs; } nsresult ExtensionPolicyService::InjectContentScripts( WebExtensionPolicy* aExtension) { AutoJSAPI jsapi; MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope())); auto contentBCs = GetAllInProcessContentBCs(); for (dom::BrowsingContext* bc : contentBCs) { auto* win = bc->GetDOMWindow(); if (bc->Top()->IsDiscarded() || !win || !win->GetDocumentURI()) { continue; } DocInfo docInfo(win); using RunAt = dom::ContentScriptRunAt; namespace RunAtValues = dom::ContentScriptRunAtValues; using Scripts = AutoTArray, 8>; Scripts scripts[RunAtValues::Count]; auto GetScripts = [&](RunAt aRunAt) -> Scripts&& { static_assert(sizeof(aRunAt) == 1, "Our cast is wrong"); return std::move(scripts[uint8_t(aRunAt)]); }; for (const auto& script : aExtension->ContentScripts()) { if (script->Matches(docInfo)) { GetScripts(script->RunAt()).AppendElement(script); } } nsCOMPtr inner = win->GetCurrentInnerWindow(); MOZ_TRY(ExecuteContentScripts(jsapi.cx(), inner, GetScripts(RunAt::Document_start)) ->ThenWithCycleCollectedArgs( [](JSContext* aCx, JS::Handle aValue, ErrorResult& aRv, ExtensionPolicyService* aSelf, nsPIDOMWindowInner* aInner, Scripts&& aScripts) { return aSelf->ExecuteContentScripts(aCx, aInner, aScripts) .forget(); }, this, inner, GetScripts(RunAt::Document_end)) .andThen([&](auto aPromise) { return aPromise->ThenWithCycleCollectedArgs( [](JSContext* aCx, JS::Handle aValue, ErrorResult& aRv, ExtensionPolicyService* aSelf, nsPIDOMWindowInner* aInner, Scripts&& aScripts) { return aSelf ->ExecuteContentScripts(aCx, aInner, aScripts) .forget(); }, this, inner, GetScripts(RunAt::Document_idle)); })); } return NS_OK; } // Checks a request for matching content scripts, and begins pre-loading them // if necessary. void ExtensionPolicyService::CheckRequest(nsIChannel* aChannel) { nsCOMPtr loadInfo = aChannel->LoadInfo(); auto loadType = loadInfo->GetExternalContentPolicyType(); if (loadType != ExtContentPolicy::TYPE_DOCUMENT && loadType != ExtContentPolicy::TYPE_SUBDOCUMENT) { return; } nsCOMPtr uri; if (NS_FAILED(aChannel->GetURI(getter_AddRefs(uri)))) { return; } CheckContentScripts({uri.get(), loadInfo}, true); } static bool CheckParentFrames(nsPIDOMWindowOuter* aWindow, WebExtensionPolicy& aPolicy) { nsCOMPtr aboutAddons; if (NS_FAILED(NS_NewURI(getter_AddRefs(aboutAddons), "about:addons"))) { return false; } nsCOMPtr htmlAboutAddons; if (NS_FAILED( NS_NewURI(getter_AddRefs(htmlAboutAddons), "chrome://mozapps/content/extensions/aboutaddons.html"))) { return false; } dom::WindowContext* wc = aWindow->GetCurrentInnerWindow()->GetWindowContext(); while ((wc = wc->GetParentWindowContext())) { if (!wc->IsInProcess()) { return false; } nsGlobalWindowInner* win = wc->GetInnerWindow(); auto* principal = BasePrincipal::Cast(win->GetPrincipal()); if (principal->IsSystemPrincipal()) { // The add-on manager is a special case, since it contains extension // options pages in same-type frames. nsIURI* uri = win->GetDocumentURI(); bool equals; if ((NS_SUCCEEDED(uri->Equals(aboutAddons, &equals)) && equals) || (NS_SUCCEEDED(uri->Equals(htmlAboutAddons, &equals)) && equals)) { return true; } } if (principal->AddonPolicy() != &aPolicy) { return false; } } return true; } // Checks a document, just after the document element has been inserted, for // matching content scripts or extension principals, and loads them if // necessary. void ExtensionPolicyService::CheckDocument(Document* aDocument) { nsCOMPtr win = aDocument->GetWindow(); if (win) { if (!IsTabOrExtensionBrowser(win->GetBrowsingContext())) { return; } if (win->GetDocumentURI()) { CheckContentScripts(win.get(), false); } nsIPrincipal* principal = aDocument->NodePrincipal(); RefPtr policy = BasePrincipal::Cast(principal)->AddonPolicy(); if (policy) { bool privileged = IsExtensionProcess() && CheckParentFrames(win, *policy); ProcessScript().InitExtensionDocument(policy, aDocument, privileged); } } } void ExtensionPolicyService::CheckContentScripts(const DocInfo& aDocInfo, bool aIsPreload) { nsCOMPtr win; if (!aIsPreload) { win = aDocInfo.GetWindow()->GetCurrentInnerWindow(); } nsTArray> scriptsToLoad; for (RefPtr policy : mExtensions.Values()) { for (auto& script : policy->ContentScripts()) { if (script->Matches(aDocInfo)) { if (aIsPreload) { ProcessScript().PreloadContentScript(script); } else { // Collect the content scripts to load instead of loading them // right away (to prevent a loaded content script from being // able to invalidate the iterator by triggering a call to // policy->UnregisterContentScript while we are still iterating // over all its content scripts). See Bug 1593240. scriptsToLoad.AppendElement(script); } } } for (auto& script : scriptsToLoad) { if (!win->IsCurrentInnerWindow()) { break; } RefPtr promise; ProcessScript().LoadContentScript(script, win, getter_AddRefs(promise)); } scriptsToLoad.ClearAndRetainStorage(); } for (RefPtr observer : mObservers.Values()) { for (auto& matcher : observer->Matchers()) { if (matcher->Matches(aDocInfo)) { if (aIsPreload) { observer->NotifyMatch(*matcher, aDocInfo.GetLoadInfo()); } else { observer->NotifyMatch(*matcher, aDocInfo.GetWindow()); } } } } } /* static */ RefPtr ExtensionPolicyService::RestrictedDomains() { StaticAutoReadLock lock(sEPSLock); return sRestrictedDomains; } /* static */ RefPtr ExtensionPolicyService::QuarantinedDomains() { StaticAutoReadLock lock(sEPSLock); return sQuarantinedDomains; } void ExtensionPolicyService::UpdateRestrictedDomains() { nsAutoCString eltsString; Unused << Preferences::GetCString(RESTRICTED_DOMAINS_PREF, eltsString); AutoTArray elts; for (const nsACString& elt : eltsString.Split(',')) { elts.AppendElement(NS_ConvertUTF8toUTF16(elt)); elts.LastElement().StripWhitespace(); } RefPtr atomSet = new AtomSet(elts); StaticAutoWriteLock lock(sEPSLock); sRestrictedDomains = atomSet; } void ExtensionPolicyService::UpdateQuarantinedDomains() { if (!GetQuarantinedDomainsEnabled()) { StaticAutoWriteLock lock(sEPSLock); sQuarantinedDomains = nullptr; return; } nsAutoCString eltsString; AutoTArray elts; if (NS_SUCCEEDED( Preferences::GetCString(QUARANTINED_DOMAINS_PREF, eltsString))) { for (const nsACString& elt : eltsString.Split(',')) { elts.AppendElement(NS_ConvertUTF8toUTF16(elt)); elts.LastElement().StripWhitespace(); } } RefPtr atomSet = new AtomSet(elts); StaticAutoWriteLock lock(sEPSLock); sQuarantinedDomains = atomSet; } /***************************************************************************** * nsIAddonPolicyService *****************************************************************************/ nsresult ExtensionPolicyService::GetDefaultCSP(nsAString& aDefaultCSP) { if (mDefaultCSP.IsVoid()) { nsresult rv = Preferences::GetString(DEFAULT_CSP_PREF, mDefaultCSP); if (NS_FAILED(rv)) { mDefaultCSP.AssignLiteral(DEFAULT_DEFAULT_CSP); } mDefaultCSP.SetIsVoid(false); } aDefaultCSP.Assign(mDefaultCSP); return NS_OK; } nsresult ExtensionPolicyService::GetDefaultCSPV3(nsAString& aDefaultCSP) { if (mDefaultCSPV3.IsVoid()) { nsresult rv = Preferences::GetString(DEFAULT_CSP_PREF_V3, mDefaultCSPV3); if (NS_FAILED(rv)) { mDefaultCSPV3.AssignLiteral(DEFAULT_DEFAULT_CSP_V3); } mDefaultCSPV3.SetIsVoid(false); } aDefaultCSP.Assign(mDefaultCSPV3); return NS_OK; } nsresult ExtensionPolicyService::GetBaseCSP(const nsAString& aAddonId, nsAString& aResult) { if (WebExtensionPolicy* policy = GetByID(aAddonId)) { policy->GetBaseCSP(aResult); return NS_OK; } return NS_ERROR_INVALID_ARG; } nsresult ExtensionPolicyService::GetExtensionPageCSP(const nsAString& aAddonId, nsAString& aResult) { if (WebExtensionPolicy* policy = GetByID(aAddonId)) { policy->GetExtensionPageCSP(aResult); return NS_OK; } return NS_ERROR_INVALID_ARG; } nsresult ExtensionPolicyService::GetGeneratedBackgroundPageUrl( const nsACString& aHostname, nsACString& aResult) { if (WebExtensionPolicy* policy = GetByHost(aHostname)) { nsAutoCString url("data:text/html,"); nsCString html = policy->BackgroundPageHTML(); nsAutoCString escaped; url.Append(NS_EscapeURL(html, esc_Minimal, escaped)); aResult = url; return NS_OK; } return NS_ERROR_INVALID_ARG; } nsresult ExtensionPolicyService::AddonHasPermission(const nsAString& aAddonId, const nsAString& aPerm, bool* aResult) { if (WebExtensionPolicy* policy = GetByID(aAddonId)) { *aResult = policy->HasPermission(aPerm); return NS_OK; } return NS_ERROR_INVALID_ARG; } nsresult ExtensionPolicyService::AddonMayLoadURI(const nsAString& aAddonId, nsIURI* aURI, bool aExplicit, bool* aResult) { if (WebExtensionPolicy* policy = GetByID(aAddonId)) { *aResult = policy->CanAccessURI(aURI, aExplicit); return NS_OK; } return NS_ERROR_INVALID_ARG; } nsresult ExtensionPolicyService::GetExtensionName(const nsAString& aAddonId, nsAString& aName) { if (WebExtensionPolicy* policy = GetByID(aAddonId)) { aName.Assign(policy->Name()); return NS_OK; } return NS_ERROR_INVALID_ARG; } nsresult ExtensionPolicyService::SourceMayLoadExtensionURI( nsIURI* aSourceURI, nsIURI* aExtensionURI, bool* aResult) { URLInfo source(aSourceURI); URLInfo url(aExtensionURI); if (WebExtensionPolicy* policy = GetByURL(url)) { *aResult = policy->SourceMayAccessPath(source, url.FilePath()); return NS_OK; } return NS_ERROR_INVALID_ARG; } nsresult ExtensionPolicyService::ExtensionURIToAddonId(nsIURI* aURI, nsAString& aResult) { if (WebExtensionPolicy* policy = GetByURL(aURI)) { policy->GetId(aResult); } else { aResult.SetIsVoid(true); } return NS_OK; } NS_IMPL_CYCLE_COLLECTION(ExtensionPolicyService, mExtensions, mObservers) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionPolicyService) NS_INTERFACE_MAP_ENTRY(nsIAddonPolicyService) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_ENTRY(nsIMemoryReporter) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAddonPolicyService) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionPolicyService) NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionPolicyService) } // namespace mozilla