Bug 1711168 allow extension pages to be loaded as top level tabs by other extensions r=rpl
Ensure extensions can manage tabs and sessions that include tabs from other extensions. The parent patch to this introduces cross-extension access validation. However that breaks this specific use case that we need to continue supporting. This patch modifies three extension apis, tab.create/update and windows.create to allow the creation of extension tabs which cannot be otherwise accessed. Differential Revision: https://phabricator.services.mozilla.com/D151766
This commit is contained in:
@@ -611,6 +611,24 @@ 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({
|
||||
@@ -706,9 +724,11 @@ this.tabs = class extends ExtensionAPIPersistent {
|
||||
}
|
||||
}).then(window => {
|
||||
let url;
|
||||
let principal = context.principal;
|
||||
|
||||
let options = {};
|
||||
let options = {
|
||||
allowInheritPrincipal: true,
|
||||
triggeringPrincipal: context.principal,
|
||||
};
|
||||
if (createProperties.cookieStoreId) {
|
||||
// May throw if validation fails.
|
||||
options.userContextId = getUserContextIdForCookieStoreId(
|
||||
@@ -721,7 +741,10 @@ this.tabs = class extends ExtensionAPIPersistent {
|
||||
if (createProperties.url !== null) {
|
||||
url = context.uri.resolve(createProperties.url);
|
||||
|
||||
if (!context.checkLoadURL(url, { dontReportErrors: true })) {
|
||||
if (
|
||||
!url.startsWith("moz-extension://") &&
|
||||
!context.checkLoadURL(url, { dontReportErrors: true })
|
||||
) {
|
||||
return Promise.reject({ message: `Illegal URL: ${url}` });
|
||||
}
|
||||
|
||||
@@ -731,30 +754,10 @@ 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:");
|
||||
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;
|
||||
// Handle moz-ext separately from the discardable flag to retain prior behavior.
|
||||
if (!discardable || url?.startsWith("moz-extension://")) {
|
||||
setContentTriggeringPrincipal(url, window.gBrowser, options);
|
||||
}
|
||||
|
||||
tabListener.initTabReady();
|
||||
@@ -814,7 +817,6 @@ this.tabs = class extends ExtensionAPIPersistent {
|
||||
});
|
||||
}
|
||||
|
||||
options.triggeringPrincipal = principal;
|
||||
let nativeTab = window.gBrowser.addTab(url, options);
|
||||
|
||||
if (active) {
|
||||
@@ -888,10 +890,6 @@ 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
|
||||
@@ -899,6 +897,15 @@ 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,6 +87,26 @@ 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({
|
||||
@@ -182,7 +202,12 @@ this.windows = class extends ExtensionAPIPersistent {
|
||||
Ci.nsIMutableArray
|
||||
);
|
||||
|
||||
let principal = context.principal;
|
||||
// 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;
|
||||
|
||||
if (createData.tabId !== null) {
|
||||
if (createData.url !== null) {
|
||||
throw new ExtensionError(
|
||||
@@ -231,12 +256,24 @@ this.windows = class extends ExtensionAPIPersistent {
|
||||
let array = Cc["@mozilla.org/array;1"].createInstance(
|
||||
Ci.nsIMutableArray
|
||||
);
|
||||
for (let url of createData.url) {
|
||||
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}` });
|
||||
}
|
||||
array.appendElement(mkstr(url));
|
||||
}
|
||||
args.appendElement(array);
|
||||
} else {
|
||||
args.appendElement(mkstr(createData.url));
|
||||
let url = context.uri.resolve(createData.url);
|
||||
args.appendElement(mkstr(url));
|
||||
principal = getTriggeringPrincipalForUrl(url);
|
||||
if (allowScriptsToClose === null) {
|
||||
allowScriptsToClose = url.startsWith("moz-extension://");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let url =
|
||||
@@ -245,15 +282,7 @@ this.windows = class extends ExtensionAPIPersistent {
|
||||
? "about:privatebrowsing"
|
||||
: HomePage.get().split("|", 1)[0];
|
||||
args.appendElement(mkstr(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();
|
||||
}
|
||||
principal = getTriggeringPrincipalForUrl(url);
|
||||
}
|
||||
|
||||
args.appendElement(null); // extraOptions
|
||||
@@ -271,6 +300,7 @@ this.windows = class extends ExtensionAPIPersistent {
|
||||
createData.cookieStoreId,
|
||||
createData.incognito
|
||||
);
|
||||
|
||||
args.appendElement(userContextIdSupports); // userContextId
|
||||
} else {
|
||||
args.appendElement(null);
|
||||
@@ -316,12 +346,6 @@ 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", "format": "relativeUrl" },
|
||||
{ "type": "string" },
|
||||
{
|
||||
"type": "array",
|
||||
"items": { "type": "string", "format": "relativeUrl" }
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(4);
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/XPCOMUtils.sys.mjs"
|
||||
);
|
||||
@@ -717,3 +719,133 @@ 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,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(4);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"ExtensionSettingsStore",
|
||||
@@ -713,3 +715,72 @@ 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,112 +3,171 @@
|
||||
"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: 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");
|
||||
}
|
||||
},
|
||||
background: `(${background})("moz-extension://${pageExt.uuid}/page.html")`,
|
||||
|
||||
files: {
|
||||
"test.html": `<DOCTYPE html><html><head><meta charset="utf-8"></head></html>`,
|
||||
"test.html": `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body></body></html>`,
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("window-create-url");
|
||||
await extension.unload();
|
||||
await pageExt.unload();
|
||||
});
|
||||
|
||||
@@ -25,6 +25,8 @@ 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;
|
||||
@@ -373,6 +375,100 @@ 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>
|
||||
|
||||
Reference in New Issue
Block a user