Bug 1711168 support extension matching in webAccessibleResources r=zombie,smaug,rpl
Differential Revision: https://phabricator.services.mozilla.com/D115114
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
#include "nsAboutProtocolUtils.h"
|
#include "nsAboutProtocolUtils.h"
|
||||||
#include "ThirdPartyUtil.h"
|
#include "ThirdPartyUtil.h"
|
||||||
#include "mozilla/ContentPrincipal.h"
|
#include "mozilla/ContentPrincipal.h"
|
||||||
|
#include "mozilla/ExtensionPolicyService.h"
|
||||||
#include "mozilla/NullPrincipal.h"
|
#include "mozilla/NullPrincipal.h"
|
||||||
#include "mozilla/dom/BlobURLProtocolHandler.h"
|
#include "mozilla/dom/BlobURLProtocolHandler.h"
|
||||||
#include "mozilla/dom/ChromeUtils.h"
|
#include "mozilla/dom/ChromeUtils.h"
|
||||||
@@ -596,6 +597,8 @@ nsresult BasePrincipal::CheckMayLoadHelper(nsIURI* aURI,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Web Accessible Resources in MV2 Extensions are marked with
|
||||||
|
// URI_FETCHABLE_BY_ANYONE
|
||||||
bool fetchableByAnyone;
|
bool fetchableByAnyone;
|
||||||
rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FETCHABLE_BY_ANYONE,
|
rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FETCHABLE_BY_ANYONE,
|
||||||
&fetchableByAnyone);
|
&fetchableByAnyone);
|
||||||
@@ -603,16 +606,34 @@ nsresult BasePrincipal::CheckMayLoadHelper(nsIURI* aURI,
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aReport) {
|
// Get the principal uri for the last flag check or error.
|
||||||
nsCOMPtr<nsIURI> prinURI;
|
nsCOMPtr<nsIURI> prinURI;
|
||||||
rv = GetURI(getter_AddRefs(prinURI));
|
rv = GetURI(getter_AddRefs(prinURI));
|
||||||
if (NS_SUCCEEDED(rv) && prinURI) {
|
if (!(NS_SUCCEEDED(rv) && prinURI)) {
|
||||||
nsScriptSecurityManager::ReportError(
|
return NS_ERROR_DOM_BAD_URI;
|
||||||
"CheckSameOriginError", prinURI, aURI,
|
}
|
||||||
mOriginAttributes.mPrivateBrowsingId > 0, aInnerWindowID);
|
|
||||||
|
// If MV3 Extension uris are web accessible by this principal it is allowed to
|
||||||
|
// load.
|
||||||
|
bool maybeWebAccessible = false;
|
||||||
|
NS_URIChainHasFlags(aURI, nsIProtocolHandler::WEBEXT_URI_WEB_ACCESSIBLE,
|
||||||
|
&maybeWebAccessible);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
if (maybeWebAccessible) {
|
||||||
|
bool isWebAccessible = false;
|
||||||
|
rv = ExtensionPolicyService::GetSingleton().SourceMayLoadExtensionURI(
|
||||||
|
prinURI, aURI, &isWebAccessible);
|
||||||
|
if (NS_SUCCEEDED(rv) && isWebAccessible) {
|
||||||
|
return NS_OK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (aReport) {
|
||||||
|
nsScriptSecurityManager::ReportError(
|
||||||
|
"CheckSameOriginError", prinURI, aURI,
|
||||||
|
mOriginAttributes.mPrivateBrowsingId > 0, aInnerWindowID);
|
||||||
|
}
|
||||||
|
|
||||||
return NS_ERROR_DOM_BAD_URI;
|
return NS_ERROR_DOM_BAD_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -741,21 +741,6 @@ nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal,
|
|||||||
return NS_ERROR_DOM_BAD_URI;
|
return NS_ERROR_DOM_BAD_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extensions may allow access to a web accessible resource.
|
|
||||||
bool maybeWebAccessible = false;
|
|
||||||
NS_URIChainHasFlags(targetBaseURI,
|
|
||||||
nsIProtocolHandler::WEBEXT_URI_WEB_ACCESSIBLE,
|
|
||||||
&maybeWebAccessible);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
if (maybeWebAccessible) {
|
|
||||||
bool isWebAccessible = false;
|
|
||||||
rv = ExtensionPolicyService::GetSingleton().SourceMayLoadExtensionURI(
|
|
||||||
sourceURI, targetBaseURI, &isWebAccessible);
|
|
||||||
if (!(NS_SUCCEEDED(rv) && isWebAccessible)) {
|
|
||||||
return NS_ERROR_DOM_BAD_URI;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for uris that are only loadable by principals that subsume them
|
// Check for uris that are only loadable by principals that subsume them
|
||||||
bool targetURIIsLoadableBySubsumers = false;
|
bool targetURIIsLoadableBySubsumers = false;
|
||||||
rv = NS_URIChainHasFlags(targetBaseURI,
|
rv = NS_URIChainHasFlags(targetBaseURI,
|
||||||
@@ -829,6 +814,7 @@ nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal,
|
|||||||
bool schemesMatch =
|
bool schemesMatch =
|
||||||
scheme.Equals(otherScheme, nsCaseInsensitiveCStringComparator);
|
scheme.Equals(otherScheme, nsCaseInsensitiveCStringComparator);
|
||||||
bool isSamePage = false;
|
bool isSamePage = false;
|
||||||
|
bool isExtensionMismatch = false;
|
||||||
// about: URIs are special snowflakes.
|
// about: URIs are special snowflakes.
|
||||||
if (scheme.EqualsLiteral("about") && schemesMatch) {
|
if (scheme.EqualsLiteral("about") && schemesMatch) {
|
||||||
nsAutoCString moduleName, otherModuleName;
|
nsAutoCString moduleName, otherModuleName;
|
||||||
@@ -876,6 +862,13 @@ nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (schemesMatch && scheme.EqualsLiteral("moz-extension")) {
|
||||||
|
// If it is not the same exension, we want to ensure we end up
|
||||||
|
// calling CheckLoadURIFlags
|
||||||
|
nsAutoCString host, otherHost;
|
||||||
|
currentURI->GetHost(host);
|
||||||
|
currentOtherURI->GetHost(otherHost);
|
||||||
|
isExtensionMismatch = !host.Equals(otherHost);
|
||||||
} else {
|
} else {
|
||||||
bool equalExceptRef = false;
|
bool equalExceptRef = false;
|
||||||
rv = currentURI->EqualsExceptRef(currentOtherURI, &equalExceptRef);
|
rv = currentURI->EqualsExceptRef(currentOtherURI, &equalExceptRef);
|
||||||
@@ -884,10 +877,12 @@ nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal,
|
|||||||
|
|
||||||
// If schemes are not equal, or they're equal but the target URI
|
// If schemes are not equal, or they're equal but the target URI
|
||||||
// is different from the source URI and doesn't always allow linking
|
// is different from the source URI and doesn't always allow linking
|
||||||
// from the same scheme, check if the URI flags of the current target
|
// from the same scheme, or this is two different extensions, check
|
||||||
// URI allow the current source URI to link to it.
|
// if the URI flags of the current target URI allow the current
|
||||||
|
// source URI to link to it.
|
||||||
// The policy is specified by the protocol flags on both URIs.
|
// The policy is specified by the protocol flags on both URIs.
|
||||||
if (!schemesMatch || (denySameSchemeLinks && !isSamePage)) {
|
if (!schemesMatch || (denySameSchemeLinks && !isSamePage) ||
|
||||||
|
isExtensionMismatch) {
|
||||||
return CheckLoadURIFlags(
|
return CheckLoadURIFlags(
|
||||||
currentURI, currentOtherURI, sourceBaseURI, targetBaseURI, aFlags,
|
currentURI, currentOtherURI, sourceBaseURI, targetBaseURI, aFlags,
|
||||||
aPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0,
|
aPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0,
|
||||||
@@ -936,7 +931,8 @@ nsresult nsScriptSecurityManager::CheckLoadURIFlags(
|
|||||||
nsresult rv = aTargetBaseURI->GetScheme(targetScheme);
|
nsresult rv = aTargetBaseURI->GetScheme(targetScheme);
|
||||||
if (NS_FAILED(rv)) return rv;
|
if (NS_FAILED(rv)) return rv;
|
||||||
|
|
||||||
// Check for system target URI
|
// Check for system target URI. Regular (non web accessible) extension
|
||||||
|
// URIs will also have URI_DANGEROUS_TO_LOAD.
|
||||||
rv = DenyAccessIfURIHasFlags(aTargetURI,
|
rv = DenyAccessIfURIHasFlags(aTargetURI,
|
||||||
nsIProtocolHandler::URI_DANGEROUS_TO_LOAD);
|
nsIProtocolHandler::URI_DANGEROUS_TO_LOAD);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
@@ -962,6 +958,26 @@ nsresult nsScriptSecurityManager::CheckLoadURIFlags(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If MV3 Extension uris are web accessible they have
|
||||||
|
// WEBEXT_URI_WEB_ACCESSIBLE.
|
||||||
|
bool maybeWebAccessible = false;
|
||||||
|
NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::WEBEXT_URI_WEB_ACCESSIBLE,
|
||||||
|
&maybeWebAccessible);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
if (maybeWebAccessible) {
|
||||||
|
bool isWebAccessible = false;
|
||||||
|
rv = ExtensionPolicyService::GetSingleton().SourceMayLoadExtensionURI(
|
||||||
|
aSourceURI, aTargetURI, &isWebAccessible);
|
||||||
|
if (NS_SUCCEEDED(rv) && isWebAccessible) {
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
if (reportErrors) {
|
||||||
|
ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow,
|
||||||
|
aInnerWindowID);
|
||||||
|
}
|
||||||
|
return NS_ERROR_DOM_BAD_URI;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for chrome target URI
|
// Check for chrome target URI
|
||||||
bool targetURIIsUIResource = false;
|
bool targetURIIsUIResource = false;
|
||||||
rv = NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
|
rv = NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
|
||||||
|
|||||||
@@ -274,7 +274,8 @@ interface WebExtensionPolicy {
|
|||||||
|
|
||||||
dictionary WebAccessibleResourceInit {
|
dictionary WebAccessibleResourceInit {
|
||||||
required sequence<MatchGlobOrString> resources;
|
required sequence<MatchGlobOrString> resources;
|
||||||
MatchPatternSetOrStringSequence matches;
|
MatchPatternSetOrStringSequence? matches = null;
|
||||||
|
sequence<DOMString>? extension_ids = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
dictionary WebExtensionInit {
|
dictionary WebExtensionInit {
|
||||||
|
|||||||
@@ -1248,7 +1248,7 @@ nsresult nsContentSecurityManager::CheckAllowLoadInPrivilegedAboutContext(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Every protocol handler must set one of the five security flags
|
* Every protocol handler must set one of the six security flags
|
||||||
* defined in nsIProtocolHandler - if not - deny the load.
|
* defined in nsIProtocolHandler - if not - deny the load.
|
||||||
*/
|
*/
|
||||||
nsresult nsContentSecurityManager::CheckChannelHasProtocolSecurityFlag(
|
nsresult nsContentSecurityManager::CheckChannelHasProtocolSecurityFlag(
|
||||||
@@ -1273,6 +1273,9 @@ nsresult nsContentSecurityManager::CheckChannelHasProtocolSecurityFlag(
|
|||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
uint32_t securityFlagsSet = 0;
|
uint32_t securityFlagsSet = 0;
|
||||||
|
if (flags & nsIProtocolHandler::WEBEXT_URI_WEB_ACCESSIBLE) {
|
||||||
|
securityFlagsSet += 1;
|
||||||
|
}
|
||||||
if (flags & nsIProtocolHandler::URI_LOADABLE_BY_ANYONE) {
|
if (flags & nsIProtocolHandler::URI_LOADABLE_BY_ANYONE) {
|
||||||
securityFlagsSet += 1;
|
securityFlagsSet += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,7 +275,8 @@ interface nsIProtocolHandler : nsISupports
|
|||||||
/**
|
/**
|
||||||
* This URI may be fetched and the contents are visible to anyone. This is
|
* This URI may be fetched and the contents are visible to anyone. This is
|
||||||
* semantically equivalent to the resource being served with all-access CORS
|
* semantically equivalent to the resource being served with all-access CORS
|
||||||
* headers.
|
* headers. This is only used in MV2 Extensions and should not otherwise
|
||||||
|
* be used.
|
||||||
*/
|
*/
|
||||||
const unsigned long URI_FETCHABLE_BY_ANYONE = (1 << 18);
|
const unsigned long URI_FETCHABLE_BY_ANYONE = (1 << 18);
|
||||||
|
|
||||||
@@ -312,7 +313,7 @@ interface nsIProtocolHandler : nsISupports
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an extension web accessible uri that is loadable if checked
|
* This is an extension web accessible uri that is loadable if checked
|
||||||
* against an allow whitelist.
|
* against an allowlist using ExtensionPolicyService::SourceMayLoadExtensionURI.
|
||||||
*/
|
*/
|
||||||
const unsigned long WEBEXT_URI_WEB_ACCESSIBLE = (1 << 24);
|
const unsigned long WEBEXT_URI_WEB_ACCESSIBLE = (1 << 24);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -416,15 +416,16 @@ nsresult ExtensionProtocolHandler::GetFlagsForURI(nsIURI* aURI,
|
|||||||
|
|
||||||
URLInfo url(aURI);
|
URLInfo url(aURI);
|
||||||
if (auto* policy = EPS().GetByURL(url)) {
|
if (auto* policy = EPS().GetByURL(url)) {
|
||||||
// In general a moz-extension URI is only loadable by chrome, but a
|
// In general a moz-extension URI is only loadable by chrome, but an
|
||||||
// whitelisted subset are web-accessible (and cross-origin fetchable). Check
|
// allowlist subset are web-accessible (and cross-origin fetchable).
|
||||||
// that whitelist. For Manifest V3 extensions, an additional whitelist
|
// The allowlist is checked using EPS.SourceMayLoadExtensionURI in
|
||||||
// for the source loading the url must be checked so we add the flag
|
// BasePrincipal and nsScriptSecurityManager.
|
||||||
// WEBEXT_URI_WEB_ACCESSIBLE, which is then checked in
|
|
||||||
// nsScriptSecurityManager.
|
|
||||||
if (policy->IsWebAccessiblePath(url.FilePath())) {
|
if (policy->IsWebAccessiblePath(url.FilePath())) {
|
||||||
flags |= URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE |
|
if (policy->ManifestVersion() < 3) {
|
||||||
WEBEXT_URI_WEB_ACCESSIBLE;
|
flags |= URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE;
|
||||||
|
} else {
|
||||||
|
flags |= WEBEXT_URI_WEB_ACCESSIBLE;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
flags |= URI_DANGEROUS_TO_LOAD;
|
flags |= URI_DANGEROUS_TO_LOAD;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,6 +304,18 @@ const POSTPROCESSORS = {
|
|||||||
context.logError(context.makeError(msg));
|
context.logError(context.makeError(msg));
|
||||||
throw new Error(msg);
|
throw new Error(msg);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
webAccessibleMatching(value, context) {
|
||||||
|
// Ensure each object has at least one of matches or extension_ids array.
|
||||||
|
for (let obj of value) {
|
||||||
|
if (!obj.matches && !obj.extension_ids) {
|
||||||
|
const msg = `web_accessible_resources requires one of "matches" or "extension_ids"`;
|
||||||
|
context.logError(context.makeError(msg));
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses a regular expression, with support for the Python extended
|
// Parses a regular expression, with support for the Python extended
|
||||||
|
|||||||
@@ -143,12 +143,24 @@ WebAccessibleResource::WebAccessibleResource(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aInit.mMatches.WasPassed()) {
|
if (!aInit.mMatches.IsNull()) {
|
||||||
MatchPatternOptions options;
|
MatchPatternOptions options;
|
||||||
options.mRestrictSchemes = true;
|
options.mRestrictSchemes = true;
|
||||||
mMatches = ParseMatches(aGlobal, aInit.mMatches.Value(), options,
|
mMatches = ParseMatches(aGlobal, aInit.mMatches.Value(), options,
|
||||||
ErrorBehavior::CreateEmptyPattern, aRv);
|
ErrorBehavior::CreateEmptyPattern, aRv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!aInit.mExtension_ids.IsNull()) {
|
||||||
|
mExtensionIDs = new AtomSet(aInit.mExtension_ids.Value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebAccessibleResource::IsExtensionMatch(const URLInfo& aURI) {
|
||||||
|
if (!mExtensionIDs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
WebExtensionPolicy* policy = EPS().GetByHost(aURI.Host());
|
||||||
|
return policy && mExtensionIDs->Contains(policy->Id());
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAccessibleResource)
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAccessibleResource)
|
||||||
|
|||||||
@@ -50,16 +50,23 @@ class WebAccessibleResource final : public nsISupports {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool SourceMayAccessPath(const URLInfo& aURI, const nsAString& aPath) {
|
bool SourceMayAccessPath(const URLInfo& aURI, const nsAString& aPath) {
|
||||||
return mWebAccessiblePaths.Matches(aPath) && mMatches &&
|
return mWebAccessiblePaths.Matches(aPath) &&
|
||||||
mMatches->Matches(aURI);
|
(IsHostMatch(aURI) || IsExtensionMatch(aURI));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsHostMatch(const URLInfo& aURI) {
|
||||||
|
return mMatches && mMatches->Matches(aURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsExtensionMatch(const URLInfo& aURI);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ~WebAccessibleResource() = default;
|
virtual ~WebAccessibleResource() = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MatchGlobSet mWebAccessiblePaths;
|
MatchGlobSet mWebAccessiblePaths;
|
||||||
RefPtr<MatchPatternSet> mMatches;
|
RefPtr<MatchPatternSet> mMatches;
|
||||||
|
RefPtr<AtomSet> mExtensionIDs;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WebExtensionPolicy final : public nsISupports,
|
class WebExtensionPolicy final : public nsISupports,
|
||||||
|
|||||||
@@ -288,16 +288,27 @@
|
|||||||
{
|
{
|
||||||
"min_manifest_version": 3,
|
"min_manifest_version": 3,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"postprocess": "webAccessibleMatching",
|
||||||
|
"minItems": 1,
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"resources": {
|
"resources": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
"items": { "type": "string" }
|
"items": { "type": "string" }
|
||||||
},
|
},
|
||||||
"matches": {
|
"matches": {
|
||||||
|
"optional": true,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
"items": { "$ref": "MatchPattern" }
|
"items": { "$ref": "MatchPattern" }
|
||||||
|
},
|
||||||
|
"extension_ids": {
|
||||||
|
"optional": true,
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": { "$ref": "ExtensionID" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
|
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
|
||||||
|
|
||||||
const server = createHttpServer({ hosts: ["example.com", "example.org"] });
|
const server = createHttpServer({ hosts: ["example.com", "example.org"] });
|
||||||
@@ -11,10 +12,77 @@ let image = atob(
|
|||||||
const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0))
|
const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0))
|
||||||
.buffer;
|
.buffer;
|
||||||
|
|
||||||
|
add_task(async function test_web_accessible_resources_matching() {
|
||||||
|
let extension = await ExtensionTestUtils.loadExtension({
|
||||||
|
manifest: {
|
||||||
|
manifest_version: 3,
|
||||||
|
web_accessible_resources: [
|
||||||
|
{
|
||||||
|
resources: ["/accessible.html"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await Assert.rejects(
|
||||||
|
extension.startup(),
|
||||||
|
/web_accessible_resources requires one of "matches" or "extension_ids"/,
|
||||||
|
"web_accessible_resources object format incorrect"
|
||||||
|
);
|
||||||
|
|
||||||
|
extension = await ExtensionTestUtils.loadExtension({
|
||||||
|
manifest: {
|
||||||
|
manifest_version: 3,
|
||||||
|
web_accessible_resources: [
|
||||||
|
{
|
||||||
|
resources: ["/accessible.html"],
|
||||||
|
matches: ["http://example.com/data/*"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await extension.startup();
|
||||||
|
ok(true, "web_accessible_resources with matches loads");
|
||||||
|
await extension.unload();
|
||||||
|
|
||||||
|
extension = await ExtensionTestUtils.loadExtension({
|
||||||
|
manifest: {
|
||||||
|
manifest_version: 3,
|
||||||
|
web_accessible_resources: [
|
||||||
|
{
|
||||||
|
resources: ["/accessible.html"],
|
||||||
|
extension_ids: ["foo@mochitest"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await extension.startup();
|
||||||
|
ok(true, "web_accessible_resources with extensions loads");
|
||||||
|
await extension.unload();
|
||||||
|
|
||||||
|
extension = await ExtensionTestUtils.loadExtension({
|
||||||
|
manifest: {
|
||||||
|
manifest_version: 3,
|
||||||
|
web_accessible_resources: [
|
||||||
|
{
|
||||||
|
resources: ["/accessible.html"],
|
||||||
|
matches: ["http://example.com/data/*"],
|
||||||
|
extension_ids: ["foo@mochitest"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await extension.startup();
|
||||||
|
ok(true, "web_accessible_resources with matches and extensions loads");
|
||||||
|
await extension.unload();
|
||||||
|
});
|
||||||
|
|
||||||
add_task(async function test_web_accessible_resources() {
|
add_task(async function test_web_accessible_resources() {
|
||||||
async function contentScript() {
|
async function contentScript() {
|
||||||
let canLoad = window.location.href.startsWith("http://example.com");
|
let canLoad = window.location.href.startsWith("http://example.com");
|
||||||
|
|
||||||
let urls = [
|
let urls = [
|
||||||
{
|
{
|
||||||
name: "iframe",
|
name: "iframe",
|
||||||
@@ -136,3 +204,178 @@ add_task(async function test_web_accessible_resources() {
|
|||||||
await page.close();
|
await page.close();
|
||||||
await extension.unload();
|
await extension.unload();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function pageScript() {
|
||||||
|
function test_element_src(data) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
let elem = document.createElement(data.elem);
|
||||||
|
let elemContext =
|
||||||
|
data.content_context && elem.wrappedJSObject
|
||||||
|
? elem.wrappedJSObject
|
||||||
|
: elem;
|
||||||
|
elemContext.setAttribute("src", data.url);
|
||||||
|
elem.addEventListener(
|
||||||
|
"load",
|
||||||
|
() => {
|
||||||
|
browser.test.log(`got load event for ${data.url}`);
|
||||||
|
resolve(true);
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
elem.addEventListener(
|
||||||
|
"error",
|
||||||
|
() => {
|
||||||
|
browser.test.log(`got error event for ${data.url}`);
|
||||||
|
resolve(false);
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
document.body.appendChild(elem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
browser.test.onMessage.addListener(async msg => {
|
||||||
|
browser.test.log(`testing ${JSON.stringify(msg)}`);
|
||||||
|
let loaded = await test_element_src(msg);
|
||||||
|
browser.test.assertEq(loaded, msg.shouldLoad, `${msg.name} loaded`);
|
||||||
|
browser.test.sendMessage("web-accessible-resources");
|
||||||
|
});
|
||||||
|
browser.test.sendMessage("page-loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function test_web_accessible_resources_extensions() {
|
||||||
|
let other = ExtensionTestUtils.loadExtension({
|
||||||
|
manifest: {
|
||||||
|
applications: { gecko: { id: "other@mochitest" } },
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
"page.js": pageScript,
|
||||||
|
|
||||||
|
"page.html": `<html><head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script src="page.js"></script>
|
||||||
|
</head></html>`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let extension = ExtensionTestUtils.loadExtension({
|
||||||
|
manifest: {
|
||||||
|
manifest_version: 3,
|
||||||
|
applications: { gecko: { id: "this@mochitest" } },
|
||||||
|
web_accessible_resources: [
|
||||||
|
{
|
||||||
|
resources: ["/image.png"],
|
||||||
|
extension_ids: ["other@mochitest"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
files: {
|
||||||
|
"image.png": IMAGE_ARRAYBUFFER,
|
||||||
|
"inaccessible.png": IMAGE_ARRAYBUFFER,
|
||||||
|
"page.js": pageScript,
|
||||||
|
|
||||||
|
"page.html": `<html><head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script src="page.js"></script>
|
||||||
|
</head></html>`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await extension.startup();
|
||||||
|
let extensionUrl = `moz-extension://${extension.uuid}/`;
|
||||||
|
|
||||||
|
await other.startup();
|
||||||
|
let pageUrl = `moz-extension://${other.uuid}/page.html`;
|
||||||
|
|
||||||
|
let page = await ExtensionTestUtils.loadContentPage(pageUrl);
|
||||||
|
await other.awaitMessage("page-loaded");
|
||||||
|
|
||||||
|
other.sendMessage({
|
||||||
|
name: "accessible resource",
|
||||||
|
elem: "img",
|
||||||
|
url: `${extensionUrl}image.png`,
|
||||||
|
shouldLoad: true,
|
||||||
|
});
|
||||||
|
await other.awaitMessage("web-accessible-resources");
|
||||||
|
|
||||||
|
other.sendMessage({
|
||||||
|
name: "inaccessible resource",
|
||||||
|
elem: "img",
|
||||||
|
url: `${extensionUrl}inaccessible.png`,
|
||||||
|
shouldLoad: false,
|
||||||
|
});
|
||||||
|
await other.awaitMessage("web-accessible-resources");
|
||||||
|
|
||||||
|
await page.close();
|
||||||
|
|
||||||
|
// test that the extension may load it's own web accessible resource
|
||||||
|
page = await ExtensionTestUtils.loadContentPage(`${extensionUrl}page.html`);
|
||||||
|
await extension.awaitMessage("page-loaded");
|
||||||
|
|
||||||
|
extension.sendMessage({
|
||||||
|
name: "accessible resource",
|
||||||
|
elem: "img",
|
||||||
|
url: `${extensionUrl}image.png`,
|
||||||
|
shouldLoad: true,
|
||||||
|
});
|
||||||
|
await extension.awaitMessage("web-accessible-resources");
|
||||||
|
|
||||||
|
await page.close();
|
||||||
|
await extension.unload();
|
||||||
|
await other.unload();
|
||||||
|
});
|
||||||
|
|
||||||
|
// test that a web page not in matches cannot load the resource
|
||||||
|
add_task(async function test_web_accessible_resources_inaccessible() {
|
||||||
|
let extension = ExtensionTestUtils.loadExtension({
|
||||||
|
temporarilyInstalled: true,
|
||||||
|
manifest: {
|
||||||
|
manifest_version: 3,
|
||||||
|
applications: { gecko: { id: "web@mochitest" } },
|
||||||
|
content_scripts: [
|
||||||
|
{
|
||||||
|
matches: ["http://example.com/data/*"],
|
||||||
|
js: ["page.js"],
|
||||||
|
run_at: "document_idle",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
web_accessible_resources: [
|
||||||
|
{
|
||||||
|
resources: ["/image.png"],
|
||||||
|
extension_ids: ["some_other_ext@mochitest"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
host_permissions: ["*://example.com/*"],
|
||||||
|
granted_host_permissions: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
files: {
|
||||||
|
"image.png": IMAGE_ARRAYBUFFER,
|
||||||
|
"page.js": pageScript,
|
||||||
|
|
||||||
|
"page.html": `<html><head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script src="page.js"></script>
|
||||||
|
</head></html>`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await extension.startup();
|
||||||
|
let extensionUrl = `moz-extension://${extension.uuid}/`;
|
||||||
|
let page = await ExtensionTestUtils.loadContentPage(
|
||||||
|
"http://example.com/data/"
|
||||||
|
);
|
||||||
|
await extension.awaitMessage("page-loaded");
|
||||||
|
|
||||||
|
extension.sendMessage({
|
||||||
|
name: "cannot access resource",
|
||||||
|
elem: "img",
|
||||||
|
url: `${extensionUrl}image.png`,
|
||||||
|
content_context: true,
|
||||||
|
shouldLoad: false,
|
||||||
|
});
|
||||||
|
await extension.awaitMessage("web-accessible-resources");
|
||||||
|
|
||||||
|
await page.close();
|
||||||
|
await extension.unload();
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user