Bug 1609920 - part 4: Add new mochitests and marionette test related to the new background service worker prefs. r=marionette-reviewers,asuth,whimboo,mixedpuppy

Differential Revision: https://phabricator.services.mozilla.com/D73326
This commit is contained in:
Luca Greco 2020-06-30 16:03:02 +00:00
Родитель 0578891772
Коммит ea4f1ae5e7
11 изменённых файлов: 644 добавлений и 0 удалений

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

@ -18,3 +18,6 @@
# cleardata tests
[include:../../../../../toolkit/components/cleardata/tests/marionette/manifest.ini]
# webextensions tests
[include:../../../../../toolkit/components/extensions/test/marionette/manifest.ini]

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

@ -99,6 +99,7 @@ FINAL_LIBRARY = 'xul'
JAR_MANIFESTS += ['jar.mn']
BROWSER_CHROME_MANIFESTS += [
'test/browser/browser-serviceworker.ini',
'test/browser/browser.ini',
]

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

@ -0,0 +1,9 @@
[DEFAULT]
support-files =
head_serviceworker.js
data/**
prefs =
extensions.backgroundServiceWorker.enabled=true
[browser_ext_background_serviceworker.js]

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

@ -3,6 +3,7 @@ support-files =
head.js
data/**
[browser_ext_background_serviceworker_pref_disabled.js]
[browser_ext_downloads_referrer.js]
[browser_ext_management_themes.js]
skip-if = verify

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

@ -0,0 +1,292 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
/* globals getBackgroundServiceWorkerRegistration, waitForServiceWorkerTerminated */
Services.scriptloader.loadSubScript(
new URL("head_serviceworker.js", gTestPath).href,
this
);
add_task(assert_background_serviceworker_pref_enabled);
add_task(async function test_serviceWorker_register_guarded_by_pref() {
// Test with backgroundServiceWorkeEnable set to true and the
// extensions.serviceWorkerRegist.allowed pref set to false.
// NOTE: the scenario with backgroundServiceWorkeEnable set to false
// is part of "browser_ext_background_serviceworker_pref_disabled.js".
await SpecialPowers.pushPrefEnv({
set: [["extensions.serviceWorkerRegister.allowed", false]],
});
let extensionData = {
files: {
"page.html": "<!DOCTYPE html><script src='page.js'></script>",
"page.js": async function() {
try {
await navigator.serviceWorker.register("sw.js");
browser.test.fail(
`An extension page should not be able to register a serviceworker successfully`
);
} catch (err) {
browser.test.assertEq(
String(err),
"SecurityError: The operation is insecure.",
"Got the expected error on registering a service worker from a script"
);
}
browser.test.sendMessage("test-serviceWorker-register-disallowed");
},
"sw.js": "",
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
// Verify that an extension page can't register a moz-extension url
// as a service worker.
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: `moz-extension://${extension.uuid}/page.html`,
},
async () => {
await extension.awaitMessage("test-serviceWorker-register-disallowed");
}
);
await extension.unload();
await SpecialPowers.popPrefEnv();
// Test again with the pref set to true.
await SpecialPowers.pushPrefEnv({
set: [["extensions.serviceWorkerRegister.allowed", true]],
});
extension = ExtensionTestUtils.loadExtension({
files: {
...extensionData.files,
"page.js": async function() {
try {
await navigator.serviceWorker.register("sw.js");
} catch (err) {
browser.test.fail(
`Unexpected error on registering a service worker: ${err}`
);
throw err;
} finally {
browser.test.sendMessage("test-serviceworker-register-allowed");
}
},
},
});
await extension.startup();
// Verify that an extension page can register a moz-extension url
// as a service worker if enabled by the related pref.
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: `moz-extension://${extension.uuid}/page.html`,
},
async () => {
await extension.awaitMessage("test-serviceworker-register-allowed");
}
);
await extension.unload();
await SpecialPowers.popPrefEnv();
});
add_task(async function test_cache_api_allowed() {
// Verify that Cache API support for moz-extension url availability is also
// conditioned by the extensions.backgroundServiceWorker.enabled pref.
// NOTE: the scenario with backgroundServiceWorkeEnable set to false
// is part of "browser_ext_background_serviceworker_pref_disabled.js".
const extension = ExtensionTestUtils.loadExtension({
async background() {
try {
let cache = await window.caches.open("test-cache-api");
browser.test.assertTrue(
await window.caches.has("test-cache-api"),
"CacheStorage.has should resolve to true"
);
// Test that adding and requesting cached moz-extension urls
// works as well.
let url = browser.runtime.getURL("file.txt");
await cache.add(url);
const content = await cache.match(url).then(res => res.text());
browser.test.assertEq(
"file content",
content,
"Got the expected content from the cached moz-extension url"
);
// Test that deleting the cache storage works as expected.
browser.test.assertTrue(
await window.caches.delete("test-cache-api"),
"Cache deleted successfully"
);
browser.test.assertTrue(
!(await window.caches.has("test-cache-api")),
"CacheStorage.has should resolve to false"
);
} catch (err) {
browser.test.fail(`Unexpected error on using Cache API: ${err}`);
throw err;
} finally {
browser.test.sendMessage("test-cache-api-allowed");
}
},
files: {
"file.txt": "file content",
},
});
await extension.startup();
await extension.awaitMessage("test-cache-api-allowed");
await extension.unload();
});
function createTestSWScript({ postMessageReply }) {
return `
self.onmessage = msg => {
dump("Background ServiceWorker - onmessage handler\\n");
msg.ports[0].postMessage("${postMessageReply}");
dump("Background ServiceWorker - postMessage\\n");
};
dump("Background ServiceWorker - executed\\n");
`;
}
async function testServiceWorker({ extension, expectMessageReply }) {
// Verify that the WebExtensions framework has successfully registered the
// background service worker declared in the extension manifest.
const swRegInfo = getBackgroundServiceWorkerRegistration(extension);
// Activate the background service worker by exchanging a message
// with it.
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: `moz-extension://${extension.uuid}/page.html`,
},
async browser => {
let msgFromV1 = await SpecialPowers.spawn(
browser,
[swRegInfo.scriptURL],
async url => {
const { active } = await content.navigator.serviceWorker.ready;
const { port1, port2 } = new content.MessageChannel();
return new Promise(resolve => {
port1.onmessage = msg => resolve(msg.data);
active.postMessage("test", [port2]);
});
}
);
Assert.deepEqual(
msgFromV1,
expectMessageReply,
"Got the expected reply from the extension service worker"
);
}
);
}
function loadTestExtension({ version }) {
const postMessageReply = `reply:sw-v${version}`;
return ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {
version,
background: {
service_worker: "sw.js",
},
applications: { gecko: { id: "test-bg-sw@mochi.test" } },
},
files: {
"page.html": "<!DOCTYPE html><body></body>",
"sw.js": createTestSWScript({ postMessageReply }),
},
});
}
async function assertWorkerIsRunningInExtensionProcess(extension) {
// Activate the background service worker by exchanging a message
// with it.
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: `moz-extension://${extension.uuid}/page.html`,
},
async browser => {
const workerScriptURL = `moz-extension://${extension.uuid}/sw.js`;
const workerDebuggerURLs = await SpecialPowers.spawn(
browser,
[workerScriptURL],
async url => {
await content.navigator.serviceWorker.ready;
const wdm = Cc[
"@mozilla.org/dom/workers/workerdebuggermanager;1"
].getService(Ci.nsIWorkerDebuggerManager);
return Array.from(wdm.getWorkerDebuggerEnumerator())
.map(wd => {
return wd.url;
})
.filter(swURL => swURL == url);
}
);
Assert.deepEqual(
workerDebuggerURLs,
[workerScriptURL],
"The worker should be running in the extension child process"
);
}
);
}
add_task(async function test_background_serviceworker_with_no_ext_apis() {
const extensionV1 = loadTestExtension({ version: "1" });
await extensionV1.startup();
const swRegInfo = getBackgroundServiceWorkerRegistration(extensionV1);
const { uuid } = extensionV1;
await assertWorkerIsRunningInExtensionProcess(extensionV1);
await testServiceWorker({
extension: extensionV1,
expectMessageReply: "reply:sw-v1",
});
// Load a new version of the same addon and verify that the
// expected worker script is being executed.
const extensionV2 = loadTestExtension({ version: "2" });
await extensionV2.startup();
is(extensionV2.uuid, uuid, "The extension uuid did not change as expected");
await testServiceWorker({
extension: extensionV2,
expectMessageReply: "reply:sw-v2",
});
await Promise.all([
extensionV2.unload(),
// test extension v1 wrapper has to be unloaded explicitly, otherwise
// will be detected as a failure by the test harness.
extensionV1.unload(),
]);
await waitForServiceWorkerTerminated(swRegInfo);
await waitForServiceWorkerRegistrationsRemoved(extensionV2);
});

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

@ -0,0 +1,122 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(async function assert_background_serviceworker_pref_disabled() {
is(
WebExtensionPolicy.backgroundServiceWorkerEnabled,
false,
"Expect extensions.backgroundServiceWorker.enabled to be false"
);
});
add_task(async function test_background_serviceworker_disallowed() {
const id = "test-disallowed-worker@test";
const extensionData = {
manifest: {
background: {
service_worker: "sw.js",
},
applicantions: { gecko: { id } },
useAddonManager: "temporary",
},
};
SimpleTest.waitForExplicitFinish();
let waitForConsole = new Promise(resolve => {
SimpleTest.monitorConsole(resolve, [
{
message: /Reading manifest: Error processing background: background.service_worker is currently disabled/,
},
]);
});
const extension = ExtensionTestUtils.loadExtension(extensionData);
await Assert.rejects(
extension.startup(),
/startup failed/,
"Startup failed with background.service_worker while disabled by pref"
);
SimpleTest.endMonitorConsole();
await waitForConsole;
});
add_task(async function test_serviceWorker_register_disallowed() {
// Verify that setting extensions.serviceWorkerRegist.allowed pref to false
// doesn't allow serviceWorker.register if backgroundServiceWorkeEnable is
// set to false
await SpecialPowers.pushPrefEnv({
set: [["extensions.serviceWorkerRegister.allowed", true]],
});
let extensionData = {
files: {
"page.html": "<!DOCTYPE html><script src='page.js'></script>",
"page.js": async function() {
try {
await navigator.serviceWorker.register("sw.js");
browser.test.fail(
`An extension page should not be able to register a serviceworker successfully`
);
} catch (err) {
browser.test.assertEq(
String(err),
"SecurityError: The operation is insecure.",
"Got the expected error on registering a service worker from a script"
);
}
browser.test.sendMessage("test-serviceWorker-register-disallowed");
},
"sw.js": "",
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
// Verify that an extension page can't register a moz-extension url
// as a service worker.
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: `moz-extension://${extension.uuid}/page.html`,
},
async () => {
await extension.awaitMessage("test-serviceWorker-register-disallowed");
}
);
await extension.unload();
await SpecialPowers.popPrefEnv();
});
add_task(async function test_cache_api_disallowed() {
// Verify that Cache API support for moz-extension url availability is also
// conditioned by the extensions.backgroundServiceWorker.enabled pref.
const extension = ExtensionTestUtils.loadExtension({
async background() {
try {
await window.caches.open("test-cache-api");
browser.test.fail(
`An extension page should not be allowed to use the Cache API successfully`
);
} catch (err) {
browser.test.assertEq(
String(err),
"SecurityError: The operation is insecure.",
"Got the expected error on registering a service worker from a script"
);
} finally {
browser.test.sendMessage("test-cache-api-disallowed");
}
},
});
await extension.startup();
await extension.awaitMessage("test-cache-api-disallowed");
await extension.unload();
});

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

@ -0,0 +1,123 @@
"use strict";
/* exported assert_background_serviceworker_pref_enabled,
* getBackgroundServiceWorkerRegistration,
* getServiceWorkerInfo, getServiceWorkerState,
* waitForServiceWorkerRegistrationsRemoved, waitForServiceWorkerTerminated
*/
async function assert_background_serviceworker_pref_enabled() {
is(
WebExtensionPolicy.backgroundServiceWorkerEnabled,
true,
"Expect extensions.backgroundServiceWorker.enabled to be true"
);
}
// Return the name of the enum corresponding to the worker's state (ex: "STATE_ACTIVATED")
// because nsIServiceWorkerInfo doesn't currently provide a comparable string-returning getter.
function getServiceWorkerState(workerInfo) {
const map = Object.keys(workerInfo)
.filter(k => k.startsWith("STATE_"))
.reduce((map, name) => {
map.set(workerInfo[name], name);
return map;
}, new Map());
return map.has(workerInfo.state)
? map.get(workerInfo.state)
: "state: ${workerInfo.state}";
}
function getServiceWorkerInfo(swRegInfo) {
const {
evaluatingWorker,
installingWorker,
waitingWorker,
activeWorker,
} = swRegInfo;
return evaluatingWorker || installingWorker || waitingWorker || activeWorker;
}
async function waitForServiceWorkerTerminated(swRegInfo) {
info(`Wait all ${swRegInfo.scope} workers to be terminated`);
try {
await BrowserTestUtils.waitForCondition(
() => !getServiceWorkerInfo(swRegInfo)
);
} catch (err) {
const workerInfo = getServiceWorkerInfo(swRegInfo);
if (workerInfo) {
ok(
false,
`Error while waiting for workers for scope ${swRegInfo.scope} to be terminated. ` +
`Found a worker in state: ${getServiceWorkerState(workerInfo)}`
);
return;
}
throw err;
}
}
function getBackgroundServiceWorkerRegistration(extension) {
const policy = WebExtensionPolicy.getByHostname(extension.uuid);
const expectedSWScope = policy.getURL("/");
const expectedScriptURL = policy.extension.backgroundWorkerScript || "";
ok(
expectedScriptURL.startsWith(expectedSWScope),
`Extension does include a valid background.service_worker: ${expectedScriptURL}`
);
const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
Ci.nsIServiceWorkerManager
);
let swReg;
let regs = swm.getAllRegistrations();
for (let i = 0; i < regs.length; i++) {
let reg = regs.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
if (reg.scriptSpec === expectedScriptURL) {
swReg = reg;
break;
}
}
ok(swReg, `Found service worker registration for ${expectedScriptURL}`);
is(
swReg.scope,
expectedSWScope,
"The extension background worker registration has the expected scope URL"
);
return swReg;
}
async function waitForServiceWorkerRegistrationsRemoved(extension) {
info(`Wait ${extension.id} service worker registration to be deleted`);
const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
Ci.nsIServiceWorkerManager
);
let baseURI = Services.io.newURI(`moz-extension://${extension.uuid}/`);
let principal = Services.scriptSecurityManager.createContentPrincipal(
baseURI,
{}
);
await BrowserTestUtils.waitForCondition(() => {
let regs = swm.getAllRegistrations();
for (let i = 0; i < regs.length; i++) {
let reg = regs.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
if (principal.equals(reg.principal)) {
return false;
}
}
info(`All ${extension.id} service worker registrations are gone`);
return true;
}, `All ${extension.id} service worker registrations should be deleted`);
}

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

@ -0,0 +1,11 @@
{
"manifest_version": 2,
"name": "Test Extension with Background Service Worker",
"version": "1",
"applications": {
"gecko": { "id": "extension-with-bg-sw@test" }
},
"background": {
"service_worker": "sw.js"
}
}

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

@ -0,0 +1,3 @@
"use strict";
dump("extension-with-bg-sw: sw.js loaded");

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

@ -0,0 +1 @@
[test_extension_serviceworkers_purged_on_pref_disabled.py]

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

@ -0,0 +1,78 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette_driver import Wait
from marionette_driver.addons import Addons
from marionette_harness import MarionetteTestCase
import os
EXT_ID = "extension-with-bg-sw@test"
EXT_DIR_PATH = "extension-with-bg-sw"
PREF_BG_SW_ENABLED = "extensions.backgroundServiceWorker.enabled"
class PurgeExtensionServiceWorkersOnPrefDisabled(MarionetteTestCase):
def setUp(self):
super(PurgeExtensionServiceWorkersOnPrefDisabled, self).setUp()
self.test_extension_id = EXT_ID
# Flip the "mirror: once" pref and restart Firefox to be able
# to run the extension successfully.
self.marionette.set_pref(PREF_BG_SW_ENABLED, True)
self.marionette.restart(in_app=True)
def tearDown(self):
self.marionette.restart(clean=True)
super(PurgeExtensionServiceWorkersOnPrefDisabled, self).tearDown()
def test_unregistering_service_worker_when_clearing_data(self):
self.install_extension_with_service_worker()
# Flip the pref to false and restart again to verify that the
# service worker registration has been removed as expected.
self.marionette.set_pref(PREF_BG_SW_ENABLED, False)
self.marionette.restart(in_app=True)
self.assertFalse(self.is_extension_service_worker_registered)
def install_extension_with_service_worker(self):
addons = Addons(self.marionette)
test_extension_path = os.path.join(
os.path.dirname(self.filepath),
"data",
EXT_DIR_PATH
)
addons.install(test_extension_path, temp=True)
self.test_extension_base_url = self.get_extension_url()
Wait(self.marionette).until(
lambda _: self.is_extension_service_worker_registered,
message="Wait the extension service worker to be registered"
)
def get_extension_url(self, path="/"):
with self.marionette.using_context("chrome"):
return self.marionette.execute_script("""
let policy = WebExtensionPolicy.getByID(arguments[0]);
return policy.getURL(arguments[1])
""", script_args=(self.test_extension_id, path))
@property
def is_extension_service_worker_registered(self):
with self.marionette.using_context("chrome"):
return self.marionette.execute_script("""
let serviceWorkerManager = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
Ci.nsIServiceWorkerManager
);
let serviceWorkers = serviceWorkerManager.getAllRegistrations();
for (let i = 0; i < serviceWorkers.length; i++) {
let sw = serviceWorkers.queryElementAt(
i,
Ci.nsIServiceWorkerRegistrationInfo
);
if (sw.scope == arguments[0]) {
return true;
}
}
return false;
""", script_args=(self.test_extension_base_url,))