Backed out 3 changesets (bug 1711168, bug 1773115) for causing failures at test_ext_web_accessible_resources.html. CLOSED TREE

Backed out changeset 703c909eb009 (bug 1773115)
Backed out changeset 902385a7ce60 (bug 1711168)
Backed out changeset 3d061ce03de7 (bug 1711168)
This commit is contained in:
Butkovits Atila 2022-07-27 00:13:26 +03:00
Родитель a8e8878706
Коммит ae6f4c94fc
19 изменённых файлов: 188 добавлений и 1032 удалений

Просмотреть файл

@ -611,24 +611,6 @@ this.tabs = class extends ExtensionAPIPersistent {
return tab;
}
function setContentTriggeringPrincipal(url, browser, options) {
// For urls that we want to allow an extension to open in a tab, but
// that it may not otherwise have access to, we set the triggering
// principal to the url that is being opened. This is used for newtab,
// about: and moz-extension: protocols.
// We also prevent discarded, or lazy tabs by setting allowInheritPrincipal to false.
options.allowInheritPrincipal = false;
options.triggeringPrincipal = Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(url),
{
userContextId: options.userContextId,
privateBrowsingId: PrivateBrowsingUtils.isBrowserPrivate(browser)
? 1
: 0,
}
);
}
let tabsApi = {
tabs: {
onActivated: new EventManager({
@ -724,11 +706,9 @@ this.tabs = class extends ExtensionAPIPersistent {
}
}).then(window => {
let url;
let principal = context.principal;
let options = {
allowInheritPrincipal: true,
triggeringPrincipal: context.principal,
};
let options = {};
if (createProperties.cookieStoreId) {
// May throw if validation fails.
options.userContextId = getUserContextIdForCookieStoreId(
@ -741,10 +721,7 @@ this.tabs = class extends ExtensionAPIPersistent {
if (createProperties.url !== null) {
url = context.uri.resolve(createProperties.url);
if (
!url.startsWith("moz-extension://") &&
!context.checkLoadURL(url, { dontReportErrors: true })
) {
if (!context.checkLoadURL(url, { dontReportErrors: true })) {
return Promise.reject({ message: `Illegal URL: ${url}` });
}
@ -754,10 +731,30 @@ this.tabs = class extends ExtensionAPIPersistent {
} else {
url = window.BROWSER_NEW_TAB_URL;
}
// Only set allowInheritPrincipal on discardable urls as it
// will override creating a lazy browser. Setting triggeringPrincipal
// will ensure other cases are handled, but setting it may prevent
// creating about and data urls.
let discardable = url && !url.startsWith("about:");
// Handle moz-ext separately from the discardable flag to retain prior behavior.
if (!discardable || url?.startsWith("moz-extension://")) {
setContentTriggeringPrincipal(url, window.gBrowser, options);
if (!discardable) {
// Make sure things like about:blank and data: URIs never inherit,
// and instead always get a NullPrincipal.
options.allowInheritPrincipal = false;
// Falling back to content here as about: requires it, however is safe.
principal = Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(url),
{
userContextId: options.userContextId,
privateBrowsingId: PrivateBrowsingUtils.isBrowserPrivate(
window.gBrowser
)
? 1
: 0,
}
);
} else {
options.allowInheritPrincipal = true;
options.triggeringPrincipal = context.principal;
}
tabListener.initTabReady();
@ -817,6 +814,7 @@ this.tabs = class extends ExtensionAPIPersistent {
});
}
options.triggeringPrincipal = principal;
let nativeTab = window.gBrowser.addTab(url, options);
if (active) {
@ -890,6 +888,10 @@ this.tabs = class extends ExtensionAPIPersistent {
if (updateProperties.url !== null) {
let url = context.uri.resolve(updateProperties.url);
if (!context.checkLoadURL(url, { dontReportErrors: true })) {
return Promise.reject({ message: `Illegal URL: ${url}` });
}
let options = {
flags: updateProperties.loadReplace
? Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY
@ -897,15 +899,6 @@ this.tabs = class extends ExtensionAPIPersistent {
triggeringPrincipal: context.principal,
};
if (!context.checkLoadURL(url, { dontReportErrors: true })) {
// We allow loading top level tabs for "other" extensions.
if (url.startsWith("moz-extension://")) {
setContentTriggeringPrincipal(url, tabbrowser, options);
} else {
return Promise.reject({ message: `Illegal URL: ${url}` });
}
}
let browser = nativeTab.linkedBrowser;
if (nativeTab.linkedPanel) {
browser.loadURI(url, options);

Просмотреть файл

@ -87,26 +87,6 @@ this.windows = class extends ExtensionAPIPersistent {
const { windowManager } = extension;
function getTriggeringPrincipalForUrl(url) {
if (context.checkLoadURL(url, { dontReportErrors: true })) {
return context.principal;
}
let window = context.currentWindow || windowTracker.topWindow;
// The extension principal cannot directly load about:-URLs except for about:blank, and
// possibly some other loads such as moz-extension. Ensure any page set as a home page
// will load by using a content principal.
return Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(url),
{
privateBrowsingId: PrivateBrowsingUtils.isBrowserPrivate(
window.gBrowser
)
? 1
: 0,
}
);
}
return {
windows: {
onCreated: new EventManager({
@ -202,12 +182,7 @@ this.windows = class extends ExtensionAPIPersistent {
Ci.nsIMutableArray
);
// Creating a new window allows one single triggering principal for all tabs that
// are created in the window. Due to that, if we need a browser principal to load
// some urls, we fallback to using a content principal like we do in the tabs api.
// Throws if url is an array and any url can't be loaded by the extension principal.
let { allowScriptsToClose, principal } = createData;
let principal = context.principal;
if (createData.tabId !== null) {
if (createData.url !== null) {
throw new ExtensionError(
@ -256,24 +231,12 @@ this.windows = class extends ExtensionAPIPersistent {
let array = Cc["@mozilla.org/array;1"].createInstance(
Ci.nsIMutableArray
);
for (let url of createData.url.map(u => context.uri.resolve(u))) {
// We can only provide a single triggering principal when
// opening a window, so if the extension cannot normally
// access a url, we fail. This includes about and moz-ext
// urls.
if (!context.checkLoadURL(url, { dontReportErrors: true })) {
return Promise.reject({ message: `Illegal URL: ${url}` });
}
for (let url of createData.url) {
array.appendElement(mkstr(url));
}
args.appendElement(array);
} else {
let url = context.uri.resolve(createData.url);
args.appendElement(mkstr(url));
principal = getTriggeringPrincipalForUrl(url);
if (allowScriptsToClose === null) {
allowScriptsToClose = url.startsWith("moz-extension://");
}
args.appendElement(mkstr(createData.url));
}
} else {
let url =
@ -282,7 +245,15 @@ this.windows = class extends ExtensionAPIPersistent {
? "about:privatebrowsing"
: HomePage.get().split("|", 1)[0];
args.appendElement(mkstr(url));
principal = getTriggeringPrincipalForUrl(url);
if (
url.startsWith("about:") &&
!context.checkLoadURL(url, { dontReportErrors: true })
) {
// The extension principal cannot directly load about:-URLs,
// except for about:blank. So use the system principal instead.
principal = Services.scriptSecurityManager.getSystemPrincipal();
}
}
args.appendElement(null); // extraOptions
@ -300,7 +271,6 @@ this.windows = class extends ExtensionAPIPersistent {
createData.cookieStoreId,
createData.incognito
);
args.appendElement(userContextIdSupports); // userContextId
} else {
args.appendElement(null);
@ -346,6 +316,12 @@ this.windows = class extends ExtensionAPIPersistent {
}
}
let { allowScriptsToClose, url } = createData;
if (allowScriptsToClose === null) {
allowScriptsToClose =
typeof url === "string" && url.startsWith("moz-extension://");
}
let window = Services.ww.openWindow(
null,
AppConstants.BROWSER_CHROME_URL,

Просмотреть файл

@ -252,10 +252,10 @@
"description": "A URL or array of URLs to open as tabs in the window. Fully-qualified URLs must include a scheme (i.e. 'http://www.google.com', not 'www.google.com'). Relative URLs will be relative to the current page within the extension. Defaults to the New Tab Page.",
"optional": true,
"choices": [
{ "type": "string" },
{ "type": "string", "format": "relativeUrl" },
{
"type": "array",
"items": { "type": "string" }
"items": { "type": "string", "format": "relativeUrl" }
}
]
},

Просмотреть файл

@ -3,8 +3,6 @@
"use strict";
requestLongerTimeout(4);
const { XPCOMUtils } = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
);
@ -719,133 +717,3 @@ add_task(async function test_overriding_home_page_incognito_external() {
await extension.unload();
});
// This tests that the homepage provided by an extension can be opened by any extension
// and does not require web_accessible_resource entries.
async function _test_overriding_home_page_open(manifest_version) {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
manifest_version,
chrome_settings_overrides: { homepage: "home.html" },
name: "homepage provider",
applications: {
gecko: { id: "homepage@mochitest" },
},
},
files: {
"home.html": `<h1>Home Page!</h1><pre id="result"></pre><script src="home.js"></script>`,
"home.js": () => {
document.querySelector("#result").textContent = "homepage loaded";
},
},
useAddonManager: "permanent",
});
await extension.startup();
// ensure it works and deal with initial panel prompt.
await testHomePageWindow({
expectPanel: true,
async test(win) {
Assert.equal(
HomePage.get(win),
`moz-extension://${extension.uuid}/home.html`,
"The homepage is set"
);
Assert.equal(
win.gURLBar.value,
`moz-extension://${extension.uuid}/home.html`,
"extension is control in window"
);
const { selectedBrowser } = win.gBrowser;
const result = await SpecialPowers.spawn(
selectedBrowser,
[],
async () => {
const { document } = this.content;
if (document.readyState !== "complete") {
await new Promise(resolve => (document.onload = resolve));
}
return document.querySelector("#result").textContent;
}
);
Assert.equal(
result,
"homepage loaded",
"Overridden homepage loaded successfully"
);
},
});
// Extension used to open the homepage in a new window.
let opener = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
async background() {
let win;
browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
if (tab.windowId !== win.id || tab.status !== "complete") {
return;
}
browser.test.sendMessage("created", tab.url);
});
browser.test.onMessage.addListener(async msg => {
if (msg == "create") {
win = await browser.windows.create({});
browser.test.assertTrue(
win.id !== browser.windows.WINDOW_ID_NONE,
"New window was created."
);
}
});
},
});
function listener(msg) {
Assert.ok(!/may not load or link to moz-extension/.test(msg.message));
}
Services.console.registerListener(listener);
registerCleanupFunction(() => {
Services.console.unregisterListener(listener);
});
await opener.startup();
const promiseNewWindow = BrowserTestUtils.waitForNewWindow();
await opener.sendMessage("create");
let homepageUrl = await opener.awaitMessage("created");
Assert.equal(
homepageUrl,
`moz-extension://${extension.uuid}/home.html`,
"The homepage is set"
);
const newWin = await promiseNewWindow;
Assert.equal(
await SpecialPowers.spawn(newWin.gBrowser.selectedBrowser, [], async () => {
const { document } = this.content;
if (document.readyState !== "complete") {
await new Promise(resolve => (document.onload = resolve));
}
return document.querySelector("#result").textContent;
}),
"homepage loaded",
"Overridden homepage loaded as expected"
);
await BrowserTestUtils.closeWindow(newWin);
await opener.unload();
await extension.unload();
}
add_task(async function test_overriding_home_page_open_mv2() {
await _test_overriding_home_page_open(2);
});
add_task(async function test_overriding_home_page_open_mv3() {
await SpecialPowers.pushPrefEnv({
set: [["extensions.manifestV3.enabled", true]],
});
await _test_overriding_home_page_open(3);
});

Просмотреть файл

@ -3,8 +3,6 @@
"use strict";
requestLongerTimeout(4);
ChromeUtils.defineModuleGetter(
this,
"ExtensionSettingsStore",
@ -715,72 +713,3 @@ add_task(async function testNewTabPrefsReset() {
"privateAllowed pref is not set"
);
});
// This test ensures that an extension provided newtab
// can be opened by another extension (e.g. tab manager)
// regardless of whether the newtab url is made available
// in web_accessible_resources.
add_task(async function test_newtab_from_extension() {
let panel = getNewTabDoorhanger().closest("panel");
let extension = ExtensionTestUtils.loadExtension({
manifest: {
applications: {
gecko: {
id: "newtaburl@mochi.test",
},
},
chrome_url_overrides: {
newtab: "newtab.html",
},
},
files: {
"newtab.html": `<h1>New tab!</h1><script src="newtab.js"></script>`,
"newtab.js": () => {
browser.test.sendMessage("newtab-loaded");
},
},
useAddonManager: "temporary",
});
await extension.startup();
let extensionNewTabUrl = `moz-extension://${extension.uuid}/newtab.html`;
let popupShown = promisePopupShown(panel);
let tab = await promiseNewTab(extensionNewTabUrl);
await popupShown;
// This will show a confirmation doorhanger, make sure we don't leave it open.
let popupHidden = promisePopupHidden(panel);
panel.hidePopup();
await popupHidden;
BrowserTestUtils.removeTab(tab);
// extension to open the newtab
let opener = ExtensionTestUtils.loadExtension({
async background() {
let newtab = await browser.tabs.create({});
browser.test.assertTrue(
newtab.id !== browser.tabs.TAB_ID_NONE,
"New tab was created."
);
await browser.tabs.remove(newtab.id);
browser.test.sendMessage("complete");
},
});
function listener(msg) {
Assert.ok(!/may not load or link to moz-extension/.test(msg.message));
}
Services.console.registerListener(listener);
registerCleanupFunction(() => {
Services.console.unregisterListener(listener);
});
await opener.startup();
await opener.awaitMessage("complete");
await extension.awaitMessage("newtab-loaded");
await opener.unload();
await extension.unload();
});

Просмотреть файл

@ -3,171 +3,112 @@
"use strict";
add_task(async function testWindowCreate() {
let pageExt = ExtensionTestUtils.loadExtension({
manifest: {
applications: { gecko: { id: "page@mochitest" } },
protocol_handlers: [
{
protocol: "ext+foo",
name: "a foo protocol handler",
uriTemplate: "page.html?val=%s",
},
],
},
files: {
"page.html": `<html><head>
<meta charset="utf-8">
</head></html>`,
},
});
await pageExt.startup();
async function background(OTHER_PAGE) {
browser.test.log(`== using ${OTHER_PAGE}`);
const EXTENSION_URL = browser.runtime.getURL("test.html");
const EXT_PROTO = "ext+bar:foo";
const OTHER_PROTO = "ext+foo:bar";
let windows = new (class extends Map {
// eslint-disable-line new-parens
get(id) {
if (!this.has(id)) {
let window = {
tabs: new Map(),
};
window.promise = new Promise(resolve => {
window.resolvePromise = resolve;
});
this.set(id, window);
}
return super.get(id);
}
})();
browser.tabs.onUpdated.addListener((tabId, changed, tab) => {
if (changed.status == "complete" && tab.url !== "about:blank") {
let window = windows.get(tab.windowId);
window.tabs.set(tab.index, tab);
if (window.tabs.size === window.expectedTabs) {
browser.test.log("resolving a window load");
window.resolvePromise(window);
}
}
});
async function create(options) {
browser.test.log(`creating window for ${options.url}`);
let window = await browser.windows.create(options);
let win = windows.get(window.id);
win.id = window.id;
win.expectedTabs = Array.isArray(options.url) ? options.url.length : 1;
return win.promise;
}
function createFail(options) {
return browser.windows
.create(options)
.then(() => {
browser.test.fail(`window opened with ${options.url}`);
})
.catch(() => {
browser.test.succeed(`window could not open with ${options.url}`);
});
}
let TEST_SETS = [
{
name: "Single protocol URL in this extension",
url: EXT_PROTO,
expect: [`${EXTENSION_URL}?val=ext%2Bbar%3Afoo`],
},
{
name: "Single, relative URL",
url: "test.html",
expect: [EXTENSION_URL],
},
{
name: "Single, absolute, extension URL",
url: EXTENSION_URL,
expect: [EXTENSION_URL],
},
{
name: "Single, absolute, other extension URL",
url: OTHER_PAGE,
expect: [OTHER_PAGE],
},
{
name: "Single protocol URL in other extension",
url: OTHER_PROTO,
expect: [`${OTHER_PAGE}?val=ext%2Bfoo%3Abar`],
},
{
name: "multiple urls",
url: [EXT_PROTO, "test.html", EXTENSION_URL, OTHER_PROTO],
expect: [
`${EXTENSION_URL}?val=ext%2Bbar%3Afoo`,
EXTENSION_URL,
EXTENSION_URL,
`${OTHER_PAGE}?val=ext%2Bfoo%3Abar`,
],
},
];
try {
let windows = await Promise.all(
TEST_SETS.map(t => create({ url: t.url }))
);
TEST_SETS.forEach((test, i) => {
test.expect.forEach((expectUrl, x) => {
browser.test.assertEq(
expectUrl,
windows[i].tabs.get(x)?.url,
TEST_SETS[i].name
);
});
});
Promise.all(windows.map(({ id }) => browser.windows.remove(id))).then(
() => {
browser.test.notifyPass("window-create-url");
}
);
// Expecting to fail when opening windows with multiple urls that includes
// other extension urls.
await Promise.all([createFail({ url: [EXTENSION_URL, OTHER_PAGE] })]);
} catch (e) {
browser.test.fail(`${e} :: ${e.stack}`);
browser.test.notifyFail("window-create-url");
}
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
protocol_handlers: [
{
protocol: "ext+bar",
name: "a bar protocol handler",
uriTemplate: "test.html?val=%s",
},
],
},
background: `(${background})("moz-extension://${pageExt.uuid}/page.html")`,
background: async function() {
const EXTENSION_URL = browser.runtime.getURL("test.html");
const REMOTE_URL = browser.runtime.getURL("test.html");
let windows = new (class extends Map {
// eslint-disable-line new-parens
get(id) {
if (!this.has(id)) {
let window = {
tabs: new Map(),
};
window.promise = new Promise(resolve => {
window.resolvePromise = resolve;
});
this.set(id, window);
}
return super.get(id);
}
})();
browser.tabs.onUpdated.addListener((tabId, changed, tab) => {
if (changed.status == "complete" && tab.url !== "about:blank") {
let window = windows.get(tab.windowId);
window.tabs.set(tab.index, tab);
if (window.tabs.size === window.expectedTabs) {
window.resolvePromise(window);
}
}
});
async function create(options) {
let window = await browser.windows.create(options);
let win = windows.get(window.id);
win.id = window.id;
win.expectedTabs = Array.isArray(options.url) ? options.url.length : 1;
return win.promise;
}
try {
let windows = await Promise.all([
create({ url: REMOTE_URL }),
create({ url: "test.html" }),
create({ url: EXTENSION_URL }),
create({ url: [REMOTE_URL, "test.html", EXTENSION_URL] }),
]);
browser.test.assertEq(
REMOTE_URL,
windows[0].tabs.get(0).url,
"Single, absolute, remote URL"
);
browser.test.assertEq(
REMOTE_URL,
windows[1].tabs.get(0).url,
"Single, relative URL"
);
browser.test.assertEq(
REMOTE_URL,
windows[2].tabs.get(0).url,
"Single, absolute, extension URL"
);
browser.test.assertEq(
REMOTE_URL,
windows[3].tabs.get(0).url,
"url[0]: Absolute, remote URL"
);
browser.test.assertEq(
EXTENSION_URL,
windows[3].tabs.get(1).url,
"url[1]: Relative URL"
);
browser.test.assertEq(
EXTENSION_URL,
windows[3].tabs.get(2).url,
"url[2]: Absolute, extension URL"
);
Promise.all(windows.map(({ id }) => browser.windows.remove(id))).then(
() => {
browser.test.notifyPass("window-create-url");
}
);
} catch (e) {
browser.test.fail(`${e} :: ${e.stack}`);
browser.test.notifyFail("window-create-url");
}
},
files: {
"test.html": `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body></body></html>`,
"test.html": `<DOCTYPE html><html><head><meta charset="utf-8"></head></html>`,
},
});
await extension.startup();
await extension.awaitFinish("window-create-url");
await extension.unload();
await pageExt.unload();
});

Просмотреть файл

@ -18,7 +18,6 @@
#include "nsAboutProtocolUtils.h"
#include "ThirdPartyUtil.h"
#include "mozilla/ContentPrincipal.h"
#include "mozilla/ExtensionPolicyService.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/dom/BlobURLProtocolHandler.h"
#include "mozilla/dom/ChromeUtils.h"
@ -597,8 +596,6 @@ nsresult BasePrincipal::CheckMayLoadHelper(nsIURI* aURI,
}
}
// Web Accessible Resources in MV2 Extensions are marked with
// URI_FETCHABLE_BY_ANYONE
bool fetchableByAnyone;
rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FETCHABLE_BY_ANYONE,
&fetchableByAnyone);
@ -606,32 +603,14 @@ nsresult BasePrincipal::CheckMayLoadHelper(nsIURI* aURI,
return NS_OK;
}
// Get the principal uri for the last flag check or error.
nsCOMPtr<nsIURI> prinURI;
rv = GetURI(getter_AddRefs(prinURI));
if (!(NS_SUCCEEDED(rv) && prinURI)) {
return NS_ERROR_DOM_BAD_URI;
}
// 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);
nsCOMPtr<nsIURI> prinURI;
rv = GetURI(getter_AddRefs(prinURI));
if (NS_SUCCEEDED(rv) && prinURI) {
nsScriptSecurityManager::ReportError(
"CheckSameOriginError", prinURI, aURI,
mOriginAttributes.mPrivateBrowsingId > 0, aInnerWindowID);
}
}
return NS_ERROR_DOM_BAD_URI;

Просмотреть файл

@ -741,6 +741,21 @@ nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal,
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
bool targetURIIsLoadableBySubsumers = false;
rv = NS_URIChainHasFlags(targetBaseURI,
@ -814,7 +829,6 @@ nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal,
bool schemesMatch =
scheme.Equals(otherScheme, nsCaseInsensitiveCStringComparator);
bool isSamePage = false;
bool isExtensionMismatch = false;
// about: URIs are special snowflakes.
if (scheme.EqualsLiteral("about") && schemesMatch) {
nsAutoCString moduleName, otherModuleName;
@ -862,13 +876,6 @@ 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 {
bool equalExceptRef = false;
rv = currentURI->EqualsExceptRef(currentOtherURI, &equalExceptRef);
@ -877,12 +884,10 @@ nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal,
// 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
// from the same scheme, or this is two different extensions, check
// if the URI flags of the current target URI allow the current
// source URI to link to it.
// from the same scheme, check 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.
if (!schemesMatch || (denySameSchemeLinks && !isSamePage) ||
isExtensionMismatch) {
if (!schemesMatch || (denySameSchemeLinks && !isSamePage)) {
return CheckLoadURIFlags(
currentURI, currentOtherURI, sourceBaseURI, targetBaseURI, aFlags,
aPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0,
@ -931,8 +936,7 @@ nsresult nsScriptSecurityManager::CheckLoadURIFlags(
nsresult rv = aTargetBaseURI->GetScheme(targetScheme);
if (NS_FAILED(rv)) return rv;
// Check for system target URI. Regular (non web accessible) extension
// URIs will also have URI_DANGEROUS_TO_LOAD.
// Check for system target URI
rv = DenyAccessIfURIHasFlags(aTargetURI,
nsIProtocolHandler::URI_DANGEROUS_TO_LOAD);
if (NS_FAILED(rv)) {
@ -958,26 +962,6 @@ 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
bool targetURIIsUIResource = false;
rv = NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,

Просмотреть файл

@ -274,8 +274,7 @@ interface WebExtensionPolicy {
dictionary WebAccessibleResourceInit {
required sequence<MatchGlobOrString> resources;
MatchPatternSetOrStringSequence? matches = null;
sequence<DOMString>? extension_ids = null;
MatchPatternSetOrStringSequence matches;
};
dictionary WebExtensionInit {

Просмотреть файл

@ -1248,7 +1248,7 @@ nsresult nsContentSecurityManager::CheckAllowLoadInPrivilegedAboutContext(
}
/*
* Every protocol handler must set one of the six security flags
* Every protocol handler must set one of the five security flags
* defined in nsIProtocolHandler - if not - deny the load.
*/
nsresult nsContentSecurityManager::CheckChannelHasProtocolSecurityFlag(
@ -1273,9 +1273,6 @@ nsresult nsContentSecurityManager::CheckChannelHasProtocolSecurityFlag(
NS_ENSURE_SUCCESS(rv, rv);
uint32_t securityFlagsSet = 0;
if (flags & nsIProtocolHandler::WEBEXT_URI_WEB_ACCESSIBLE) {
securityFlagsSet += 1;
}
if (flags & nsIProtocolHandler::URI_LOADABLE_BY_ANYONE) {
securityFlagsSet += 1;
}

Просмотреть файл

@ -275,8 +275,7 @@ interface nsIProtocolHandler : nsISupports
/**
* 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
* headers. This is only used in MV2 Extensions and should not otherwise
* be used.
* headers.
*/
const unsigned long URI_FETCHABLE_BY_ANYONE = (1 << 18);
@ -313,7 +312,7 @@ interface nsIProtocolHandler : nsISupports
/**
* This is an extension web accessible uri that is loadable if checked
* against an allowlist using ExtensionPolicyService::SourceMayLoadExtensionURI.
* against an allow whitelist.
*/
const unsigned long WEBEXT_URI_WEB_ACCESSIBLE = (1 << 24);
};

Просмотреть файл

@ -416,16 +416,15 @@ nsresult ExtensionProtocolHandler::GetFlagsForURI(nsIURI* aURI,
URLInfo url(aURI);
if (auto* policy = EPS().GetByURL(url)) {
// In general a moz-extension URI is only loadable by chrome, but an
// allowlist subset are web-accessible (and cross-origin fetchable).
// The allowlist is checked using EPS.SourceMayLoadExtensionURI in
// BasePrincipal and nsScriptSecurityManager.
// In general a moz-extension URI is only loadable by chrome, but a
// whitelisted subset are web-accessible (and cross-origin fetchable). Check
// that whitelist. For Manifest V3 extensions, an additional whitelist
// for the source loading the url must be checked so we add the flag
// WEBEXT_URI_WEB_ACCESSIBLE, which is then checked in
// nsScriptSecurityManager.
if (policy->IsWebAccessiblePath(url.FilePath())) {
if (policy->ManifestVersion() < 3) {
flags |= URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE;
} else {
flags |= WEBEXT_URI_WEB_ACCESSIBLE;
}
flags |= URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE |
WEBEXT_URI_WEB_ACCESSIBLE;
} else {
flags |= URI_DANGEROUS_TO_LOAD;
}

Просмотреть файл

@ -304,18 +304,6 @@ const POSTPROCESSORS = {
context.logError(context.makeError(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

Просмотреть файл

@ -143,27 +143,12 @@ WebAccessibleResource::WebAccessibleResource(
return;
}
if (!aInit.mMatches.IsNull()) {
if (aInit.mMatches.WasPassed()) {
MatchPatternOptions options;
options.mRestrictSchemes = true;
mMatches = ParseMatches(aGlobal, aInit.mMatches.Value(), options,
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;
}
if (mExtensionIDs->Contains(nsGkAtoms::_asterisk)) {
return true;
}
WebExtensionPolicy* policy = EPS().GetByHost(aURI.Host());
return policy && mExtensionIDs->Contains(policy->Id());
}
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAccessibleResource)

Просмотреть файл

@ -19,7 +19,6 @@
#include "mozilla/WeakPtr.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsGkAtoms.h"
#include "nsISupports.h"
#include "nsWrapperCache.h"
@ -51,23 +50,16 @@ class WebAccessibleResource final : public nsISupports {
}
bool SourceMayAccessPath(const URLInfo& aURI, const nsAString& aPath) {
return mWebAccessiblePaths.Matches(aPath) &&
(IsHostMatch(aURI) || IsExtensionMatch(aURI));
return mWebAccessiblePaths.Matches(aPath) && mMatches &&
mMatches->Matches(aURI);
}
bool IsHostMatch(const URLInfo& aURI) {
return mMatches && mMatches->Matches(aURI);
}
bool IsExtensionMatch(const URLInfo& aURI);
protected:
virtual ~WebAccessibleResource() = default;
private:
MatchGlobSet mWebAccessiblePaths;
RefPtr<MatchPatternSet> mMatches;
RefPtr<AtomSet> mExtensionIDs;
};
class WebExtensionPolicy final : public nsISupports,
@ -125,11 +117,6 @@ class WebExtensionPolicy final : public nsISupports,
}
bool SourceMayAccessPath(const URLInfo& aURI, const nsAString& aPath) const {
if (aURI.Scheme() == nsGkAtoms::moz_extension &&
mHostname.Equals(aURI.Host())) {
// An extension can always access it's own paths.
return true;
}
if (mManifestVersion < 3) {
return IsWebAccessiblePath(aPath);
}

Просмотреть файл

@ -288,32 +288,16 @@
{
"min_manifest_version": 3,
"type": "array",
"postprocess": "webAccessibleMatching",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"resources": {
"type": "array",
"minItems": 1,
"items": { "type": "string" }
},
"matches": {
"optional": true,
"type": "array",
"minItems": 1,
"items": { "$ref": "MatchPattern" }
},
"extension_ids": {
"optional": true,
"type": "array",
"minItems": 1,
"items": {
"choices": [
{"$ref": "ExtensionID" },
{ "type": "string", "enum": ["*"] }
]
}
}
}
}

Просмотреть файл

@ -25,8 +25,6 @@ let image = atob(
const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0))
.buffer;
const ANDROID = navigator.userAgent.includes("Android");
async function testImageLoading(src, expectedAction) {
let imageLoadingPromise = new Promise((resolve, reject) => {
let cleanupListeners;
@ -375,100 +373,6 @@ add_task(async function test_web_accessible_resources_mixed_content() {
await extension.unload();
});
// test that MV2 extensions continue to open other MV2 extension pages
// when they are not listed in web_accessible_resources.
add_task(async function test_web_accessible_resources_extensions_MV2() {
function background() {
let newtab;
let win;
let expectUrl;
browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
if (tab.url != expectUrl || tab.status !== "complete") {
return;
}
browser.test.sendMessage("onUpdated", tab.url);
});
browser.test.onMessage.addListener(async (msg, url) => {
expectUrl = url;
if (msg == "create") {
newtab = await browser.tabs.create({ url });
browser.test.assertTrue(
newtab.id !== browser.tabs.TAB_ID_NONE,
"New tab was created."
);
} else if (msg == "update") {
await browser.tabs.update(newtab.id, { url });
} else if (msg == "remove") {
await browser.tabs.remove(newtab.id);
newtab = null;
browser.test.sendMessage("completed");
} else if (msg == "open-window") {
win = await browser.windows.create({ url });
} else if (msg == "close-window") {
await browser.windows.remove(win.id);
browser.test.sendMessage("completed");
win = null;
}
});
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
applications: { gecko: { id: "this-mv2@mochitest" } },
},
background,
files: {
"page.html": `<html><head>
<meta charset="utf-8">
</head></html>`,
},
});
async function testTabsAction(ext, action, url) {
ext.sendMessage(action, url);
let loaded = await ext.awaitMessage("onUpdated");
is(loaded, url, "extension url was loaded");
}
await extension.startup();
let extensionUrl = `moz-extension://${extension.uuid}/page.html`;
// Test opening its own pages
await testTabsAction(extension, "create", extensionUrl);
await testTabsAction(extension, "update", extensionUrl);
extension.sendMessage("remove");
await extension.awaitMessage("completed");
if (!ANDROID) {
await testTabsAction(extension, "open-window", extensionUrl);
extension.sendMessage("close-window");
await extension.awaitMessage("completed");
}
// Extension used to open the homepage in a new window.
let other = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs", "<all_urls>"],
},
background,
});
await other.startup();
// Test opening another extensions pages
await testTabsAction(other, "create", extensionUrl);
await testTabsAction(other, "update", extensionUrl);
other.sendMessage("remove");
await other.awaitMessage("completed");
if (!ANDROID) {
await testTabsAction(other, "open-window", extensionUrl);
other.sendMessage("close-window");
await other.awaitMessage("completed");
}
await extension.unload();
await other.unload();
});
</script>
</body>

Просмотреть файл

@ -132,11 +132,6 @@ add_task(async function test_WebExtensionPolicy() {
"Non-web-accessible path should not be web-accessible"
);
ok(
policy.sourceMayAccessPath(mozExtURI, "/bar.baz"),
"Web-accessible path should be web-accessible to self"
);
// Localization
equal(
@ -257,114 +252,6 @@ add_task(async function test_WebExtensionPolicy() {
}
});
add_task(async function test_WebExtensionPolicy_V3() {
const id = "foo@bar.baz";
const uuid = "ca9d3f23-125c-4b24-abfc-1ca2692b0610";
const id2 = "foo-2@bar.baz";
const uuid2 = "89383c45-7db4-4999-83f7-f4cc246372cd";
const id3 = "foo-3@bar.baz";
const uuid3 = "56652231-D7E2-45D1-BDBD-BD3BFF80927E";
const baseURL = "file:///foo/";
const mozExtURL = `moz-extension://${uuid}/`;
const mozExtURI = newURI(mozExtURL);
let policy = new WebExtensionPolicy({
id,
mozExtensionHostname: uuid,
baseURL,
manifestVersion: 3,
localizeCallback(str) {
return `<${str}>`;
},
allowedOrigins: new MatchPatternSet(["http://foo.bar/", "*://*.baz/"], {
ignorePath: true,
}),
permissions: ["<all_urls>"],
webAccessibleResources: [
{
resources: ["/foo/*", "/bar.baz"].map(glob => new MatchGlob(glob)),
matches: ["http://foo.bar/"],
extension_ids: [id3],
},
{
resources: ["/foo.bar.baz"].map(glob => new MatchGlob(glob)),
extension_ids: ["*"],
},
],
});
policy.active = true;
let policy2 = new WebExtensionPolicy({
id: id2,
mozExtensionHostname: uuid2,
baseURL,
localizeCallback() {},
allowedOrigins: new MatchPatternSet([]),
permissions: ["<all_urls>"],
});
policy2.active = true;
let policy3 = new WebExtensionPolicy({
id: id3,
mozExtensionHostname: uuid3,
baseURL,
localizeCallback() {},
allowedOrigins: new MatchPatternSet([]),
permissions: ["<all_urls>"],
});
policy3.active = true;
ok(
policy.isWebAccessiblePath("/bar.baz"),
"Web-accessible path should be web-accessible"
);
ok(
!policy.isWebAccessiblePath("/bar.baz/quux"),
"Non-web-accessible path should not be web-accessible"
);
// Extension can always access itself
ok(
policy.sourceMayAccessPath(mozExtURI, "/bar.baz"),
"Web-accessible path should be accessible to self"
);
ok(
policy.sourceMayAccessPath(mozExtURI, "/foo.bar.baz"),
"Web-accessible path should be accessible to self"
);
ok(
!policy.sourceMayAccessPath(newURI(`https://${uuid}/`), "/bar.baz"),
"Web-accessible path should not be accessible due to scheme mismatch"
);
let extURI = newURI(policy2.getURL(""));
ok(
!policy.sourceMayAccessPath(extURI, "/bar.baz"),
"Web-accessible path should not be accessible to other extension"
);
ok(
policy.sourceMayAccessPath(extURI, "/foo.bar.baz"),
"Web-accessible path should be accessible to other extension"
);
extURI = newURI(policy3.getURL(""));
ok(
!policy.sourceMayAccessPath(extURI, "/bar.baz"),
"Web-accessible path should be accessible to other extension"
);
ok(
policy.sourceMayAccessPath(extURI, "/foo.bar.baz"),
"Web-accessible path should be accessible to other extension"
);
policy.active = false;
policy2.active = false;
policy3.active = false;
});
add_task(async function test_WebExtensionPolicy_registerContentScripts() {
const id = "foo@bar.baz";
const uuid = "77a7b9d3-e73c-4cf3-97fb-1824868fe00f";

Просмотреть файл

@ -1,5 +1,4 @@
"use strict";
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
const server = createHttpServer({ hosts: ["example.com", "example.org"] });
@ -12,77 +11,10 @@ let image = atob(
const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0))
.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() {
async function contentScript() {
let canLoad = window.location.href.startsWith("http://example.com");
let urls = [
{
name: "iframe",
@ -204,178 +136,3 @@ add_task(async function test_web_accessible_resources() {
await page.close();
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();
});