mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 15:23:51 +00:00
Bug 1679688 - make host permissions grant access to privileged parts of the tabs API and fix Bug 1686443 r=robwu,geckoview-reviewers,agi
Differential Revision: https://phabricator.services.mozilla.com/D98471
This commit is contained in:
parent
f56289c230
commit
cdec8d59d8
@ -214,10 +214,10 @@ class TabsUpdateFilterEventManager extends EventManager {
|
||||
filter.properties = allProperties;
|
||||
}
|
||||
|
||||
function sanitize(extension, changeInfo) {
|
||||
function sanitize(tab, changeInfo) {
|
||||
let result = {};
|
||||
let nonempty = false;
|
||||
let hasTabs = extension.hasPermission("tabs");
|
||||
const hasTabs = tab.hasTabPermission;
|
||||
for (let prop in changeInfo) {
|
||||
if (hasTabs || !restricted.has(prop)) {
|
||||
nonempty = true;
|
||||
@ -264,7 +264,7 @@ class TabsUpdateFilterEventManager extends EventManager {
|
||||
return;
|
||||
}
|
||||
|
||||
let changeInfo = sanitize(extension, changed);
|
||||
let changeInfo = sanitize(tab, changed);
|
||||
if (changeInfo) {
|
||||
tabTracker.maybeWaitForTabOpen(nativeTab).then(() => {
|
||||
if (!nativeTab.parentNode) {
|
||||
@ -418,22 +418,6 @@ class TabsUpdateFilterEventManager extends EventManager {
|
||||
register,
|
||||
});
|
||||
}
|
||||
|
||||
addListener(callback, filter) {
|
||||
let { extension } = this.context;
|
||||
if (
|
||||
filter &&
|
||||
filter.urls &&
|
||||
!extension.hasPermission("tabs") &&
|
||||
!extension.hasPermission("activeTab")
|
||||
) {
|
||||
Cu.reportError(
|
||||
'Url filtering in tabs.onUpdated requires "tabs" or "activeTab" permission.'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return super.addListener(callback, filter);
|
||||
}
|
||||
}
|
||||
|
||||
function TabEventManager({ context, name, event, listener }) {
|
||||
@ -946,14 +930,6 @@ this.tabs = class extends ExtensionAPI {
|
||||
},
|
||||
|
||||
async query(queryInfo) {
|
||||
if (!extension.hasPermission("tabs")) {
|
||||
if (queryInfo.url !== null || queryInfo.title !== null) {
|
||||
return Promise.reject({
|
||||
message:
|
||||
'The "tabs" permission is required to use the query API with the "url" or "title" parameters',
|
||||
});
|
||||
}
|
||||
}
|
||||
return Array.from(tabManager.query(queryInfo, context), tab =>
|
||||
tab.convert()
|
||||
);
|
||||
|
@ -354,36 +354,6 @@ add_task(async function testQueryPermissions() {
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function testQueryWithoutURLOrTitlePermissions() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
permissions: [],
|
||||
},
|
||||
|
||||
async background() {
|
||||
await browser.test.assertRejects(
|
||||
browser.tabs.query({ url: "http://www.bbc.com/" }),
|
||||
'The "tabs" permission is required to use the query API with the "url" or "title" parameters',
|
||||
"Expected tabs.query with 'url' or 'title' to fail with permissions error message"
|
||||
);
|
||||
|
||||
await browser.test.assertRejects(
|
||||
browser.tabs.query({ title: "Foo" }),
|
||||
'The "tabs" permission is required to use the query API with the "url" or "title" parameters',
|
||||
"Expected tabs.query with 'url' or 'title' to fail with permissions error message"
|
||||
);
|
||||
|
||||
browser.test.notifyPass("testQueryWithoutURLOrTitlePermissions");
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
await extension.awaitFinish("testQueryWithoutURLOrTitlePermissions");
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function testInvalidUrl() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
|
@ -230,14 +230,12 @@ this.tabs = class extends ExtensionAPI {
|
||||
register: fire => {
|
||||
const restricted = ["url", "favIconUrl", "title"];
|
||||
|
||||
function sanitize(extension, changeInfo) {
|
||||
function sanitize(tab, changeInfo) {
|
||||
const result = {};
|
||||
let nonempty = false;
|
||||
const hasTabs = tab.hasTabPermission;
|
||||
for (const prop in changeInfo) {
|
||||
if (
|
||||
extension.hasPermission("tabs") ||
|
||||
!restricted.includes(prop)
|
||||
) {
|
||||
if (hasTabs || !restricted.includes(prop)) {
|
||||
nonempty = true;
|
||||
result[prop] = changeInfo[prop];
|
||||
}
|
||||
@ -246,7 +244,7 @@ this.tabs = class extends ExtensionAPI {
|
||||
}
|
||||
|
||||
const fireForTab = (tab, changed) => {
|
||||
const [needed, changeInfo] = sanitize(extension, changed);
|
||||
const [needed, changeInfo] = sanitize(tab, changed);
|
||||
if (needed) {
|
||||
fire.async(tab.id, changeInfo, tab.convert());
|
||||
}
|
||||
@ -256,7 +254,7 @@ this.tabs = class extends ExtensionAPI {
|
||||
const needed = [];
|
||||
let nativeTab;
|
||||
switch (event.type) {
|
||||
case "DOMTitleChanged": {
|
||||
case "pagetitlechanged": {
|
||||
const window = getBrowserWindow(event.target.ownerGlobal);
|
||||
nativeTab = window.tab;
|
||||
|
||||
@ -299,10 +297,10 @@ this.tabs = class extends ExtensionAPI {
|
||||
};
|
||||
|
||||
windowTracker.addListener("status", statusListener);
|
||||
windowTracker.addListener("DOMTitleChanged", listener);
|
||||
windowTracker.addListener("pagetitlechanged", listener);
|
||||
return () => {
|
||||
windowTracker.removeListener("status", statusListener);
|
||||
windowTracker.removeListener("DOMTitleChanged", listener);
|
||||
windowTracker.removeListener("pagetitlechanged", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
@ -455,14 +453,6 @@ this.tabs = class extends ExtensionAPI {
|
||||
},
|
||||
|
||||
async query(queryInfo) {
|
||||
if (!extension.hasPermission("tabs")) {
|
||||
if (queryInfo.url !== null || queryInfo.title !== null) {
|
||||
return Promise.reject({
|
||||
message:
|
||||
'The "tabs" permission is required to use the query API with the "url" or "title" parameters',
|
||||
});
|
||||
}
|
||||
}
|
||||
return Array.from(tabManager.query(queryInfo, context), tab =>
|
||||
tab.convert()
|
||||
);
|
||||
|
@ -134,6 +134,10 @@ class MockExtension {
|
||||
return this._extension.testMessage(...args);
|
||||
}
|
||||
|
||||
get tabManager() {
|
||||
return this._extension.tabManager;
|
||||
}
|
||||
|
||||
on(...args) {
|
||||
this._extensionPromise.then(extension => {
|
||||
extension.on(...args);
|
||||
|
@ -169,7 +169,11 @@ class TabBase {
|
||||
* @readonly
|
||||
*/
|
||||
get hasTabPermission() {
|
||||
return this.extension.hasPermission("tabs") || this.hasActiveTabPermission;
|
||||
return (
|
||||
this.extension.hasPermission("tabs") ||
|
||||
this.hasActiveTabPermission ||
|
||||
this.matchesHostPermission
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -190,6 +194,15 @@ class TabBase {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @property {boolean} matchesHostPermission
|
||||
* Returns true if the extensions host permissions match the current tab url.
|
||||
* @readonly
|
||||
*/
|
||||
get matchesHostPermission() {
|
||||
return this.extension.allowedOrigins.matches(this._url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @property {boolean} incognito
|
||||
* Returns true if this is a private browsing tab, false otherwise.
|
||||
@ -612,12 +625,17 @@ class TabBase {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (queryInfo.url && !queryInfo.url.matches(this.uri)) {
|
||||
return false;
|
||||
}
|
||||
if (queryInfo.title && !queryInfo.title.matches(this.title)) {
|
||||
return false;
|
||||
if (queryInfo.url || queryInfo.title) {
|
||||
if (!this.hasTabPermission) {
|
||||
return false;
|
||||
}
|
||||
// Using _url and _title instead of url/title to avoid repeated permission checks.
|
||||
if (queryInfo.url && !queryInfo.url.matches(this._url)) {
|
||||
return false;
|
||||
}
|
||||
if (queryInfo.title && !queryInfo.title.matches(this._title)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE HTML>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>The Title</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE HTML>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Another Title</title>
|
||||
<link href="file_image_great.png" rel="icon" type="image/png" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -65,6 +65,8 @@ support-files =
|
||||
!/toolkit/components/passwordmgr/test/authenticate.sjs
|
||||
file_redirect_data_uri.html
|
||||
file_redirect_cors_bypass.html
|
||||
file_tabs_permission_page1.html
|
||||
file_tabs_permission_page2.html
|
||||
prefs =
|
||||
security.mixed_content.upgrade_display_content=false
|
||||
browser.chrome.guess_favicon=true
|
||||
@ -152,7 +154,7 @@ skip-if = xorigin # JavaScript Error: "SecurityError: Permission denied to acces
|
||||
scheme=https
|
||||
[test_ext_storage_smoke_test.html]
|
||||
[test_ext_streamfilter_multiple.html]
|
||||
skip-if =
|
||||
skip-if =
|
||||
!debug # Bug 1628642
|
||||
os == 'linux' # Bug 1628642
|
||||
[test_ext_streamfilter_processswitch.html]
|
||||
@ -160,6 +162,7 @@ skip-if =
|
||||
skip-if = os == 'android' || verify # bug 1489771
|
||||
[test_ext_tabs_captureTab.html]
|
||||
[test_ext_tabs_query_popup.html]
|
||||
[test_ext_tabs_permissions.html]
|
||||
[test_ext_tabs_sendMessage.html]
|
||||
[test_ext_test.html]
|
||||
[test_ext_unlimitedStorage.html]
|
||||
|
@ -0,0 +1,780 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tabs permissions test</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
|
||||
<script type="text/javascript" src="head.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
const URL1 =
|
||||
"http://www.example.com/tests/toolkit/components/extensions/test/mochitest/file_tabs_permission_page1.html";
|
||||
const URL2 =
|
||||
"http://example.net/tests/toolkit/components/extensions/test/mochitest/file_tabs_permission_page2.html";
|
||||
|
||||
const helperExtensionDef = {
|
||||
manifest: {
|
||||
applications: {
|
||||
gecko: {
|
||||
id: "helper@tests.mozilla.org",
|
||||
},
|
||||
},
|
||||
permissions: ["webNavigation", "<all_urls>"],
|
||||
},
|
||||
|
||||
useAddonManager: "permanent",
|
||||
|
||||
async background() {
|
||||
browser.test.onMessage.addListener(async message => {
|
||||
switch (message.subject) {
|
||||
case "createTab": {
|
||||
const tabLoaded = new Promise(resolve => {
|
||||
browser.webNavigation.onCompleted.addListener(function listener(
|
||||
details
|
||||
) {
|
||||
if (details.url === message.data.url) {
|
||||
browser.webNavigation.onCompleted.removeListener(listener);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const tab = await browser.tabs.create({ url: message.data.url });
|
||||
await tabLoaded;
|
||||
browser.test.sendMessage("tabCreated", tab.id);
|
||||
break;
|
||||
}
|
||||
|
||||
case "changeTabURL": {
|
||||
const tabLoaded = new Promise(resolve => {
|
||||
browser.webNavigation.onCompleted.addListener(function listener(
|
||||
details
|
||||
) {
|
||||
if (details.url === message.data.url) {
|
||||
browser.webNavigation.onCompleted.removeListener(listener);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await browser.tabs.update(message.data.tabId, {
|
||||
url: message.data.url,
|
||||
});
|
||||
await tabLoaded;
|
||||
browser.test.sendMessage("tabURLChanged", message.data.tabId);
|
||||
break;
|
||||
}
|
||||
|
||||
case "changeTabHashAndTitle": {
|
||||
const tabChanged = new Promise(resolve => {
|
||||
let hasURLChangeInfo = false,
|
||||
hasTitleChangeInfo = false;
|
||||
browser.tabs.onUpdated.addListener(function listener(
|
||||
tabId,
|
||||
changeInfo,
|
||||
tab
|
||||
) {
|
||||
if (changeInfo.url?.endsWith(message.data.urlHash)) {
|
||||
hasURLChangeInfo = true;
|
||||
}
|
||||
if (changeInfo.title === message.data.title) {
|
||||
hasTitleChangeInfo = true;
|
||||
}
|
||||
if (hasURLChangeInfo && hasTitleChangeInfo) {
|
||||
browser.tabs.onUpdated.removeListener(listener);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await browser.tabs.executeScript(message.data.tabId, {
|
||||
code: `
|
||||
document.location.hash = ${JSON.stringify(message.data.urlHash)};
|
||||
document.title = ${JSON.stringify(message.data.title)};
|
||||
`,
|
||||
});
|
||||
await tabChanged;
|
||||
browser.test.sendMessage("tabHashAndTitleChanged");
|
||||
break;
|
||||
}
|
||||
|
||||
case "removeTab": {
|
||||
await browser.tabs.remove(message.data.tabId);
|
||||
browser.test.sendMessage("tabRemoved");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
browser.test.fail(`Received unexpected message: ${message}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* Test tabs.query function
|
||||
* Check if the correct tabs are queried by url or title based on the granted permissions
|
||||
*/
|
||||
async function test_query(testCases, permissions) {
|
||||
const helperExtension = ExtensionTestUtils.loadExtension(helperExtensionDef);
|
||||
|
||||
const extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
applications: {
|
||||
gecko: {
|
||||
id: "permissions@tests.mozilla.org",
|
||||
},
|
||||
},
|
||||
permissions,
|
||||
},
|
||||
|
||||
useAddonManager: "permanent",
|
||||
|
||||
async background() {
|
||||
// wait for start message
|
||||
const [testCases, tabIdFromURL1, tabIdFromURL2] = await new Promise(
|
||||
resolve => {
|
||||
browser.test.onMessage.addListener(message => resolve(message));
|
||||
}
|
||||
);
|
||||
|
||||
for (const testCase of testCases) {
|
||||
const query = testCase.query;
|
||||
const matchingTabs = testCase.matchingTabs;
|
||||
|
||||
let tabQuery = await browser.tabs.query(query);
|
||||
// ignore other tabs in the window
|
||||
tabQuery = tabQuery.filter(tab => {
|
||||
return tab.id === tabIdFromURL1 || tab.id === tabIdFromURL2;
|
||||
});
|
||||
|
||||
browser.test.assertEq(matchingTabs, tabQuery.length, `Tabs queried`);
|
||||
}
|
||||
// send end message
|
||||
browser.test.notifyPass("tabs.query");
|
||||
},
|
||||
});
|
||||
|
||||
await helperExtension.startup();
|
||||
await extension.startup();
|
||||
|
||||
helperExtension.sendMessage({
|
||||
subject: "createTab",
|
||||
data: { url: URL1 },
|
||||
});
|
||||
const tabIdFromURL1 = await helperExtension.awaitMessage("tabCreated");
|
||||
|
||||
helperExtension.sendMessage({
|
||||
subject: "createTab",
|
||||
data: { url: URL2 },
|
||||
});
|
||||
const tabIdFromURL2 = await helperExtension.awaitMessage("tabCreated");
|
||||
|
||||
if (permissions.includes("activeTab")) {
|
||||
extension.grantActiveTab(tabIdFromURL2);
|
||||
}
|
||||
|
||||
extension.sendMessage([testCases, tabIdFromURL1, tabIdFromURL2]);
|
||||
await extension.awaitFinish("tabs.query");
|
||||
|
||||
helperExtension.sendMessage({
|
||||
subject: "removeTab",
|
||||
data: { tabId: tabIdFromURL1 },
|
||||
});
|
||||
await helperExtension.awaitMessage("tabRemoved");
|
||||
|
||||
helperExtension.sendMessage({
|
||||
subject: "removeTab",
|
||||
data: { tabId: tabIdFromURL2 },
|
||||
});
|
||||
await helperExtension.awaitMessage("tabRemoved");
|
||||
|
||||
await extension.unload();
|
||||
await helperExtension.unload();
|
||||
}
|
||||
|
||||
// http://www.example.com host permission
|
||||
add_task(function query_with_host_permission_url1() {
|
||||
return test_query(
|
||||
[
|
||||
{
|
||||
query: { url: "*://www.example.com/*" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: { url: "<all_urls>" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: { url: ["*://www.example.com/*", "*://example.net/*"] },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: { title: "The Title" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: { title: "Another Title" },
|
||||
matchingTabs: 0,
|
||||
},
|
||||
{
|
||||
query: {},
|
||||
matchingTabs: 2,
|
||||
},
|
||||
],
|
||||
["*://www.example.com/*"]
|
||||
);
|
||||
});
|
||||
|
||||
// http://example.net host permission
|
||||
add_task(function query_with_host_permission_url2() {
|
||||
return test_query(
|
||||
[
|
||||
{
|
||||
query: { url: "*://www.example.com/*" },
|
||||
matchingTabs: 0,
|
||||
},
|
||||
{
|
||||
query: { url: "<all_urls>" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: { url: ["*://www.example.com/*", "*://example.net/*"] },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: { title: "The Title" },
|
||||
matchingTabs: 0,
|
||||
},
|
||||
{
|
||||
query: { title: "Another Title" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: {},
|
||||
matchingTabs: 2,
|
||||
},
|
||||
],
|
||||
["*://example.net/*"]
|
||||
);
|
||||
});
|
||||
|
||||
// <all_urls> permission
|
||||
add_task(function query_with_host_permission_all_urls() {
|
||||
return test_query(
|
||||
[
|
||||
{
|
||||
query: { url: "*://www.example.com/*" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: { url: "<all_urls>" },
|
||||
matchingTabs: 2,
|
||||
},
|
||||
{
|
||||
query: { url: ["*://www.example.com/*", "*://example.net/*"] },
|
||||
matchingTabs: 2,
|
||||
},
|
||||
{
|
||||
query: { title: "The Title" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: { title: "Another Title" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: {},
|
||||
matchingTabs: 2,
|
||||
},
|
||||
],
|
||||
["<all_urls>"]
|
||||
);
|
||||
});
|
||||
|
||||
// tabs permission
|
||||
add_task(function query_with_tabs_permission() {
|
||||
return test_query(
|
||||
[
|
||||
{
|
||||
query: { url: "*://www.example.com/*" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: { url: "<all_urls>" },
|
||||
matchingTabs: 2,
|
||||
},
|
||||
{
|
||||
query: { url: ["*://www.example.com/*", "*://example.net/*"] },
|
||||
matchingTabs: 2,
|
||||
},
|
||||
{
|
||||
query: { title: "The Title" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: { title: "Another Title" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: {},
|
||||
matchingTabs: 2,
|
||||
},
|
||||
],
|
||||
["tabs"]
|
||||
);
|
||||
});
|
||||
|
||||
// activeTab permission
|
||||
add_task(function query_with_activeTab_permission() {
|
||||
return test_query(
|
||||
[
|
||||
{
|
||||
query: { url: "*://www.example.com/*" },
|
||||
matchingTabs: 0,
|
||||
},
|
||||
{
|
||||
query: { url: "<all_urls>" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: { url: ["*://www.example.com/*", "*://example.net/*"] },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: { title: "The Title" },
|
||||
matchingTabs: 0,
|
||||
},
|
||||
{
|
||||
query: { title: "Another Title" },
|
||||
matchingTabs: 1,
|
||||
},
|
||||
{
|
||||
query: {},
|
||||
matchingTabs: 2,
|
||||
},
|
||||
],
|
||||
["activeTab"]
|
||||
);
|
||||
});
|
||||
// no permission
|
||||
add_task(function query_without_permission() {
|
||||
return test_query(
|
||||
[
|
||||
{
|
||||
query: { url: "*://www.example.com/*" },
|
||||
matchingTabs: 0,
|
||||
},
|
||||
{
|
||||
query: { url: "<all_urls>" },
|
||||
matchingTabs: 0,
|
||||
},
|
||||
{
|
||||
query: { url: ["*://www.example.com/*", "*://example.net/*"] },
|
||||
matchingTabs: 0,
|
||||
},
|
||||
{
|
||||
query: { title: "The Title" },
|
||||
matchingTabs: 0,
|
||||
},
|
||||
{
|
||||
query: { title: "Another Title" },
|
||||
matchingTabs: 0,
|
||||
},
|
||||
{
|
||||
query: {},
|
||||
matchingTabs: 2,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
/*
|
||||
* Test tabs.onUpdate and tabs.get function
|
||||
* Check if the changeInfo or tab object contains the restricted properties
|
||||
* url and title only when the right permissions are granted
|
||||
* The tab is updated without causing navigation in order to also test activeTab permission
|
||||
*/
|
||||
async function test_restricted_properties(
|
||||
permissions,
|
||||
hasRestrictedProperties
|
||||
) {
|
||||
const helperExtension = ExtensionTestUtils.loadExtension(helperExtensionDef);
|
||||
|
||||
const extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
applications: {
|
||||
gecko: {
|
||||
id: "permissions@tests.mozilla.org",
|
||||
},
|
||||
},
|
||||
permissions,
|
||||
},
|
||||
|
||||
useAddonManager: "permanent",
|
||||
|
||||
async background() {
|
||||
// wait for test start signal and data
|
||||
const [
|
||||
hasRestrictedProperties,
|
||||
tabId,
|
||||
urlHash,
|
||||
title,
|
||||
] = await new Promise(resolve => {
|
||||
browser.test.onMessage.addListener(message => {
|
||||
resolve(message);
|
||||
});
|
||||
});
|
||||
|
||||
let hasURLChangeInfo = false,
|
||||
hasTitleChangeInfo = false;
|
||||
function onUpdateListener(tabId, changeInfo, tab) {
|
||||
if (changeInfo.url?.endsWith(urlHash)) {
|
||||
hasURLChangeInfo = true;
|
||||
}
|
||||
if (changeInfo.title === title) {
|
||||
hasTitleChangeInfo = true;
|
||||
}
|
||||
}
|
||||
browser.tabs.onUpdated.addListener(onUpdateListener);
|
||||
|
||||
// wait for test evaluation signal and data
|
||||
await new Promise(resolve => {
|
||||
browser.test.onMessage.addListener(message => {
|
||||
if (message === "collectTestResults") {
|
||||
resolve(message);
|
||||
}
|
||||
});
|
||||
browser.test.sendMessage("waitingForTabPropertyChanges");
|
||||
});
|
||||
|
||||
// check onUpdate changeInfo
|
||||
browser.test.assertEq(
|
||||
hasRestrictedProperties,
|
||||
hasURLChangeInfo,
|
||||
`Has changeInfo property "url"`
|
||||
);
|
||||
browser.test.assertEq(
|
||||
hasRestrictedProperties,
|
||||
hasTitleChangeInfo,
|
||||
`Has changeInfo property "title"`
|
||||
);
|
||||
// check tab properties
|
||||
const tabGet = await browser.tabs.get(tabId);
|
||||
browser.test.assertEq(
|
||||
hasRestrictedProperties,
|
||||
!!tabGet.url?.endsWith(urlHash),
|
||||
`Has tab property "url"`
|
||||
);
|
||||
browser.test.assertEq(
|
||||
hasRestrictedProperties,
|
||||
tabGet.title === title,
|
||||
`Has tab property "title"`
|
||||
);
|
||||
// send end message
|
||||
browser.test.notifyPass("tabs.restricted_properties");
|
||||
},
|
||||
});
|
||||
|
||||
const urlHash = "#ChangedURL";
|
||||
const title = "Changed Title";
|
||||
|
||||
await helperExtension.startup();
|
||||
await extension.startup();
|
||||
|
||||
helperExtension.sendMessage({
|
||||
subject: "createTab",
|
||||
data: { url: URL1 },
|
||||
});
|
||||
const tabId = await helperExtension.awaitMessage("tabCreated");
|
||||
|
||||
if (permissions.includes("activeTab")) {
|
||||
extension.grantActiveTab(tabId);
|
||||
}
|
||||
// send test start signal and data
|
||||
extension.sendMessage([hasRestrictedProperties, tabId, urlHash, title]);
|
||||
await extension.awaitMessage("waitingForTabPropertyChanges");
|
||||
|
||||
helperExtension.sendMessage({
|
||||
subject: "changeTabHashAndTitle",
|
||||
data: {
|
||||
tabId,
|
||||
urlHash,
|
||||
title,
|
||||
},
|
||||
});
|
||||
await helperExtension.awaitMessage("tabHashAndTitleChanged");
|
||||
|
||||
// send end signal and evaluate results
|
||||
extension.sendMessage("collectTestResults");
|
||||
await extension.awaitFinish("tabs.restricted_properties");
|
||||
|
||||
helperExtension.sendMessage({
|
||||
subject: "removeTab",
|
||||
data: { tabId },
|
||||
});
|
||||
await helperExtension.awaitMessage("tabRemoved");
|
||||
|
||||
await extension.unload();
|
||||
await helperExtension.unload();
|
||||
}
|
||||
|
||||
// http://www.example.com host permission
|
||||
add_task(function has_restricted_properties_with_host_permission_url1() {
|
||||
return test_restricted_properties(["*://www.example.com/*"], true);
|
||||
});
|
||||
// http://example.net host permission
|
||||
add_task(function has_restricted_properties_with_host_permission_url2() {
|
||||
return test_restricted_properties(["*://example.net/*"], false);
|
||||
});
|
||||
// <all_urls> permission
|
||||
add_task(function has_restricted_properties_with_host_permission_all_urls() {
|
||||
return test_restricted_properties(["<all_urls>"], true);
|
||||
});
|
||||
// tabs permission
|
||||
add_task(function has_restricted_properties_with_tabs_permission() {
|
||||
return test_restricted_properties(["tabs"], true);
|
||||
});
|
||||
// activeTab permission
|
||||
add_task(function has_restricted_properties_with_activeTab_permission() {
|
||||
return test_restricted_properties(["activeTab"], true);
|
||||
}).skip(); // TODO bug 1686080: support changeInfo.url with activeTab
|
||||
// no permission
|
||||
add_task(function has_restricted_properties_without_permission() {
|
||||
return test_restricted_properties([], false);
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Test tabs.onUpdate filter functionality
|
||||
* Check if the restricted filter properties only work if the
|
||||
* right permissions are granted
|
||||
*/
|
||||
async function test_onUpdateFilter(testCases, permissions) {
|
||||
// Filters for onUpdated are not supported on Android.
|
||||
if (AppConstants.platform === "android") {
|
||||
return;
|
||||
}
|
||||
|
||||
const helperExtension = ExtensionTestUtils.loadExtension(helperExtensionDef);
|
||||
|
||||
const extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
applications: {
|
||||
gecko: {
|
||||
id: "permissions@tests.mozilla.org",
|
||||
},
|
||||
},
|
||||
permissions,
|
||||
},
|
||||
|
||||
useAddonManager: "permanent",
|
||||
|
||||
async background() {
|
||||
let listenerGotCalled = false;
|
||||
function onUpdateListener(tabId, changeInfo, tab) {
|
||||
listenerGotCalled = true;
|
||||
}
|
||||
|
||||
browser.test.onMessage.addListener(async message => {
|
||||
switch (message.subject) {
|
||||
case "setup": {
|
||||
browser.tabs.onUpdated.addListener(
|
||||
onUpdateListener,
|
||||
message.data.filter
|
||||
);
|
||||
browser.test.sendMessage("done");
|
||||
break;
|
||||
}
|
||||
|
||||
case "collectTestResults": {
|
||||
browser.test.assertEq(
|
||||
message.data.expectEvent,
|
||||
listenerGotCalled,
|
||||
`Update listener called`
|
||||
);
|
||||
browser.tabs.onUpdated.removeListener(onUpdateListener);
|
||||
listenerGotCalled = false;
|
||||
browser.test.sendMessage("done");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
browser.test.fail(`Received unexpected message: ${message}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await helperExtension.startup();
|
||||
await extension.startup();
|
||||
|
||||
for (const testCase of testCases) {
|
||||
helperExtension.sendMessage({
|
||||
subject: "createTab",
|
||||
data: { url: URL1 },
|
||||
});
|
||||
const tabId = await helperExtension.awaitMessage("tabCreated");
|
||||
|
||||
extension.sendMessage({
|
||||
subject: "setup",
|
||||
data: {
|
||||
filter: testCase.filter,
|
||||
},
|
||||
});
|
||||
await extension.awaitMessage("done");
|
||||
|
||||
helperExtension.sendMessage({
|
||||
subject: "changeTabURL",
|
||||
data: {
|
||||
tabId,
|
||||
url: URL2,
|
||||
},
|
||||
});
|
||||
await helperExtension.awaitMessage("tabURLChanged");
|
||||
|
||||
extension.sendMessage({
|
||||
subject: "collectTestResults",
|
||||
data: {
|
||||
expectEvent: testCase.expectEvent,
|
||||
},
|
||||
});
|
||||
await extension.awaitMessage("done");
|
||||
|
||||
helperExtension.sendMessage({
|
||||
subject: "removeTab",
|
||||
data: { tabId },
|
||||
});
|
||||
await helperExtension.awaitMessage("tabRemoved");
|
||||
}
|
||||
|
||||
await extension.unload();
|
||||
await helperExtension.unload();
|
||||
}
|
||||
|
||||
// http://mozilla.org host permission
|
||||
add_task(function onUpdateFilter_with_host_permission_url3() {
|
||||
return test_onUpdateFilter(
|
||||
[
|
||||
{
|
||||
filter: { urls: ["*://mozilla.org/*"] },
|
||||
expectEvent: false,
|
||||
},
|
||||
{
|
||||
filter: { urls: ["<all_urls>"] },
|
||||
expectEvent: false,
|
||||
},
|
||||
{
|
||||
filter: { urls: ["*://mozilla.org/*", "*://example.net/*"] },
|
||||
expectEvent: false,
|
||||
},
|
||||
{
|
||||
filter: { properties: ["title"] },
|
||||
expectEvent: false,
|
||||
},
|
||||
{
|
||||
filter: {},
|
||||
expectEvent: true,
|
||||
},
|
||||
],
|
||||
["*://mozilla.org/*"]
|
||||
);
|
||||
});
|
||||
|
||||
// http://example.net host permission
|
||||
add_task(function onUpdateFilter_with_host_permission_url2() {
|
||||
return test_onUpdateFilter(
|
||||
[
|
||||
{
|
||||
filter: { urls: ["*://mozilla.org/*"] },
|
||||
expectEvent: false,
|
||||
},
|
||||
{
|
||||
filter: { urls: ["<all_urls>"] },
|
||||
expectEvent: true,
|
||||
},
|
||||
{
|
||||
filter: { urls: ["*://mozilla.org/*", "*://example.net/*"] },
|
||||
expectEvent: true,
|
||||
},
|
||||
{
|
||||
filter: { properties: ["title"] },
|
||||
expectEvent: true,
|
||||
},
|
||||
{
|
||||
filter: {},
|
||||
expectEvent: true,
|
||||
},
|
||||
],
|
||||
["*://example.net/*"]
|
||||
);
|
||||
});
|
||||
|
||||
// <all_urls> permission
|
||||
add_task(function onUpdateFilter_with_host_permission_all_urls() {
|
||||
return test_onUpdateFilter(
|
||||
[
|
||||
{
|
||||
filter: { urls: ["*://mozilla.org/*"] },
|
||||
expectEvent: false,
|
||||
},
|
||||
{
|
||||
filter: { urls: ["<all_urls>"] },
|
||||
expectEvent: true,
|
||||
},
|
||||
{
|
||||
filter: { urls: ["*://mozilla.org/*", "*://example.net/*"] },
|
||||
expectEvent: true,
|
||||
},
|
||||
{
|
||||
filter: { properties: ["title"] },
|
||||
expectEvent: true,
|
||||
},
|
||||
{
|
||||
filter: {},
|
||||
expectEvent: true,
|
||||
},
|
||||
],
|
||||
["<all_urls>"]
|
||||
);
|
||||
});
|
||||
|
||||
// tabs permission
|
||||
add_task(function onUpdateFilter_with_tabs_permission() {
|
||||
return test_onUpdateFilter(
|
||||
[
|
||||
{
|
||||
filter: { urls: ["*://mozilla.org/*"] },
|
||||
expectEvent: false,
|
||||
},
|
||||
{
|
||||
filter: { urls: ["<all_urls>"] },
|
||||
expectEvent: true,
|
||||
},
|
||||
{
|
||||
filter: { urls: ["*://mozilla.org/*", "*://example.net/*"] },
|
||||
expectEvent: true,
|
||||
},
|
||||
{
|
||||
filter: { properties: ["title"] },
|
||||
expectEvent: true,
|
||||
},
|
||||
{
|
||||
filter: {},
|
||||
expectEvent: true,
|
||||
},
|
||||
],
|
||||
["tabs"]
|
||||
);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user