diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /toolkit/components/extensions/ExtensionPolicyService.cpp | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | toolkit/components/extensions/ExtensionPolicyService.cpp | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/toolkit/components/extensions/ExtensionPolicyService.cpp b/toolkit/components/extensions/ExtensionPolicyService.cpp new file mode 100644 index 0000000000..04d0d7df0d --- /dev/null +++ b/toolkit/components/extensions/ExtensionPolicyService.cpp @@ -0,0 +1,652 @@ +/* -*- 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/ContentChild.h" +#include "mozilla/dom/ContentFrameMessageManager.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/Promise-inl.h" +#include "mozIExtensionProcessScript.h" +#include "nsDocShell.h" +#include "nsEscape.h" +#include "nsGkAtoms.h" +#include "nsIChannel.h" +#include "nsIContentPolicy.h" +#include "nsIDocShell.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::ContentFrameMessageManager; +using dom::Document; +using dom::Promise; + +#define DEFAULT_CSP_PREF \ + "extensions.webextensions.default-content-security-policy" +#define DEFAULT_DEFAULT_CSP "script-src 'self'; object-src 'self';" + +#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"; + +static mozIExtensionProcessScript& ProcessScript() { + static nsCOMPtr<mozIExtensionProcessScript> sProcessScript; + + if (MOZ_UNLIKELY(!sProcessScript)) { + sProcessScript = + do_ImportModule("resource://gre/modules/ExtensionProcessScript.jsm", + "ExtensionProcessScript"); + MOZ_RELEASE_ASSERT(sProcessScript); + ClearOnShutdown(&sProcessScript); + } + return *sProcessScript; +} + +/***************************************************************************** + * ExtensionPolicyService + *****************************************************************************/ + +/* static */ ExtensionPolicyService& ExtensionPolicyService::GetSingleton() { + static RefPtr<ExtensionPolicyService> sExtensionPolicyService; + + if (MOZ_UNLIKELY(!sExtensionPolicyService)) { + sExtensionPolicyService = new ExtensionPolicyService(); + RegisterWeakMemoryReporter(sExtensionPolicyService); + ClearOnShutdown(&sExtensionPolicyService); + } + return *sExtensionPolicyService.get(); +} + +ExtensionPolicyService::ExtensionPolicyService() { + mObs = services::GetObserverService(); + MOZ_RELEASE_ASSERT(mObs); + + mDefaultCSP.SetIsVoid(true); + + RegisterObservers(); +} + +ExtensionPolicyService::~ExtensionPolicyService() { + UnregisterWeakMemoryReporter(this); +} + +bool ExtensionPolicyService::UseRemoteExtensions() const { + static Maybe<bool> 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(); +} + +WebExtensionPolicy* ExtensionPolicyService::GetByURL(const URLInfo& aURL) { + if (aURL.Scheme() == nsGkAtoms::moz_extension) { + return GetByHost(aURL.Host()); + } + return nullptr; +} + +void ExtensionPolicyService::GetAll( + nsTArray<RefPtr<WebExtensionPolicy>>& aResult) { + for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) { + aResult.AppendElement(iter.Data()); + } +} + +bool ExtensionPolicyService::RegisterExtension(WebExtensionPolicy& aPolicy) { + bool ok = + (!GetByID(aPolicy.Id()) && !GetByHost(aPolicy.MozExtensionHostname())); + MOZ_ASSERT(ok); + + if (!ok) { + return false; + } + + mExtensions.Put(aPolicy.Id(), RefPtr{&aPolicy}); + mExtensionHosts.Put(aPolicy.MozExtensionHostname(), RefPtr{&aPolicy}); + 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()); + mExtensionHosts.Remove(aPolicy.MozExtensionHostname()); + return true; +} + +bool ExtensionPolicyService::RegisterObserver(DocumentObserver& aObserver) { + if (mObservers.GetWeak(&aObserver)) { + return false; + } + + mObservers.Put(&aObserver, RefPtr{&aObserver}); + return true; +} + +bool ExtensionPolicyService::UnregisterObserver(DocumentObserver& aObserver) { + if (!mObservers.GetWeak(&aObserver)) { + return false; + } + + mObservers.Remove(&aObserver); + return true; +} + +/***************************************************************************** + * nsIMemoryReporter + *****************************************************************************/ + +NS_IMETHODIMP +ExtensionPolicyService::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) { + auto& ext = iter.Data(); + + 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); + mObs->AddObserver(this, "tab-content-frameloader-created", 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); +} + +void ExtensionPolicyService::UnregisterObservers() { + mObs->RemoveObserver(this, kDocElementInserted); + mObs->RemoveObserver(this, "tab-content-frameloader-created"); + if (XRE_IsContentProcess()) { + mObs->RemoveObserver(this, "http-on-opening-request"); + mObs->RemoveObserver(this, "document-on-opening-request"); + } + + Preferences::RemoveObserver(this, DEFAULT_CSP_PREF); +} + +nsresult ExtensionPolicyService::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, kDocElementInserted)) { + nsCOMPtr<Document> doc = do_QueryInterface(aSubject); + if (doc) { + CheckDocument(doc); + } + } else if (!strcmp(aTopic, "http-on-opening-request") || + !strcmp(aTopic, "document-on-opening-request")) { + nsCOMPtr<nsIChannel> chan = do_QueryInterface(aSubject); + if (chan) { + CheckRequest(chan); + } + } else if (!strcmp(aTopic, "tab-content-frameloader-created")) { + RefPtr<ContentFrameMessageManager> mm = do_QueryObject(aSubject); + NS_ENSURE_TRUE(mm, NS_ERROR_UNEXPECTED); + + mMessageManagers.PutEntry(mm); + + mm->AddSystemEventListener(u"unload"_ns, this, false, false); + } 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); + } + } + return NS_OK; +} + +nsresult ExtensionPolicyService::HandleEvent(dom::Event* aEvent) { + RefPtr<ContentFrameMessageManager> mm = do_QueryObject(aEvent->GetTarget()); + MOZ_ASSERT(mm); + if (mm) { + mMessageManagers.RemoveEntry(mm); + } + return NS_OK; +} + +nsresult ForEachDocShell( + nsIDocShell* aDocShell, + const std::function<nsresult(nsIDocShell*)>& aCallback) { + nsTArray<RefPtr<nsIDocShell>> docShells; + MOZ_TRY(aDocShell->GetAllDocShellsInSubtree( + nsIDocShell::typeContent, nsIDocShell::ENUMERATE_FORWARDS, docShells)); + + for (auto& docShell : docShells) { + MOZ_TRY(aCallback(docShell)); + } + return NS_OK; +} + +already_AddRefed<Promise> ExtensionPolicyService::ExecuteContentScript( + nsPIDOMWindowInner* aWindow, WebExtensionContentScript& aScript) { + if (!aWindow->IsCurrentInnerWindow()) { + return nullptr; + } + + RefPtr<Promise> promise; + ProcessScript().LoadContentScript(&aScript, aWindow, getter_AddRefs(promise)); + return promise.forget(); +} + +RefPtr<Promise> ExtensionPolicyService::ExecuteContentScripts( + JSContext* aCx, nsPIDOMWindowInner* aWindow, + const nsTArray<RefPtr<WebExtensionContentScript>>& aScripts) { + AutoTArray<RefPtr<Promise>, 8> promises; + + for (auto& script : aScripts) { + if (RefPtr<Promise> promise = ExecuteContentScript(aWindow, *script)) { + promises.AppendElement(std::move(promise)); + } + } + + RefPtr<Promise> promise = Promise::All(aCx, promises, IgnoreErrors()); + MOZ_RELEASE_ASSERT(promise); + return promise; +} + +nsresult ExtensionPolicyService::InjectContentScripts( + WebExtensionPolicy* aExtension) { + AutoJSAPI jsapi; + MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope())); + + for (auto iter = mMessageManagers.ConstIter(); !iter.Done(); iter.Next()) { + ContentFrameMessageManager* mm = iter.Get()->GetKey(); + + nsCOMPtr<nsIDocShell> docShell = mm->GetDocShell(IgnoreErrors()); + NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED); + + auto result = + ForEachDocShell(docShell, [&](nsIDocShell* aDocShell) -> nsresult { + nsCOMPtr<nsPIDOMWindowOuter> win = aDocShell->GetWindow(); + if (!win->GetDocumentURI()) { + return NS_OK; + } + DocInfo docInfo(win); + + using RunAt = dom::ContentScriptRunAt; + namespace RunAtValues = dom::ContentScriptRunAtValues; + using Scripts = AutoTArray<RefPtr<WebExtensionContentScript>, 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<nsPIDOMWindowInner> inner = win->GetCurrentInnerWindow(); + + MOZ_TRY(ExecuteContentScripts(jsapi.cx(), inner, + GetScripts(RunAt::Document_start)) + ->ThenWithCycleCollectedArgs( + [](JSContext* aCx, JS::HandleValue aValue, + 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::HandleValue aValue, + ExtensionPolicyService* aSelf, + nsPIDOMWindowInner* aInner, Scripts&& aScripts) { + return aSelf + ->ExecuteContentScripts(aCx, aInner, aScripts) + .forget(); + }, + this, inner, GetScripts(RunAt::Document_idle)); + })); + + return NS_OK; + }); + MOZ_TRY(result); + } + return NS_OK; +} + +// Checks a request for matching content scripts, and begins pre-loading them +// if necessary. +void ExtensionPolicyService::CheckRequest(nsIChannel* aChannel) { + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + auto loadType = loadInfo->GetExternalContentPolicyType(); + if (loadType != ExtContentPolicy::TYPE_DOCUMENT && + loadType != ExtContentPolicy::TYPE_SUBDOCUMENT) { + return; + } + + nsCOMPtr<nsIURI> uri; + if (NS_FAILED(aChannel->GetURI(getter_AddRefs(uri)))) { + return; + } + + CheckContentScripts({uri.get(), loadInfo}, true); +} + +static bool CheckParentFrames(nsPIDOMWindowOuter* aWindow, + WebExtensionPolicy& aPolicy) { + nsCOMPtr<nsIURI> aboutAddons; + if (NS_FAILED(NS_NewURI(getter_AddRefs(aboutAddons), "about:addons"))) { + return false; + } + nsCOMPtr<nsIURI> 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 <browser> 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<nsPIDOMWindowOuter> win = aDocument->GetWindow(); + if (win) { + nsIDocShell* docShell = win->GetDocShell(); + RefPtr<ContentFrameMessageManager> mm = docShell->GetMessageManager(); + nsString group = win->GetBrowsingContext()->Top()->GetMessageManagerGroup(); + + // Currently, we use frame scripts to select specific kinds of browsers + // where we want to run content scripts. + if ((!mm || !mMessageManagers.Contains(mm)) && + // With Fission, OOP iframes don't have a frame message manager, so we + // use the browser's MessageManagerGroup attribute to decide if content + // scripts should run. The "browsers" group includes iframes from tabs, + // and the "webext-browsers" group includes custom browsers for + // extension popups/sidebars and xpcshell tests. + !group.EqualsLiteral("browsers") && + !group.EqualsLiteral("webext-browsers")) { + return; + } + + if (win->GetDocumentURI()) { + CheckContentScripts(win.get(), false); + } + + nsIPrincipal* principal = aDocument->NodePrincipal(); + + RefPtr<WebExtensionPolicy> 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<nsPIDOMWindowInner> win; + if (!aIsPreload) { + win = aDocInfo.GetWindow()->GetCurrentInnerWindow(); + } + + nsTArray<RefPtr<WebExtensionContentScript>> scriptsToLoad; + + for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) { + RefPtr<WebExtensionPolicy> policy = iter.Data(); + + 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> promise; + ProcessScript().LoadContentScript(script, win, getter_AddRefs(promise)); + } + + scriptsToLoad.ClearAndRetainStorage(); + } + + for (auto iter = mObservers.Iter(); !iter.Done(); iter.Next()) { + RefPtr<DocumentObserver> observer = iter.Data(); + + for (auto& matcher : observer->Matchers()) { + if (matcher->Matches(aDocInfo)) { + if (aIsPreload) { + observer->NotifyMatch(*matcher, aDocInfo.GetLoadInfo()); + } else { + observer->NotifyMatch(*matcher, aDocInfo.GetWindow()); + } + } + } + } +} + +/***************************************************************************** + * 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::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::ExtensionURILoadableByAnyone(nsIURI* aURI, + bool* aResult) { + URLInfo url(aURI); + if (WebExtensionPolicy* policy = GetByURL(url)) { + *aResult = policy->IsPathWebAccessible(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, mExtensionHosts, + mObservers) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionPolicyService) + NS_INTERFACE_MAP_ENTRY(nsIAddonPolicyService) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) + 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 |