forked from mirrors/gecko-dev
This requires migrating some members from WebExtensionPolicy to WebExtensionPolicyCore. The mHostPermissions member could not be fully transferred, as the WebIDL reflector needs to be cached for WebExtensionPolicy.allowedOrigins, however the threadsafe core is shared. Differential Revision: https://phabricator.services.mozilla.com/D163039
1056 lines
32 KiB
C++
1056 lines
32 KiB
C++
/* -*- 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 "MainThreadUtils.h"
|
|
#include "mozilla/ExtensionPolicyService.h"
|
|
#include "mozilla/extensions/DocumentObserver.h"
|
|
#include "mozilla/extensions/WebExtensionContentScript.h"
|
|
#include "mozilla/extensions/WebExtensionPolicy.h"
|
|
|
|
#include "mozilla/AddonManagerWebAPI.h"
|
|
#include "mozilla/BasePrincipal.h"
|
|
#include "mozilla/dom/WindowGlobalChild.h"
|
|
#include "mozilla/ResultExtensions.h"
|
|
#include "mozilla/StaticPrefs_extensions.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsEscape.h"
|
|
#include "nsGlobalWindowInner.h"
|
|
#include "nsIObserver.h"
|
|
#include "nsISubstitutingProtocolHandler.h"
|
|
#include "nsLiteralString.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPrintfCString.h"
|
|
|
|
namespace mozilla {
|
|
namespace extensions {
|
|
|
|
using namespace dom;
|
|
|
|
static const char kProto[] = "moz-extension";
|
|
|
|
static const char kBackgroundPageHTMLStart[] =
|
|
"<!DOCTYPE html>\n\
|
|
<html>\n\
|
|
<head><meta charset=\"utf-8\"></head>\n\
|
|
<body>";
|
|
|
|
static const char kBackgroundPageHTMLScript[] =
|
|
"\n\
|
|
<script type=\"text/javascript\" src=\"%s\"></script>";
|
|
|
|
static const char kBackgroundPageHTMLEnd[] =
|
|
"\n\
|
|
</body>\n\
|
|
</html>";
|
|
|
|
#define BASE_CSP_PREF_V2 "extensions.webextensions.base-content-security-policy"
|
|
#define DEFAULT_BASE_CSP_V2 \
|
|
"script-src 'self' https://* http://localhost:* http://127.0.0.1:* " \
|
|
"moz-extension: blob: filesystem: 'unsafe-eval' 'wasm-unsafe-eval' " \
|
|
"'unsafe-inline';"
|
|
|
|
#define BASE_CSP_PREF_V3 \
|
|
"extensions.webextensions.base-content-security-policy.v3"
|
|
#define DEFAULT_BASE_CSP_V3 "script-src 'self' 'wasm-unsafe-eval';"
|
|
|
|
static inline ExtensionPolicyService& EPS() {
|
|
return ExtensionPolicyService::GetSingleton();
|
|
}
|
|
|
|
static nsISubstitutingProtocolHandler* Proto() {
|
|
static nsCOMPtr<nsISubstitutingProtocolHandler> sHandler;
|
|
|
|
if (MOZ_UNLIKELY(!sHandler)) {
|
|
nsCOMPtr<nsIIOService> ios = do_GetIOService();
|
|
MOZ_RELEASE_ASSERT(ios);
|
|
|
|
nsCOMPtr<nsIProtocolHandler> handler;
|
|
ios->GetProtocolHandler(kProto, getter_AddRefs(handler));
|
|
|
|
sHandler = do_QueryInterface(handler);
|
|
MOZ_RELEASE_ASSERT(sHandler);
|
|
|
|
ClearOnShutdown(&sHandler);
|
|
}
|
|
|
|
return sHandler;
|
|
}
|
|
|
|
bool ParseGlobs(GlobalObject& aGlobal,
|
|
Sequence<OwningMatchGlobOrUTF8String> aGlobs,
|
|
nsTArray<RefPtr<MatchGlobCore>>& aResult, ErrorResult& aRv) {
|
|
for (auto& elem : aGlobs) {
|
|
if (elem.IsMatchGlob()) {
|
|
aResult.AppendElement(elem.GetAsMatchGlob()->Core());
|
|
} else {
|
|
RefPtr<MatchGlobCore> glob =
|
|
new MatchGlobCore(elem.GetAsUTF8String(), true, aRv);
|
|
if (aRv.Failed()) {
|
|
return false;
|
|
}
|
|
aResult.AppendElement(glob);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
enum class ErrorBehavior {
|
|
CreateEmptyPattern,
|
|
Fail,
|
|
};
|
|
|
|
already_AddRefed<MatchPatternSet> ParseMatches(
|
|
GlobalObject& aGlobal,
|
|
const OwningMatchPatternSetOrStringSequence& aMatches,
|
|
const MatchPatternOptions& aOptions, ErrorBehavior aErrorBehavior,
|
|
ErrorResult& aRv) {
|
|
if (aMatches.IsMatchPatternSet()) {
|
|
return do_AddRef(aMatches.GetAsMatchPatternSet().get());
|
|
}
|
|
|
|
const auto& strings = aMatches.GetAsStringSequence();
|
|
|
|
nsTArray<OwningStringOrMatchPattern> patterns;
|
|
if (!patterns.SetCapacity(strings.Length(), fallible)) {
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
return nullptr;
|
|
}
|
|
|
|
for (auto& string : strings) {
|
|
OwningStringOrMatchPattern elt;
|
|
elt.SetAsString() = string;
|
|
patterns.AppendElement(elt);
|
|
}
|
|
|
|
RefPtr<MatchPatternSet> result =
|
|
MatchPatternSet::Constructor(aGlobal, patterns, aOptions, aRv);
|
|
|
|
if (aRv.Failed() && aErrorBehavior == ErrorBehavior::CreateEmptyPattern) {
|
|
aRv.SuppressException();
|
|
result = MatchPatternSet::Constructor(aGlobal, {}, aOptions, aRv);
|
|
}
|
|
|
|
return result.forget();
|
|
}
|
|
|
|
WebAccessibleResource::WebAccessibleResource(
|
|
GlobalObject& aGlobal, const WebAccessibleResourceInit& aInit,
|
|
ErrorResult& aRv) {
|
|
ParseGlobs(aGlobal, aInit.mResources, mWebAccessiblePaths, aRv);
|
|
if (aRv.Failed()) {
|
|
return;
|
|
}
|
|
|
|
if (!aInit.mMatches.IsNull()) {
|
|
MatchPatternOptions options;
|
|
options.mRestrictSchemes = true;
|
|
RefPtr<MatchPatternSet> matches =
|
|
ParseMatches(aGlobal, aInit.mMatches.Value(), options,
|
|
ErrorBehavior::CreateEmptyPattern, aRv);
|
|
MOZ_DIAGNOSTIC_ASSERT(!aRv.Failed());
|
|
mMatches = matches->Core();
|
|
}
|
|
|
|
if (!aInit.mExtension_ids.IsNull()) {
|
|
mExtensionIDs = new AtomSet(aInit.mExtension_ids.Value());
|
|
}
|
|
}
|
|
|
|
bool WebAccessibleResource::IsExtensionMatch(const URLInfo& aURI) {
|
|
if (!mExtensionIDs) {
|
|
return false;
|
|
}
|
|
RefPtr<WebExtensionPolicyCore> policy =
|
|
ExtensionPolicyService::GetCoreByHost(aURI.Host());
|
|
return policy && (mExtensionIDs->Contains(nsGkAtoms::_asterisk) ||
|
|
mExtensionIDs->Contains(policy->Id()));
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* WebExtensionPolicyCore
|
|
*****************************************************************************/
|
|
|
|
WebExtensionPolicyCore::WebExtensionPolicyCore(GlobalObject& aGlobal,
|
|
WebExtensionPolicy* aPolicy,
|
|
const WebExtensionInit& aInit,
|
|
ErrorResult& aRv)
|
|
: mPolicy(aPolicy),
|
|
mId(NS_AtomizeMainThread(aInit.mId)),
|
|
mName(aInit.mName),
|
|
mType(NS_AtomizeMainThread(aInit.mType)),
|
|
mManifestVersion(aInit.mManifestVersion),
|
|
mExtensionPageCSP(aInit.mExtensionPageCSP),
|
|
mIsPrivileged(aInit.mIsPrivileged),
|
|
mTemporarilyInstalled(aInit.mTemporarilyInstalled),
|
|
mBackgroundWorkerScript(aInit.mBackgroundWorkerScript),
|
|
mPermissions(new AtomSet(aInit.mPermissions)) {
|
|
// In practice this is not necessary, but in tests where the uuid
|
|
// passed in is not lowercased various tests can fail.
|
|
ToLowerCase(aInit.mMozExtensionHostname, mHostname);
|
|
|
|
// Initialize the base CSP and extension page CSP
|
|
if (mManifestVersion < 3) {
|
|
nsresult rv = Preferences::GetString(BASE_CSP_PREF_V2, mBaseCSP);
|
|
if (NS_FAILED(rv)) {
|
|
mBaseCSP = NS_LITERAL_STRING_FROM_CSTRING(DEFAULT_BASE_CSP_V2);
|
|
}
|
|
} else {
|
|
nsresult rv = Preferences::GetString(BASE_CSP_PREF_V3, mBaseCSP);
|
|
if (NS_FAILED(rv)) {
|
|
mBaseCSP = NS_LITERAL_STRING_FROM_CSTRING(DEFAULT_BASE_CSP_V3);
|
|
}
|
|
}
|
|
|
|
if (mExtensionPageCSP.IsVoid()) {
|
|
if (mManifestVersion < 3) {
|
|
EPS().GetDefaultCSP(mExtensionPageCSP);
|
|
} else {
|
|
EPS().GetDefaultCSPV3(mExtensionPageCSP);
|
|
}
|
|
}
|
|
|
|
mWebAccessibleResources.SetCapacity(aInit.mWebAccessibleResources.Length());
|
|
for (const auto& resourceInit : aInit.mWebAccessibleResources) {
|
|
RefPtr<WebAccessibleResource> resource =
|
|
new WebAccessibleResource(aGlobal, resourceInit, aRv);
|
|
if (aRv.Failed()) {
|
|
return;
|
|
}
|
|
mWebAccessibleResources.AppendElement(std::move(resource));
|
|
}
|
|
|
|
nsresult rv = NS_NewURI(getter_AddRefs(mBaseURI), aInit.mBaseURL);
|
|
if (NS_FAILED(rv)) {
|
|
aRv.Throw(rv);
|
|
}
|
|
}
|
|
|
|
bool WebExtensionPolicyCore::SourceMayAccessPath(
|
|
const URLInfo& aURI, const nsACString& aPath) const {
|
|
if (aURI.Scheme() == nsGkAtoms::moz_extension &&
|
|
MozExtensionHostname().Equals(aURI.Host())) {
|
|
// An extension can always access it's own paths.
|
|
return true;
|
|
}
|
|
// Bug 1786564 Static themes need to allow access to theme resources.
|
|
if (Type() == nsGkAtoms::theme) {
|
|
RefPtr<WebExtensionPolicyCore> policyCore =
|
|
ExtensionPolicyService::GetCoreByHost(aURI.Host());
|
|
return policyCore != nullptr;
|
|
}
|
|
|
|
if (ManifestVersion() < 3) {
|
|
return IsWebAccessiblePath(aPath);
|
|
}
|
|
for (const auto& resource : mWebAccessibleResources) {
|
|
if (resource->SourceMayAccessPath(aURI, aPath)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool WebExtensionPolicyCore::CanAccessURI(const URLInfo& aURI, bool aExplicit,
|
|
bool aCheckRestricted,
|
|
bool aAllowFilePermission) const {
|
|
if (aCheckRestricted && WebExtensionPolicy::IsRestrictedURI(aURI)) {
|
|
return false;
|
|
}
|
|
if (!aAllowFilePermission && aURI.Scheme() == nsGkAtoms::file) {
|
|
return false;
|
|
}
|
|
|
|
AutoReadLock lock(mLock);
|
|
return mHostPermissions && mHostPermissions->Matches(aURI, aExplicit);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* WebExtensionPolicy
|
|
*****************************************************************************/
|
|
|
|
WebExtensionPolicy::WebExtensionPolicy(GlobalObject& aGlobal,
|
|
const WebExtensionInit& aInit,
|
|
ErrorResult& aRv)
|
|
: mCore(new WebExtensionPolicyCore(aGlobal, this, aInit, aRv)),
|
|
mLocalizeCallback(aInit.mLocalizeCallback) {
|
|
if (aRv.Failed()) {
|
|
return;
|
|
}
|
|
|
|
MatchPatternOptions options;
|
|
options.mRestrictSchemes = !HasPermission(nsGkAtoms::mozillaAddons);
|
|
|
|
// Set host permissions with SetAllowedOrigins to make sure the copy in core
|
|
// and WebExtensionPolicy stay in sync.
|
|
RefPtr<MatchPatternSet> hostPermissions =
|
|
ParseMatches(aGlobal, aInit.mAllowedOrigins, options,
|
|
ErrorBehavior::CreateEmptyPattern, aRv);
|
|
if (aRv.Failed()) {
|
|
return;
|
|
}
|
|
SetAllowedOrigins(*hostPermissions);
|
|
|
|
if (!aInit.mBackgroundScripts.IsNull()) {
|
|
mBackgroundScripts.SetValue().AppendElements(
|
|
aInit.mBackgroundScripts.Value());
|
|
}
|
|
|
|
mContentScripts.SetCapacity(aInit.mContentScripts.Length());
|
|
for (const auto& scriptInit : aInit.mContentScripts) {
|
|
// The activeTab permission is only for dynamically injected scripts,
|
|
// it cannot be used for declarative content scripts.
|
|
if (scriptInit.mHasActiveTabPermission) {
|
|
aRv.Throw(NS_ERROR_INVALID_ARG);
|
|
return;
|
|
}
|
|
|
|
RefPtr<WebExtensionContentScript> contentScript =
|
|
new WebExtensionContentScript(aGlobal, *this, scriptInit, aRv);
|
|
if (aRv.Failed()) {
|
|
return;
|
|
}
|
|
mContentScripts.AppendElement(std::move(contentScript));
|
|
}
|
|
|
|
if (aInit.mReadyPromise.WasPassed()) {
|
|
mReadyPromise = &aInit.mReadyPromise.Value();
|
|
}
|
|
}
|
|
|
|
already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::Constructor(
|
|
GlobalObject& aGlobal, const WebExtensionInit& aInit, ErrorResult& aRv) {
|
|
RefPtr<WebExtensionPolicy> policy =
|
|
new WebExtensionPolicy(aGlobal, aInit, aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
return policy.forget();
|
|
}
|
|
|
|
/* static */
|
|
void WebExtensionPolicy::GetActiveExtensions(
|
|
dom::GlobalObject& aGlobal,
|
|
nsTArray<RefPtr<WebExtensionPolicy>>& aResults) {
|
|
EPS().GetAll(aResults);
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByID(
|
|
dom::GlobalObject& aGlobal, const nsAString& aID) {
|
|
return do_AddRef(EPS().GetByID(aID));
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByHostname(
|
|
dom::GlobalObject& aGlobal, const nsACString& aHostname) {
|
|
return do_AddRef(EPS().GetByHost(aHostname));
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByURI(
|
|
dom::GlobalObject& aGlobal, nsIURI* aURI) {
|
|
return do_AddRef(EPS().GetByURL(aURI));
|
|
}
|
|
|
|
void WebExtensionPolicy::SetActive(bool aActive, ErrorResult& aRv) {
|
|
if (aActive == mActive) {
|
|
return;
|
|
}
|
|
|
|
bool ok = aActive ? Enable() : Disable();
|
|
|
|
if (!ok) {
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
}
|
|
}
|
|
|
|
bool WebExtensionPolicy::Enable() {
|
|
MOZ_ASSERT(!mActive);
|
|
|
|
if (!EPS().RegisterExtension(*this)) {
|
|
return false;
|
|
}
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
// Reserve a BrowsingContextGroup for use by this WebExtensionPolicy.
|
|
RefPtr<BrowsingContextGroup> group = BrowsingContextGroup::Create();
|
|
mBrowsingContextGroup = group->MakeKeepAlivePtr();
|
|
}
|
|
|
|
Unused << Proto()->SetSubstitution(MozExtensionHostname(), BaseURI());
|
|
|
|
mActive = true;
|
|
return true;
|
|
}
|
|
|
|
bool WebExtensionPolicy::Disable() {
|
|
MOZ_ASSERT(mActive);
|
|
MOZ_ASSERT(EPS().GetByID(Id()) == this);
|
|
|
|
if (!EPS().UnregisterExtension(*this)) {
|
|
return false;
|
|
}
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
// Clear our BrowsingContextGroup reference. A new instance will be created
|
|
// when the extension is next activated.
|
|
mBrowsingContextGroup = nullptr;
|
|
}
|
|
|
|
Unused << Proto()->SetSubstitution(MozExtensionHostname(), nullptr);
|
|
|
|
mActive = false;
|
|
return true;
|
|
}
|
|
|
|
void WebExtensionPolicy::GetURL(const nsAString& aPath, nsAString& aResult,
|
|
ErrorResult& aRv) const {
|
|
auto result = GetURL(aPath);
|
|
if (result.isOk()) {
|
|
aResult = result.unwrap();
|
|
} else {
|
|
aRv.Throw(result.unwrapErr());
|
|
}
|
|
}
|
|
|
|
Result<nsString, nsresult> WebExtensionPolicy::GetURL(
|
|
const nsAString& aPath) const {
|
|
nsPrintfCString spec("%s://%s/", kProto, MozExtensionHostname().get());
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
MOZ_TRY(NS_NewURI(getter_AddRefs(uri), spec));
|
|
|
|
MOZ_TRY(uri->Resolve(NS_ConvertUTF16toUTF8(aPath), spec));
|
|
|
|
return NS_ConvertUTF8toUTF16(spec);
|
|
}
|
|
|
|
void WebExtensionPolicy::RegisterContentScript(
|
|
WebExtensionContentScript& script, ErrorResult& aRv) {
|
|
// Raise an "invalid argument" error if the script is not related to
|
|
// the expected extension or if it is already registered.
|
|
if (script.mExtension != this || mContentScripts.Contains(&script)) {
|
|
aRv.Throw(NS_ERROR_INVALID_ARG);
|
|
return;
|
|
}
|
|
|
|
RefPtr<WebExtensionContentScript> newScript = &script;
|
|
|
|
if (!mContentScripts.AppendElement(std::move(newScript), fallible)) {
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
return;
|
|
}
|
|
|
|
WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this);
|
|
}
|
|
|
|
void WebExtensionPolicy::UnregisterContentScript(
|
|
const WebExtensionContentScript& script, ErrorResult& aRv) {
|
|
if (script.mExtension != this || !mContentScripts.RemoveElement(&script)) {
|
|
aRv.Throw(NS_ERROR_INVALID_ARG);
|
|
return;
|
|
}
|
|
|
|
WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this);
|
|
}
|
|
|
|
void WebExtensionPolicy::SetAllowedOrigins(MatchPatternSet& aAllowedOrigins) {
|
|
// Make sure to keep the version in `WebExtensionPolicy` (which can be exposed
|
|
// back to script using AllowedOrigins()), and the version in
|
|
// `WebExtensionPolicyCore` (which is threadsafe) in sync.
|
|
AutoWriteLock lock(mCore->mLock);
|
|
mHostPermissions = &aAllowedOrigins;
|
|
mCore->mHostPermissions = aAllowedOrigins.Core();
|
|
}
|
|
|
|
void WebExtensionPolicy::InjectContentScripts(ErrorResult& aRv) {
|
|
nsresult rv = EPS().InjectContentScripts(this);
|
|
if (NS_FAILED(rv)) {
|
|
aRv.Throw(rv);
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
bool WebExtensionPolicy::UseRemoteWebExtensions(GlobalObject& aGlobal) {
|
|
return EPS().UseRemoteExtensions();
|
|
}
|
|
|
|
/* static */
|
|
bool WebExtensionPolicy::IsExtensionProcess(GlobalObject& aGlobal) {
|
|
return EPS().IsExtensionProcess();
|
|
}
|
|
|
|
/* static */
|
|
bool WebExtensionPolicy::BackgroundServiceWorkerEnabled(GlobalObject& aGlobal) {
|
|
return StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup();
|
|
}
|
|
|
|
/* static */
|
|
bool WebExtensionPolicy::IsRestrictedDoc(const DocInfo& aDoc) {
|
|
// With the exception of top-level about:blank documents with null
|
|
// principals, we never match documents that have non-content principals,
|
|
// including those with null principals or system principals.
|
|
if (aDoc.Principal() && !aDoc.Principal()->GetIsContentPrincipal()) {
|
|
return true;
|
|
}
|
|
|
|
return IsRestrictedURI(aDoc.PrincipalURL());
|
|
}
|
|
|
|
/* static */
|
|
bool WebExtensionPolicy::IsRestrictedURI(const URLInfo& aURI) {
|
|
RefPtr<AtomSet> restrictedDomains =
|
|
ExtensionPolicyService::RestrictedDomains();
|
|
|
|
if (restrictedDomains && restrictedDomains->Contains(aURI.HostAtom())) {
|
|
return true;
|
|
}
|
|
|
|
if (AddonManagerWebAPI::IsValidSite(aURI.URI())) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
nsCString WebExtensionPolicy::BackgroundPageHTML() const {
|
|
nsCString result;
|
|
|
|
if (mBackgroundScripts.IsNull()) {
|
|
result.SetIsVoid(true);
|
|
return result;
|
|
}
|
|
|
|
result.AppendLiteral(kBackgroundPageHTMLStart);
|
|
|
|
for (auto& script : mBackgroundScripts.Value()) {
|
|
nsCString escaped;
|
|
nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(script), escaped);
|
|
|
|
result.AppendPrintf(kBackgroundPageHTMLScript, escaped.get());
|
|
}
|
|
|
|
result.AppendLiteral(kBackgroundPageHTMLEnd);
|
|
return result;
|
|
}
|
|
|
|
void WebExtensionPolicy::Localize(const nsAString& aInput,
|
|
nsString& aOutput) const {
|
|
RefPtr<WebExtensionLocalizeCallback> callback(mLocalizeCallback);
|
|
callback->Call(aInput, aOutput);
|
|
}
|
|
|
|
JSObject* WebExtensionPolicy::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return WebExtensionPolicy_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
void WebExtensionPolicy::GetContentScripts(
|
|
nsTArray<RefPtr<WebExtensionContentScript>>& aScripts) const {
|
|
aScripts.AppendElements(mContentScripts);
|
|
}
|
|
|
|
bool WebExtensionPolicy::PrivateBrowsingAllowed() const {
|
|
return HasPermission(nsGkAtoms::privateBrowsingAllowedPermission);
|
|
}
|
|
|
|
bool WebExtensionPolicy::CanAccessContext(nsILoadContext* aContext) const {
|
|
MOZ_ASSERT(aContext);
|
|
return PrivateBrowsingAllowed() || !aContext->UsePrivateBrowsing();
|
|
}
|
|
|
|
bool WebExtensionPolicy::CanAccessWindow(
|
|
const dom::WindowProxyHolder& aWindow) const {
|
|
if (PrivateBrowsingAllowed()) {
|
|
return true;
|
|
}
|
|
// match browsing mode with policy
|
|
nsIDocShell* docShell = aWindow.get()->GetDocShell();
|
|
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
|
|
return !(loadContext && loadContext->UsePrivateBrowsing());
|
|
}
|
|
|
|
void WebExtensionPolicy::GetReadyPromise(
|
|
JSContext* aCx, JS::MutableHandle<JSObject*> aResult) const {
|
|
if (mReadyPromise) {
|
|
aResult.set(mReadyPromise->PromiseObj());
|
|
} else {
|
|
aResult.set(nullptr);
|
|
}
|
|
}
|
|
|
|
uint64_t WebExtensionPolicy::GetBrowsingContextGroupId() const {
|
|
MOZ_ASSERT(XRE_IsParentProcess() && mActive);
|
|
return mBrowsingContextGroup ? mBrowsingContextGroup->Id() : 0;
|
|
}
|
|
|
|
uint64_t WebExtensionPolicy::GetBrowsingContextGroupId(ErrorResult& aRv) {
|
|
if (XRE_IsParentProcess() && mActive) {
|
|
return GetBrowsingContextGroupId();
|
|
}
|
|
aRv.ThrowInvalidAccessError(
|
|
"browsingContextGroupId only available for active policies in the "
|
|
"parent process");
|
|
return 0;
|
|
}
|
|
|
|
WebExtensionPolicy::~WebExtensionPolicy() { mCore->ClearPolicyWeakRef(); }
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WebExtensionPolicy)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebExtensionPolicy)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContextGroup)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalizeCallback)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHostPermissions)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentScripts)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
|
AssertIsOnMainThread();
|
|
tmp->mCore->ClearPolicyWeakRef();
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebExtensionPolicy)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContextGroup)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalizeCallback)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHostPermissions)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentScripts)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionPolicy)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionPolicy)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(WebExtensionPolicy)
|
|
|
|
/*****************************************************************************
|
|
* WebExtensionContentScript / MozDocumentMatcher
|
|
*****************************************************************************/
|
|
|
|
/* static */
|
|
already_AddRefed<MozDocumentMatcher> MozDocumentMatcher::Constructor(
|
|
GlobalObject& aGlobal, const dom::MozDocumentMatcherInit& aInit,
|
|
ErrorResult& aRv) {
|
|
RefPtr<MozDocumentMatcher> matcher =
|
|
new MozDocumentMatcher(aGlobal, aInit, false, aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
return matcher.forget();
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<WebExtensionContentScript>
|
|
WebExtensionContentScript::Constructor(GlobalObject& aGlobal,
|
|
WebExtensionPolicy& aExtension,
|
|
const ContentScriptInit& aInit,
|
|
ErrorResult& aRv) {
|
|
RefPtr<WebExtensionContentScript> script =
|
|
new WebExtensionContentScript(aGlobal, aExtension, aInit, aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
return script.forget();
|
|
}
|
|
|
|
MozDocumentMatcher::MozDocumentMatcher(GlobalObject& aGlobal,
|
|
const dom::MozDocumentMatcherInit& aInit,
|
|
bool aRestricted, ErrorResult& aRv)
|
|
: mHasActiveTabPermission(aInit.mHasActiveTabPermission),
|
|
mRestricted(aRestricted),
|
|
mAllFrames(aInit.mAllFrames),
|
|
mCheckPermissions(aInit.mCheckPermissions),
|
|
mFrameID(aInit.mFrameID),
|
|
mMatchAboutBlank(aInit.mMatchAboutBlank) {
|
|
MatchPatternOptions options;
|
|
options.mRestrictSchemes = mRestricted;
|
|
|
|
mMatches = ParseMatches(aGlobal, aInit.mMatches, options,
|
|
ErrorBehavior::CreateEmptyPattern, aRv);
|
|
if (aRv.Failed()) {
|
|
return;
|
|
}
|
|
|
|
if (!aInit.mExcludeMatches.IsNull()) {
|
|
mExcludeMatches =
|
|
ParseMatches(aGlobal, aInit.mExcludeMatches.Value(), options,
|
|
ErrorBehavior::CreateEmptyPattern, aRv);
|
|
if (aRv.Failed()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!aInit.mIncludeGlobs.IsNull()) {
|
|
if (!ParseGlobs(aGlobal, aInit.mIncludeGlobs.Value(),
|
|
mIncludeGlobs.SetValue(), aRv)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!aInit.mExcludeGlobs.IsNull()) {
|
|
if (!ParseGlobs(aGlobal, aInit.mExcludeGlobs.Value(),
|
|
mExcludeGlobs.SetValue(), aRv)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!aInit.mOriginAttributesPatterns.IsNull()) {
|
|
Sequence<OriginAttributesPattern>& arr =
|
|
mOriginAttributesPatterns.SetValue();
|
|
for (const auto& pattern : aInit.mOriginAttributesPatterns.Value()) {
|
|
if (!arr.AppendElement(OriginAttributesPattern(pattern), fallible)) {
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
WebExtensionContentScript::WebExtensionContentScript(
|
|
GlobalObject& aGlobal, WebExtensionPolicy& aExtension,
|
|
const ContentScriptInit& aInit, ErrorResult& aRv)
|
|
: MozDocumentMatcher(aGlobal, aInit,
|
|
!aExtension.HasPermission(nsGkAtoms::mozillaAddons),
|
|
aRv),
|
|
mRunAt(aInit.mRunAt) {
|
|
mCssPaths.Assign(aInit.mCssPaths);
|
|
mJsPaths.Assign(aInit.mJsPaths);
|
|
mExtension = &aExtension;
|
|
|
|
// Origin permissions are optional in mv3, so always check them at runtime.
|
|
if (mExtension->ManifestVersion() >= 3) {
|
|
mCheckPermissions = true;
|
|
}
|
|
}
|
|
|
|
bool MozDocumentMatcher::Matches(const DocInfo& aDoc,
|
|
bool aIgnorePermissions) const {
|
|
if (!mFrameID.IsNull()) {
|
|
if (aDoc.FrameID() != mFrameID.Value()) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!mAllFrames && !aDoc.IsTopLevel()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// match browsing mode with policy
|
|
nsCOMPtr<nsILoadContext> loadContext = aDoc.GetLoadContext();
|
|
if (loadContext && mExtension && !mExtension->CanAccessContext(loadContext)) {
|
|
return false;
|
|
}
|
|
|
|
if (loadContext && !mOriginAttributesPatterns.IsNull()) {
|
|
OriginAttributes docShellAttrs;
|
|
loadContext->GetOriginAttributes(docShellAttrs);
|
|
bool patternMatch = false;
|
|
for (const auto& pattern : mOriginAttributesPatterns.Value()) {
|
|
if (pattern.Matches(docShellAttrs)) {
|
|
patternMatch = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!patternMatch) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!mMatchAboutBlank && aDoc.URL().InheritsPrincipal()) {
|
|
return false;
|
|
}
|
|
|
|
// Top-level about:blank is a special case. We treat it as a match if
|
|
// matchAboutBlank is true and it has the null principal. In all other
|
|
// cases, we test the URL of the principal that it inherits.
|
|
if (mMatchAboutBlank && aDoc.IsTopLevel() &&
|
|
(aDoc.URL().Spec().EqualsLiteral("about:blank") ||
|
|
aDoc.URL().Scheme() == nsGkAtoms::data) &&
|
|
aDoc.Principal() && aDoc.Principal()->GetIsNullPrincipal()) {
|
|
return true;
|
|
}
|
|
|
|
if (mRestricted && mExtension && mExtension->IsRestrictedDoc(aDoc)) {
|
|
return false;
|
|
}
|
|
|
|
auto& urlinfo = aDoc.PrincipalURL();
|
|
if (mExtension && mExtension->ManifestVersion() >= 3) {
|
|
// In MV3, activeTab only allows access to same-origin iframes.
|
|
if (mHasActiveTabPermission && aDoc.IsSameOriginWithTop() &&
|
|
MatchPattern::MatchesAllURLs(urlinfo)) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (mHasActiveTabPermission && aDoc.ShouldMatchActiveTabPermission() &&
|
|
MatchPattern::MatchesAllURLs(urlinfo)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return MatchesURI(urlinfo, aIgnorePermissions);
|
|
}
|
|
|
|
bool MozDocumentMatcher::MatchesURI(const URLInfo& aURL,
|
|
bool aIgnorePermissions) const {
|
|
MOZ_ASSERT((!mRestricted && !mCheckPermissions) || mExtension);
|
|
|
|
if (!mMatches->Matches(aURL)) {
|
|
return false;
|
|
}
|
|
|
|
if (mExcludeMatches && mExcludeMatches->Matches(aURL)) {
|
|
return false;
|
|
}
|
|
|
|
if (!mIncludeGlobs.IsNull() && !mIncludeGlobs.Value().Matches(aURL.CSpec())) {
|
|
return false;
|
|
}
|
|
|
|
if (!mExcludeGlobs.IsNull() && mExcludeGlobs.Value().Matches(aURL.CSpec())) {
|
|
return false;
|
|
}
|
|
|
|
if (mRestricted && mExtension->IsRestrictedURI(aURL)) {
|
|
return false;
|
|
}
|
|
|
|
if (mCheckPermissions && !aIgnorePermissions &&
|
|
!mExtension->CanAccessURI(aURL, false, false, true)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MozDocumentMatcher::MatchesWindowGlobal(WindowGlobalChild& aWindow,
|
|
bool aIgnorePermissions) const {
|
|
if (aWindow.IsClosed() || !aWindow.IsCurrentGlobal()) {
|
|
return false;
|
|
}
|
|
nsGlobalWindowInner* inner = aWindow.GetWindowGlobal();
|
|
if (!inner || !inner->GetDocShell()) {
|
|
return false;
|
|
}
|
|
return Matches(inner->GetOuterWindow(), aIgnorePermissions);
|
|
}
|
|
|
|
void MozDocumentMatcher::GetOriginAttributesPatterns(
|
|
JSContext* aCx, JS::MutableHandle<JS::Value> aVal,
|
|
ErrorResult& aError) const {
|
|
if (!ToJSValue(aCx, mOriginAttributesPatterns, aVal)) {
|
|
aError.NoteJSContextException(aCx);
|
|
}
|
|
}
|
|
|
|
JSObject* MozDocumentMatcher::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return MozDocumentMatcher_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
JSObject* WebExtensionContentScript::WrapObject(
|
|
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
|
|
return WebExtensionContentScript_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MozDocumentMatcher, mMatches,
|
|
mExcludeMatches, mExtension)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MozDocumentMatcher)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(MozDocumentMatcher)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(MozDocumentMatcher)
|
|
|
|
/*****************************************************************************
|
|
* MozDocumentObserver
|
|
*****************************************************************************/
|
|
|
|
/* static */
|
|
already_AddRefed<DocumentObserver> DocumentObserver::Constructor(
|
|
GlobalObject& aGlobal, dom::MozDocumentCallback& aCallbacks) {
|
|
RefPtr<DocumentObserver> matcher =
|
|
new DocumentObserver(aGlobal.GetAsSupports(), aCallbacks);
|
|
return matcher.forget();
|
|
}
|
|
|
|
void DocumentObserver::Observe(
|
|
const dom::Sequence<OwningNonNull<MozDocumentMatcher>>& matchers,
|
|
ErrorResult& aRv) {
|
|
if (!EPS().RegisterObserver(*this)) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
mMatchers.Clear();
|
|
for (auto& matcher : matchers) {
|
|
if (!mMatchers.AppendElement(matcher, fallible)) {
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DocumentObserver::Disconnect() {
|
|
Unused << EPS().UnregisterObserver(*this);
|
|
}
|
|
|
|
void DocumentObserver::NotifyMatch(MozDocumentMatcher& aMatcher,
|
|
nsPIDOMWindowOuter* aWindow) {
|
|
IgnoredErrorResult rv;
|
|
mCallbacks->OnNewDocument(
|
|
aMatcher, WindowProxyHolder(aWindow->GetBrowsingContext()), rv);
|
|
}
|
|
|
|
void DocumentObserver::NotifyMatch(MozDocumentMatcher& aMatcher,
|
|
nsILoadInfo* aLoadInfo) {
|
|
IgnoredErrorResult rv;
|
|
mCallbacks->OnPreloadDocument(aMatcher, aLoadInfo, rv);
|
|
}
|
|
|
|
JSObject* DocumentObserver::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return MozDocumentObserver_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DocumentObserver, mCallbacks, mMatchers,
|
|
mParent)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentObserver)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(DocumentObserver)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(DocumentObserver)
|
|
|
|
/*****************************************************************************
|
|
* DocInfo
|
|
*****************************************************************************/
|
|
|
|
DocInfo::DocInfo(const URLInfo& aURL, nsILoadInfo* aLoadInfo)
|
|
: mURL(aURL), mObj(AsVariant(aLoadInfo)) {}
|
|
|
|
DocInfo::DocInfo(nsPIDOMWindowOuter* aWindow)
|
|
: mURL(aWindow->GetDocumentURI()), mObj(AsVariant(aWindow)) {}
|
|
|
|
bool DocInfo::IsTopLevel() const {
|
|
if (mIsTopLevel.isNothing()) {
|
|
struct Matcher {
|
|
bool operator()(Window aWin) {
|
|
return aWin->GetBrowsingContext()->IsTop();
|
|
}
|
|
bool operator()(LoadInfo aLoadInfo) {
|
|
return aLoadInfo->GetIsTopLevelLoad();
|
|
}
|
|
};
|
|
mIsTopLevel.emplace(mObj.match(Matcher()));
|
|
}
|
|
return mIsTopLevel.ref();
|
|
}
|
|
|
|
bool WindowShouldMatchActiveTab(nsPIDOMWindowOuter* aWin) {
|
|
for (WindowContext* wc = aWin->GetCurrentInnerWindow()->GetWindowContext();
|
|
wc; wc = wc->GetParentWindowContext()) {
|
|
BrowsingContext* bc = wc->GetBrowsingContext();
|
|
if (bc->IsTopContent()) {
|
|
return true;
|
|
}
|
|
|
|
if (bc->CreatedDynamically() || !wc->GetIsOriginalFrameSource()) {
|
|
return false;
|
|
}
|
|
}
|
|
MOZ_ASSERT_UNREACHABLE("Should reach top content before end of loop");
|
|
return false;
|
|
}
|
|
|
|
bool DocInfo::ShouldMatchActiveTabPermission() const {
|
|
struct Matcher {
|
|
bool operator()(Window aWin) { return WindowShouldMatchActiveTab(aWin); }
|
|
bool operator()(LoadInfo aLoadInfo) { return false; }
|
|
};
|
|
return mObj.match(Matcher());
|
|
}
|
|
|
|
bool DocInfo::IsSameOriginWithTop() const {
|
|
struct Matcher {
|
|
bool operator()(Window aWin) {
|
|
WindowContext* wc = aWin->GetCurrentInnerWindow()->GetWindowContext();
|
|
return wc && wc->SameOriginWithTop();
|
|
}
|
|
bool operator()(LoadInfo aLoadInfo) { return false; }
|
|
};
|
|
return mObj.match(Matcher());
|
|
}
|
|
|
|
uint64_t DocInfo::FrameID() const {
|
|
if (mFrameID.isNothing()) {
|
|
if (IsTopLevel()) {
|
|
mFrameID.emplace(0);
|
|
} else {
|
|
struct Matcher {
|
|
uint64_t operator()(Window aWin) {
|
|
return aWin->GetBrowsingContext()->Id();
|
|
}
|
|
uint64_t operator()(LoadInfo aLoadInfo) {
|
|
return aLoadInfo->GetBrowsingContextID();
|
|
}
|
|
};
|
|
mFrameID.emplace(mObj.match(Matcher()));
|
|
}
|
|
}
|
|
return mFrameID.ref();
|
|
}
|
|
|
|
nsIPrincipal* DocInfo::Principal() const {
|
|
if (mPrincipal.isNothing()) {
|
|
struct Matcher {
|
|
explicit Matcher(const DocInfo& aThis) : mThis(aThis) {}
|
|
const DocInfo& mThis;
|
|
|
|
nsIPrincipal* operator()(Window aWin) {
|
|
RefPtr<Document> doc = aWin->GetDoc();
|
|
return doc->NodePrincipal();
|
|
}
|
|
nsIPrincipal* operator()(LoadInfo aLoadInfo) {
|
|
if (!(mThis.URL().InheritsPrincipal() ||
|
|
aLoadInfo->GetForceInheritPrincipal())) {
|
|
return nullptr;
|
|
}
|
|
if (auto principal = aLoadInfo->PrincipalToInherit()) {
|
|
return principal;
|
|
}
|
|
return aLoadInfo->TriggeringPrincipal();
|
|
}
|
|
};
|
|
mPrincipal.emplace(mObj.match(Matcher(*this)));
|
|
}
|
|
return mPrincipal.ref();
|
|
}
|
|
|
|
const URLInfo& DocInfo::PrincipalURL() const {
|
|
if (!(Principal() && Principal()->GetIsContentPrincipal())) {
|
|
return URL();
|
|
}
|
|
|
|
if (mPrincipalURL.isNothing()) {
|
|
nsIPrincipal* prin = Principal();
|
|
auto* basePrin = BasePrincipal::Cast(prin);
|
|
nsCOMPtr<nsIURI> uri;
|
|
if (NS_SUCCEEDED(basePrin->GetURI(getter_AddRefs(uri)))) {
|
|
MOZ_DIAGNOSTIC_ASSERT(uri);
|
|
mPrincipalURL.emplace(uri);
|
|
} else {
|
|
mPrincipalURL.emplace(URL());
|
|
}
|
|
}
|
|
|
|
return mPrincipalURL.ref();
|
|
}
|
|
|
|
} // namespace extensions
|
|
} // namespace mozilla
|