Bug 1645054 - Disable/purge service workers when dom.serviceWorkers.enabled is false r=dom-worker-reviewers,necko-reviewers,asuth,webdriver-reviewers,whimboo

Differential Revision: https://phabricator.services.mozilla.com/D167550
This commit is contained in:
Joshua Marshall 2023-02-28 15:57:55 +00:00
Родитель 3575a37276
Коммит b0c912c415
14 изменённых файлов: 246 добавлений и 92 удалений

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

@ -468,10 +468,6 @@ void ServiceWorkerManager::Init(ServiceWorkerRegistrar* aRegistrar) {
MOZ_DIAGNOSTIC_ASSERT(aRegistrar);
nsTArray<ServiceWorkerRegistrationData> data;
aRegistrar->GetRegistrations(data);
LoadRegistrations(data);
PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
if (NS_WARN_IF(!actorChild)) {
MaybeStartShutdown();
@ -487,6 +483,12 @@ void ServiceWorkerManager::Init(ServiceWorkerRegistrar* aRegistrar) {
mActor = static_cast<ServiceWorkerManagerChild*>(actor);
// mActor must be set before LoadRegistrations is called because it can purge
// service workers if preferences are disabled.
nsTArray<ServiceWorkerRegistrationData> data;
aRegistrar->GetRegistrations(data);
LoadRegistrations(data);
mTelemetryLastChange = TimeStamp::Now();
}
@ -1365,8 +1367,8 @@ ServiceWorkerManager::Unregister(nsIPrincipal* aPrincipal,
NS_ConvertUTF16toUTF8 scope(aScope);
RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, scope);
RefPtr<ServiceWorkerUnregisterJob> job = new ServiceWorkerUnregisterJob(
aPrincipal, scope, true /* send to parent */);
RefPtr<ServiceWorkerUnregisterJob> job =
new ServiceWorkerUnregisterJob(aPrincipal, scope);
if (aCallback) {
RefPtr<UnregisterJobCallback> cb = new UnregisterJobCallback(aCallback);
@ -1498,6 +1500,14 @@ void ServiceWorkerManager::HandleError(
aColumnNumber, aFlags);
}
void ServiceWorkerManager::PurgeServiceWorker(
const ServiceWorkerRegistrationData& aRegistration,
nsIPrincipal* aPrincipal) {
MOZ_ASSERT(mActor);
serviceWorkerScriptCache::PurgeCache(aPrincipal, aRegistration.cacheName());
MaybeSendUnregister(aPrincipal, aRegistration.scope());
}
void ServiceWorkerManager::LoadRegistration(
const ServiceWorkerRegistrationData& aRegistration) {
MOZ_ASSERT(NS_IsMainThread());
@ -1508,6 +1518,13 @@ void ServiceWorkerManager::LoadRegistration(
}
nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
if (!StaticPrefs::dom_serviceWorkers_enabled()) {
// If service workers are disabled, remove the registration from disk
// instead of loading.
PurgeServiceWorker(aRegistration, principal);
return;
}
// Purge extensions registrations if they are disabled by prefs.
if (!StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) {
nsCOMPtr<nsIURI> uri = principal->GetURI();
@ -1516,8 +1533,7 @@ void ServiceWorkerManager::LoadRegistration(
// the extension may not have been loaded yet and the WebExtensionPolicy
// may not exist yet.
if (uri->SchemeIs("moz-extension")) {
const auto& cacheName = aRegistration.cacheName();
serviceWorkerScriptCache::PurgeCache(principal, cacheName);
PurgeServiceWorker(aRegistration, principal);
return;
}
}

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

@ -355,6 +355,9 @@ class ServiceWorkerManager final : public nsIServiceWorkerManager,
void MaybeRemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration);
void PurgeServiceWorker(const ServiceWorkerRegistrationData& aRegistration,
nsIPrincipal* aPrincipal);
RefPtr<ServiceWorkerManagerChild> mActor;
bool mShuttingDown;

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

@ -43,11 +43,9 @@ NS_IMPL_ISUPPORTS(ServiceWorkerUnregisterJob::PushUnsubscribeCallback,
nsIUnsubscribeResultCallback)
ServiceWorkerUnregisterJob::ServiceWorkerUnregisterJob(nsIPrincipal* aPrincipal,
const nsACString& aScope,
bool aSendToParent)
const nsACString& aScope)
: ServiceWorkerJob(Type::Unregister, aPrincipal, aScope, ""_ns),
mResult(false),
mSendToParent(aSendToParent) {}
mResult(false) {}
bool ServiceWorkerUnregisterJob::GetResult() const {
MOZ_ASSERT(NS_IsMainThread());
@ -110,9 +108,7 @@ void ServiceWorkerUnregisterJob::Unregister() {
// Note, we send the message to remove the registration from disk now. This is
// necessary to ensure the registration is removed if the controlled
// clients are closed by shutting down the browser.
if (mSendToParent) {
swm->MaybeSendUnregister(mPrincipal, mScope);
}
swm->MaybeSendUnregister(mPrincipal, mScope);
swm->EvictFromBFCache(registration);

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

@ -13,8 +13,8 @@ namespace mozilla::dom {
class ServiceWorkerUnregisterJob final : public ServiceWorkerJob {
public:
ServiceWorkerUnregisterJob(nsIPrincipal* aPrincipal, const nsACString& aScope,
bool aSendToParent);
ServiceWorkerUnregisterJob(nsIPrincipal* aPrincipal,
const nsACString& aScope);
bool GetResult() const;
@ -28,7 +28,6 @@ class ServiceWorkerUnregisterJob final : public ServiceWorkerJob {
void Unregister();
bool mResult;
bool mSendToParent;
};
} // namespace mozilla::dom

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

@ -218,11 +218,15 @@ support-files =
self_update_worker.sjs
!/dom/events/test/event_leak_utils.js
onmessageerror_worker.js
pref/fetch_nonexistent_file.html
pref/intercept_nonexistent_file_sw.js
[test_abrupt_completion.html]
skip-if =
os == 'linux' #Bug 1615164
win10_2004 # Bug 1615164
[test_async_waituntil.html]
[test_bad_script_cache.html]
[test_bug1151916.html]
[test_bug1240436.html]
[test_bug1408734.html]
@ -232,6 +236,7 @@ skip-if =
[test_cross_origin_url_after_redirect.html]
[test_devtools_bypass_serviceworker.html]
[test_empty_serviceworker.html]
[test_enabled_pref.html]
[test_error_reporting.html]
skip-if = serviceworker_e10s
[test_escapedSlashes.html]
@ -251,6 +256,9 @@ skip-if = serviceworker_e10s
support-files = console_monitor.js
[test_file_blob_response.html]
[test_file_blob_upload.html]
[test_file_upload.html]
skip-if = toolkit == 'android' #Bug 1430182
support-files = script_file_upload.js sw_file_upload.js server_file_upload.sjs
[test_force_refresh.html]
[test_gzip_redirect.html]
[test_hsts_upgrade_intercept.html]
@ -280,6 +288,7 @@ scheme = https
skip-if =
os == "linux" && bits == 64 && debug # Bug 1749068
[test_navigator.html]
[test_nofetch_handler.html]
[test_not_intercept_plugin.html]
skip-if = serviceworker_e10s # leaks InterceptedHttpChannel and others things
[test_notification_constructor_error.html]
@ -291,12 +300,12 @@ skip-if =
xorigin # JavaScript error: http://mochi.xorigin-test:8888/tests/SimpleTest/TestRunner.js, line 157: SecurityError: Permission denied to access property "wrappedJSObject" on cross-origin object
support-files = notification_openWindow_worker.js file_notification_openWindow.html
tags = openwindow
[test_notificationclick-otherwindow.html]
skip-if = xorigin # Bug 1792790
[test_notificationclick.html]
skip-if = xorigin # Bug 1792790
[test_notificationclick_focus.html]
skip-if = xorigin # Bug 1792790
[test_notificationclick-otherwindow.html]
skip-if = xorigin # Bug 1792790
[test_notificationclose.html]
skip-if = xorigin # Bug 1792790
[test_onmessageerror.html]
@ -312,12 +321,15 @@ skip-if = xorigin # Hangs with no error log
[test_register_base.html]
[test_register_https_in_http.html]
[test_sandbox_intercept.html]
[test_sanitize.html]
[test_scopes.html]
[test_script_loader_intercepted_js_cache.html]
skip-if = serviceworker_e10s
[test_sanitize.html]
[test_serviceworker.html]
[test_self_update_worker.html]
skip-if = serviceworker_e10s
toolkit == 'android'
[test_service_worker_allowed.html]
[test_serviceworker.html]
[test_serviceworker_header.html]
[test_serviceworker_interfaces.html]
[test_serviceworker_not_sharedworker.html]
@ -333,15 +345,6 @@ skip-if = verify
serviceworker_e10s
[test_workerUnregister.html]
[test_workerUpdate.html]
[test_worker_reference_gc_timeout.html]
[test_workerupdatefoundevent.html]
[test_xslt.html]
[test_async_waituntil.html]
[test_worker_reference_gc_timeout.html]
[test_nofetch_handler.html]
[test_bad_script_cache.html]
[test_file_upload.html]
skip-if = toolkit == 'android' #Bug 1430182
support-files = script_file_upload.js sw_file_upload.js server_file_upload.sjs
[test_self_update_worker.html]
skip-if = serviceworker_e10s
toolkit == 'android'

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

@ -0,0 +1,15 @@
<!DOCTYPE HTML>
<html>
<head>
<script>
async function fetch_status() {
let response = await fetch('this_file_does_not_exist.txt');
return response.status;
}
</script>
</head>
<body>
</body>
</html>

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

@ -0,0 +1,5 @@
onfetch = function(e) {
if (e.request.url.match(/this_file_does_not_exist.txt$/)) {
e.respondWith(new Response("intercepted"));
}
};

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

@ -0,0 +1,55 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 1645054 - test dom.serviceWorkers.enabled preference</title>
</head>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="utils.js"></script>
<script>
function create_iframe(url) {
return new Promise(function(res) {
iframe = document.createElement('iframe');
iframe.src = url;
iframe.onload = function() { res(iframe) }
document.body.appendChild(iframe);
});
}
async function do_fetch(pref) {
await SpecialPowers.pushPrefEnv({ set: [pref] });
let iframe = await create_iframe("./pref/fetch_nonexistent_file.html");
let status = await iframe.contentWindow.fetch_status();
await SpecialPowers.popPrefEnv();
return status;
}
add_task(async () => {
await SpecialPowers.pushPrefEnv({
set: [['dom.serviceWorkers.testing.enabled', true]]
});
let reg = await navigator.serviceWorker.register(
'pref/intercept_nonexistent_file_sw.js');
await waitForState(reg.installing, 'activated');
let status;
status = await do_fetch(['dom.serviceWorkers.enabled', true]);
is(status, 200, 'SW enabled');
status = await do_fetch(['dom.serviceWorkers.enabled', false]);
is(status, 404, 'SW disabled');
status = await do_fetch(['dom.serviceWorkers.enabled', true]);
is(status, 200, 'SW enabled again');
await reg.unregister();
});
</script>
<body>
</body>
</html>

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

@ -1 +1,2 @@
[test_service_workers_at_startup.py]
[test_service_workers_disabled.py]

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

@ -0,0 +1,63 @@
# 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/.
import os
from marionette_driver import Wait
from marionette_harness import MarionetteTestCase
class MarionetteServiceWorkerTestCase(MarionetteTestCase):
def install_service_worker(self, path):
install_url = self.marionette.absolute_url(path)
self.marionette.navigate(install_url)
Wait(self.marionette).until(
lambda _: self.is_service_worker_registered,
message="Service worker not successfully installed",
)
# Wait for the registered service worker to be stored in the Firefox
# profile before restarting the instance to prevent intermittent
# failures (Bug 1665184).
Wait(self.marionette, timeout=10).until(
lambda _: self.profile_serviceworker_txt_exists,
message="Service worker not stored in profile",
)
# self.marionette.restart(in_app=True) will restore service workers if
# we don't navigate away before restarting.
self.marionette.navigate("about:blank")
# Using @property helps avoid the case where missing parens at the call site
# yields an unvarying 'true' value.
@property
def profile_serviceworker_txt_exists(self):
return "serviceworker.txt" in os.listdir(self.marionette.profile_path)
@property
def is_service_worker_registered(self):
with self.marionette.using_context("chrome"):
return self.marionette.execute_script(
"""
let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
Ci.nsIServiceWorkerManager
);
let ssm = Services.scriptSecurityManager;
let principal = ssm.createContentPrincipalFromOrigin(arguments[0]);
let serviceWorkers = swm.getAllRegistrations();
for (let i = 0; i < serviceWorkers.length; i++) {
let sw = serviceWorkers.queryElementAt(
i,
Ci.nsIServiceWorkerRegistrationInfo
);
if (sw.principal.origin == principal.origin) {
return true;
}
}
return false;
""",
script_args=(self.marionette.absolute_url(""),),
)

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

@ -3,80 +3,29 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os
import sys
# Add this directory to the import path.
sys.path.append(os.path.dirname(__file__))
from marionette_driver import Wait
from marionette_harness import MarionetteTestCase
from service_worker_utils import MarionetteServiceWorkerTestCase
class ServiceWorkerAtStartupTestCase(MarionetteTestCase):
class ServiceWorkerAtStartupTestCase(MarionetteServiceWorkerTestCase):
def setUp(self):
super(ServiceWorkerAtStartupTestCase, self).setUp()
self.install_service_worker()
self.install_service_worker("serviceworker/install_serviceworker.html")
def tearDown(self):
self.marionette.restart(in_app=False, clean=True)
super(ServiceWorkerAtStartupTestCase, self).tearDown()
def install_service_worker(self):
install_url = self.marionette.absolute_url(
"serviceworker/install_serviceworker.html"
)
self.marionette.navigate(install_url)
Wait(self.marionette).until(
lambda _: self.is_service_worker_registered,
message="Wait the service worker to be installed",
)
def test_registered_service_worker_after_restart(self):
# Wait the registered service worker to be stored in the Firefox profile
# before restarting the instance to prevent intermittent failures
# (Bug 1665184).
Wait(self.marionette, timeout=10).until(
lambda _: self.profile_serviceworker_txt_exists,
message="Wait service workers to be stored in the profile",
)
# Quit and start a new session to simulate a full browser restart
# (`self.marionette.restart()` seems to not
# be enough to simulate this scenario, because the service workers
# are staying registered and they are not actually re-registered
# from the list stored in the profile as this test needs).
self.marionette.quit()
self.marionette.start_session()
self.marionette.restart()
Wait(self.marionette).until(
lambda _: self.is_service_worker_registered,
message="Wait the service worker to be registered after restart",
message="Service worker not registered after restart",
)
self.assertTrue(self.is_service_worker_registered)
@property
def profile_serviceworker_txt_exists(self):
return "serviceworker.txt" in os.listdir(self.marionette.profile_path)
@property
def is_service_worker_registered(self):
with self.marionette.using_context("chrome"):
return self.marionette.execute_script(
"""
let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
Ci.nsIServiceWorkerManager
);
let ssm = Services.scriptSecurityManager;
let principal = ssm.createContentPrincipalFromOrigin(arguments[0]);
let serviceWorkers = swm.getAllRegistrations();
for (let i = 0; i < serviceWorkers.length; i++) {
let sw = serviceWorkers.queryElementAt(
i,
Ci.nsIServiceWorkerRegistrationInfo
);
if (sw.principal.origin == principal.origin) {
return true;
}
}
return false;
""",
script_args=(self.marionette.absolute_url(""),),
)

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

@ -0,0 +1,37 @@
# 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/.
import os
import sys
# Add this directory to the import path.
sys.path.append(os.path.dirname(__file__))
from service_worker_utils import MarionetteServiceWorkerTestCase
class ServiceWorkersDisabledTestCase(MarionetteServiceWorkerTestCase):
def setUp(self):
super(ServiceWorkersDisabledTestCase, self).setUp()
self.install_service_worker("serviceworker/install_serviceworker.html")
def tearDown(self):
self.marionette.restart(in_app=False, clean=True)
super(ServiceWorkersDisabledTestCase, self).tearDown()
def test_service_workers_disabled_at_startup(self):
# self.marionette.set_pref sets preferences after startup. Using it
# here causes intermittent failures.
self.marionette.instance.profile.set_preferences(
{
"dom.serviceWorkers.enabled": False,
}
)
self.marionette.restart()
self.assertFalse(
self.is_service_worker_registered,
"Service worker registration should have been purged",
)

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

@ -4012,6 +4012,10 @@ bool HttpBaseChannel::ShouldIntercept(nsIURI* aURI) {
GetCallback(controller);
bool shouldIntercept = false;
if (!StaticPrefs::dom_serviceWorkers_enabled()) {
return false;
}
// We should never intercept internal redirects. The ServiceWorker code
// can trigger interntal redirects as the result of a FetchEvent. If
// we re-intercept then an infinite loop can occur.

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

@ -28,6 +28,14 @@ support-files =
test_http3_prio_helpers.js
http2_test_common.js
# dom.serviceWorkers.enabled is currently set to false in StaticPrefList.yaml
# and enabled individually by app prefs, so for the xpcshell tests that involve
# interception, we need to explicitly enable the pref.
# Consider enabling it in StaticPrefList.yaml
# https://bugzilla.mozilla.org/show_bug.cgi?id=1816325
prefs =
dom.serviceWorkers.enabled=true
[test_trr_nat64.js]
run-sequentially = node server exceptions dont replay well
[test_nsIBufferedOutputStream_writeFrom_block.js]