Bug 1642676 - Ensure remote workers are launched in a child process based on the expected remoteType. r=asuth,mixedpuppy

Differential Revision: https://phabricator.services.mozilla.com/D61708
This commit is contained in:
Luca Greco 2020-06-26 18:13:46 +00:00
parent b6fb32bf71
commit 634a0f15cf
15 changed files with 530 additions and 40 deletions

View File

@ -42,6 +42,7 @@
#include "mozilla/dom/InternalRequest.h"
#include "mozilla/dom/ReferrerInfo.h"
#include "mozilla/dom/RemoteWorkerControllerChild.h"
#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::GetRemoteType
#include "mozilla/dom/ServiceWorkerBinding.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/IPCStreamUtils.h"
@ -186,6 +187,12 @@ nsresult ServiceWorkerPrivateImpl::Initialize() {
return rv;
}
auto remoteType = RemoteWorkerManager::GetRemoteType(
principal, WorkerType::WorkerTypeService);
if (NS_WARN_IF(remoteType.isErr())) {
return remoteType.unwrapErr();
}
mRemoteWorkerData = RemoteWorkerData(
NS_ConvertUTF8toUTF16(mOuter->mInfo->ScriptSpec()), baseScriptURL,
baseScriptURL, /* name */ VoidString(),
@ -204,7 +211,8 @@ nsresult ServiceWorkerPrivateImpl::Initialize() {
// already_AddRefed<>. Let's set it to null.
/* referrerInfo */ nullptr,
storageAccess, std::move(serviceWorkerData), regInfo->AgentClusterId());
storageAccess, std::move(serviceWorkerData), regInfo->AgentClusterId(),
remoteType.unwrap());
mRemoteWorkerData.referrerInfo() = MakeAndAddRef<ReferrerInfo>();

View File

@ -33,6 +33,7 @@
#include "mozilla/dom/FetchEventOpProxyChild.h"
#include "mozilla/dom/IndexedDatabaseManager.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::IsRemoteTypeAllowed
#include "mozilla/dom/RemoteWorkerTypes.h"
#include "mozilla/dom/ServiceWorkerDescriptor.h"
#include "mozilla/dom/ServiceWorkerInterceptController.h"
@ -313,6 +314,14 @@ nsresult RemoteWorkerChild::ExecWorkerOnMainThread(RemoteWorkerData&& aData) {
auto scopeExit = MakeScopeExit([&] { TransitionStateToTerminated(); });
// Verify the the RemoteWorker should be really allowed to run in this
// process, and fail if it shouldn't (This shouldn't normally happen,
// unless the RemoteWorkerData has been tempered in the process it was
// sent from).
if (!RemoteWorkerManager::IsRemoteTypeAllowed(aData)) {
return NS_ERROR_UNEXPECTED;
}
auto principalOrErr = PrincipalInfoToPrincipal(aData.principalInfo());
if (NS_WARN_IF(principalOrErr.isErr())) {
return principalOrErr.unwrapErr();

View File

@ -10,6 +10,7 @@
#include "mozilla/SchedulerGroup.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/dom/ContentChild.h" // ContentChild::GetSingleton
#include "mozilla/dom/RemoteWorkerParent.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/BackgroundUtils.h"
@ -37,18 +38,6 @@ bool IsServiceWorker(const RemoteWorkerData& aData) {
OptionalServiceWorkerData::TServiceWorkerData;
}
// Respecting COOP and COEP requires processing headers in the parent
// process in order to choose an appropriate content process, but the
// workers' ScriptLoader processes headers in content processes. An
// intermediary step that provides security guarantees is to simply never
// allow SharedWorkers and ServiceWorkers to exist in a COOP+COEP process.
// The ultimate goal is to allow these worker types to be put in such
// processes based on their script response headers.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1595206
bool IsServiceWorkerRemoteType(const nsAString& aRemoteType) {
return IsWebRemoteType(aRemoteType) && !IsWebCoopCoepRemoteType(aRemoteType);
}
void TransmitPermissionsAndBlobURLsForPrincipalInfo(
ContentParent* aContentParent, const PrincipalInfo& aPrincipalInfo) {
AssertIsOnMainThread();
@ -70,6 +59,140 @@ void TransmitPermissionsAndBlobURLsForPrincipalInfo(
} // namespace
// static
bool RemoteWorkerManager::MatchRemoteType(const nsAString& processRemoteType,
const nsAString& workerRemoteType) {
if (processRemoteType.Equals(workerRemoteType)) {
return true;
}
// Respecting COOP and COEP requires processing headers in the parent
// process in order to choose an appropriate content process, but the
// workers' ScriptLoader processes headers in content processes. An
// intermediary step that provides security guarantees is to simply never
// allow SharedWorkers and ServiceWorkers to exist in a COOP+COEP process.
// The ultimate goal is to allow these worker types to be put in such
// processes based on their script response headers.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1595206
if (IsWebCoopCoepRemoteType(processRemoteType)) {
return false;
}
// A worker for a non privileged child process can be launched in
// any web child process that is not COOP and COEP.
if ((workerRemoteType.IsEmpty() || IsWebRemoteType(workerRemoteType)) &&
IsWebRemoteType(processRemoteType)) {
return true;
}
return false;
}
// static
Result<nsString, nsresult> RemoteWorkerManager::GetRemoteType(
const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerType aWorkerType) {
AssertIsOnMainThread();
if (aWorkerType != WorkerType::WorkerTypeService &&
aWorkerType != WorkerType::WorkerTypeShared) {
// This methods isn't expected to be called for worker type that
// aren't remote workers (currently Service and Shared workers).
return Err(NS_ERROR_UNEXPECTED);
}
nsString remoteType;
// If Gecko is running in single process mode, there is no child process
// to select, return without assigning any remoteType.
if (!BrowserTabsRemoteAutostart()) {
return remoteType;
}
auto* contentChild = ContentChild::GetSingleton();
bool isSystem = !!BasePrincipal::Cast(aPrincipal)->IsSystemPrincipal();
bool isMozExtension =
!isSystem && !!BasePrincipal::Cast(aPrincipal)->AddonPolicy();
if (aWorkerType == WorkerType::WorkerTypeShared && !contentChild &&
!isSystem) {
// Prevent content principals SharedWorkers to be launched in the main
// process while running in multiprocess mode.
//
// NOTE this also prevents moz-extension SharedWorker to be created
// while the extension process is disabled by prefs, allowing it would
// also trigger an assertion failure in
// RemoteWorkerManager::SelectorTargetActor, due to an unexpected
// content-principal parent-process workers while e10s is on).
return Err(NS_ERROR_ABORT);
}
bool separatePrivilegedMozilla = Preferences::GetBool(
"browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", false);
if (isMozExtension) {
remoteType.Assign(NS_LITERAL_STRING(EXTENSION_REMOTE_TYPE));
} else if (separatePrivilegedMozilla) {
bool isPrivilegedMozilla = false;
aPrincipal->IsURIInPrefList("browser.tabs.remote.separatedMozillaDomains",
&isPrivilegedMozilla);
if (isPrivilegedMozilla) {
remoteType.Assign(NS_LITERAL_STRING(PRIVILEGEDMOZILLA_REMOTE_TYPE));
} else {
remoteType.Assign(aWorkerType == WorkerType::WorkerTypeShared &&
contentChild
? contentChild->GetRemoteType()
: NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
}
} else {
remoteType.Assign(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
}
return remoteType;
}
// static
bool RemoteWorkerManager::IsRemoteTypeAllowed(const RemoteWorkerData& aData) {
AssertIsOnMainThread();
// If Gecko is running in single process mode, there is no child process
// to select and we have to just consider it valid (if it should haven't
// been launched it should have been already prevented before reaching
// a RemoteWorkerChild instance).
if (!BrowserTabsRemoteAutostart()) {
return true;
}
const auto& principalInfo = aData.principalInfo();
auto* contentChild = ContentChild::GetSingleton();
if (!contentChild) {
// If e10s isn't disabled, only workers related to the system principal
// should be allowed to run in the parent process.
return principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo;
}
auto principalOrErr = PrincipalInfoToPrincipal(principalInfo);
if (NS_WARN_IF(principalOrErr.isErr())) {
return false;
}
nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
// Recompute the remoteType based on the principal, to double-check that it
// has not been tempered to select a different child process than the one
// expected.
bool isServiceWorker = aData.serviceWorkerData().type() ==
OptionalServiceWorkerData::TServiceWorkerData;
auto remoteType = GetRemoteType(
principal, isServiceWorker ? WorkerTypeService : WorkerTypeShared);
if (NS_WARN_IF(remoteType.isErr())) {
return false;
}
return MatchRemoteType(remoteType.unwrap(), contentChild->GetRemoteType());
}
/* static */
already_AddRefed<RemoteWorkerManager> RemoteWorkerManager::GetOrCreate() {
AssertIsInMainProcess();
@ -111,27 +234,24 @@ void RemoteWorkerManager::RegisterActor(RemoteWorkerServiceParent* aActor) {
MOZ_ASSERT(!mChildActors.Contains(aActor));
mChildActors.AppendElement(aActor);
if (!mPendings.IsEmpty()) {
const auto& remoteType = GetRemoteTypeForActor(aActor);
nsTArray<Pending> unlaunched;
RefPtr<ContentParent> contentParent =
BackgroundParent::GetContentParent(aActor->Manager());
auto scopeExit =
MakeScopeExit([&] { NS_ReleaseOnMainThread(contentParent.forget()); });
const auto& remoteType = contentParent->GetRemoteType();
if (!mPendings.IsEmpty()) {
// Flush pending launching.
for (Pending& p : mPendings) {
if (p.mController->IsTerminated()) {
continue;
}
if (IsServiceWorker(p.mData) && !IsServiceWorkerRemoteType(remoteType)) {
const auto& workerRemoteType = p.mData.remoteType();
if (MatchRemoteType(remoteType, workerRemoteType)) {
LaunchInternal(p.mController, aActor, p.mData);
} else {
unlaunched.AppendElement(std::move(p));
continue;
}
LaunchInternal(p.mController, aActor, p.mData);
}
std::swap(mPendings, unlaunched);
@ -227,6 +347,28 @@ void RemoteWorkerManager::AsyncCreationFailed(
NS_DispatchToCurrentThread(r.forget());
}
/* static */
nsString RemoteWorkerManager::GetRemoteTypeForActor(
const RemoteWorkerServiceParent* aActor) {
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
MOZ_ASSERT(aActor);
RefPtr<ContentParent> contentParent =
BackgroundParent::GetContentParent(aActor->Manager());
auto scopeExit =
MakeScopeExit([&] { NS_ReleaseOnMainThread(contentParent.forget()); });
if (NS_WARN_IF(!contentParent)) {
return EmptyString();
}
nsString aRemoteType(contentParent->GetRemoteType());
return aRemoteType;
}
template <typename Callback>
void RemoteWorkerManager::ForEachActor(Callback&& aCallback) const {
AssertIsOnBackgroundThread();
@ -290,11 +432,13 @@ RemoteWorkerManager::SelectTargetActorForServiceWorker(
RemoteWorkerServiceParent* actor = nullptr;
const auto& workerRemoteType = aData.remoteType();
ForEachActor([&](RemoteWorkerServiceParent* aActor,
RefPtr<ContentParent>&& aContentParent) {
const auto& remoteType = aContentParent->GetRemoteType();
if (IsServiceWorkerRemoteType(remoteType)) {
if (MatchRemoteType(remoteType, workerRemoteType)) {
auto lock = aContentParent->mRemoteWorkerActorData.Lock();
if (lock->mCount || !lock->mShutdownStarted) {
@ -327,7 +471,7 @@ RemoteWorkerManager::SelectTargetActorForServiceWorker(
RemoteWorkerServiceParent*
RemoteWorkerManager::SelectTargetActorForSharedWorker(
base::ProcessId aProcessId) const {
base::ProcessId aProcessId, const RemoteWorkerData& aData) const {
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mChildActors.IsEmpty());
@ -335,7 +479,10 @@ RemoteWorkerManager::SelectTargetActorForSharedWorker(
ForEachActor([&](RemoteWorkerServiceParent* aActor,
RefPtr<ContentParent>&& aContentParent) {
if (IsWebCoopCoepRemoteType(aContentParent->GetRemoteType())) {
bool matchRemoteType =
MatchRemoteType(aContentParent->GetRemoteType(), aData.remoteType());
if (!matchRemoteType) {
return true;
}
@ -378,8 +525,9 @@ RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActor(
return nullptr;
}
return IsServiceWorker(aData) ? SelectTargetActorForServiceWorker(aData)
: SelectTargetActorForSharedWorker(aProcessId);
return IsServiceWorker(aData)
? SelectTargetActorForServiceWorker(aData)
: SelectTargetActorForSharedWorker(aProcessId, aData);
}
void RemoteWorkerManager::LaunchNewContentProcess(
@ -401,7 +549,8 @@ void RemoteWorkerManager::LaunchNewContentProcess(
principalInfo = aData.principalInfo(),
bgEventTarget = std::move(bgEventTarget),
self = RefPtr<RemoteWorkerManager>(this)](
const CallbackParamType& aValue) mutable {
const CallbackParamType& aValue,
const nsString& remoteType) mutable {
if (aValue.IsResolve()) {
if (isServiceWorker) {
TransmitPermissionsAndBlobURLsForPrincipalInfo(aValue.ResolveValue(),
@ -413,12 +562,21 @@ void RemoteWorkerManager::LaunchNewContentProcess(
NS_ProxyRelease(__func__, bgEventTarget, self.forget());
} else {
// The "real" failure callback.
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableFunction(__func__, [self = std::move(self)] {
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
__func__, [self = std::move(self), remoteType] {
nsTArray<Pending> uncancelled;
auto pendings = std::move(self->mPendings);
for (const auto& pending : pendings) {
const auto& workerRemoteType = pending.mData.remoteType();
if (self->MatchRemoteType(remoteType, workerRemoteType)) {
pending.mController->CreationFailed();
} else {
uncancelled.AppendElement(pending);
}
}
std::swap(self->mPendings, uncancelled);
});
bgEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
@ -426,14 +584,19 @@ void RemoteWorkerManager::LaunchNewContentProcess(
};
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
__func__, [callback = std::move(processLaunchCallback)]() mutable {
__func__, [callback = std::move(processLaunchCallback),
workerRemoteType = aData.remoteType()]() mutable {
auto remoteType = workerRemoteType.IsEmpty()
? NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE)
: workerRemoteType;
ContentParent::GetNewOrUsedBrowserProcessAsync(
/* aFrameElement = */ nullptr,
/* aRemoteType = */ NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE))
/* aRemoteType = */ remoteType)
->Then(GetCurrentSerialEventTarget(), __func__,
[callback = std::move(callback)](
const CallbackParamType& aValue) mutable {
callback(aValue);
[callback = std::move(callback),
remoteType](const CallbackParamType& aValue) mutable {
callback(aValue, remoteType);
});
});

View File

@ -11,6 +11,7 @@
#include "mozilla/RefPtr.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/RemoteWorkerTypes.h"
#include "mozilla/dom/WorkerPrivate.h" // WorkerType enum
#include "nsISupportsImpl.h"
#include "nsTArray.h"
@ -34,6 +35,22 @@ class RemoteWorkerManager final {
void Launch(RemoteWorkerController* aController,
const RemoteWorkerData& aData, base::ProcessId aProcessId);
static bool MatchRemoteType(const nsAString& processRemoteType,
const nsAString& workerRemoteType);
/**
* Get the child process RemoteType where a RemoteWorker should be
* launched.
*/
static Result<nsString, nsresult> GetRemoteType(
const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerType aWorkerType);
/**
* Verify if a remote worker should be allowed to run in the current
* child process remoteType.
*/
static bool IsRemoteTypeAllowed(const RemoteWorkerData& aData);
private:
RemoteWorkerManager();
~RemoteWorkerManager();
@ -45,7 +62,7 @@ class RemoteWorkerManager final {
const RemoteWorkerData& aData) const;
RemoteWorkerServiceParent* SelectTargetActorForSharedWorker(
base::ProcessId aProcessId) const;
base::ProcessId aProcessId, const RemoteWorkerData& aData) const;
void LaunchInternal(RemoteWorkerController* aController,
RemoteWorkerServiceParent* aTargetActor,
@ -56,6 +73,9 @@ class RemoteWorkerManager final {
void AsyncCreationFailed(RemoteWorkerController* aController);
static nsString GetRemoteTypeForActor(
const RemoteWorkerServiceParent* aActor);
// Iterate through all RemoteWorkerServiceParent actors, starting from a
// random index (as if iterating through a circular array).
//

View File

@ -70,6 +70,9 @@ struct RemoteWorkerData
OptionalServiceWorkerData serviceWorkerData;
nsID agentClusterId;
// Child process remote type where the worker should only run on.
nsString remoteType;
};
// ErrorData/ErrorDataNote correspond to WorkerErrorReport/WorkerErrorNote

View File

@ -0,0 +1,65 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "gtest/gtest.h"
#include "../RemoteWorkerManager.h"
using namespace mozilla::dom;
TEST(RemoteWorkerManager, TestMatchRemoteType)
{
static const struct {
const nsString processRemoteType;
const nsString workerRemoteType;
const bool shouldMatch;
} tests[] = {
// Test exact matches between process and worker remote types.
{NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), true},
{NS_LITERAL_STRING(EXTENSION_REMOTE_TYPE),
NS_LITERAL_STRING(EXTENSION_REMOTE_TYPE), true},
{NS_LITERAL_STRING(PRIVILEGEDMOZILLA_REMOTE_TYPE),
NS_LITERAL_STRING(PRIVILEGEDMOZILLA_REMOTE_TYPE), true},
// Test workers with remoteType "web" not launched on non-web or coop+coep
// processes.
{NS_LITERAL_STRING(PRIVILEGEDMOZILLA_REMOTE_TYPE),
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), false},
{NS_LITERAL_STRING(PRIVILEGEDABOUT_REMOTE_TYPE),
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), false},
{NS_LITERAL_STRING(EXTENSION_REMOTE_TYPE),
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), false},
{NS_LITERAL_STRING(FILE_REMOTE_TYPE),
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), false},
{NS_LITERAL_STRING(WITH_COOP_COEP_REMOTE_TYPE_PREFIX),
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), false},
// Test workers with remoteType "web" launched in web child processes.
{NS_LITERAL_STRING(LARGE_ALLOCATION_REMOTE_TYPE),
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), true},
{NS_LITERAL_STRING(FISSION_WEB_REMOTE_TYPE),
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), true},
// Test empty remoteType default to "web" remoteType.
{NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), EmptyString(), true},
{NS_LITERAL_STRING(WITH_COOP_COEP_REMOTE_TYPE_PREFIX), EmptyString(),
false},
{NS_LITERAL_STRING(PRIVILEGEDMOZILLA_REMOTE_TYPE), EmptyString(), false},
{NS_LITERAL_STRING(EXTENSION_REMOTE_TYPE), EmptyString(), false},
};
for (const auto& test : tests) {
auto message =
nsPrintfCString(R"(MatchRemoteType("%s", "%s") should return %s)",
NS_ConvertUTF16toUTF8(test.processRemoteType).get(),
NS_ConvertUTF16toUTF8(test.workerRemoteType).get(),
test.shouldMatch ? "true" : "false");
ASSERT_EQ(RemoteWorkerManager::MatchRemoteType(test.processRemoteType,
test.workerRemoteType),
test.shouldMatch)
<< message;
}
}

View File

@ -0,0 +1,13 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
UNIFIED_SOURCES = [
'TestMatchRemoteType.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul-gtest'

View File

@ -9,6 +9,7 @@ EXPORTS.mozilla.dom += [
'RemoteWorkerController.h',
'RemoteWorkerControllerChild.h',
'RemoteWorkerControllerParent.h',
'RemoteWorkerManager.h',
'RemoteWorkerParent.h',
'RemoteWorkerService.h',
'RemoteWorkerServiceChild.h',
@ -39,6 +40,8 @@ IPDL_SOURCES += [
'RemoteWorkerTypes.ipdlh',
]
TEST_DIRS += ['gtest']
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'

View File

@ -13,6 +13,7 @@
#include "mozilla/dom/MessageChannel.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/PMessagePort.h"
#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::GetRemoteType
#include "mozilla/dom/RemoteWorkerTypes.h"
#include "mozilla/dom/SharedWorkerBinding.h"
#include "mozilla/dom/SharedWorkerChild.h"
@ -198,13 +199,21 @@ already_AddRefed<SharedWorker> SharedWorker::Constructor(
MOZ_ASSERT(loadInfo.mCookieJarSettings);
net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings)->Serialize(cjsData);
auto remoteType = RemoteWorkerManager::GetRemoteType(
loadInfo.mPrincipal, WorkerType::WorkerTypeShared);
if (NS_WARN_IF(remoteType.isErr())) {
aRv.Throw(remoteType.unwrapErr());
return nullptr;
}
RemoteWorkerData remoteWorkerData(
nsString(aScriptURL), baseURL, resolvedScriptURL, name,
loadingPrincipalInfo, principalInfo, partitionedPrincipalInfo,
loadInfo.mUseRegularPrincipal,
loadInfo.mHasStorageAccessPermissionGranted, cjsData, loadInfo.mDomain,
isSecureContext, ipcClientInfo, loadInfo.mReferrerInfo, storageAllowed,
void_t() /* OptionalServiceWorkerData */, agentClusterId);
void_t() /* OptionalServiceWorkerData */, agentClusterId,
remoteType.unwrap());
PSharedWorkerChild* pActor = actorChild->SendPSharedWorkerConstructor(
remoteWorkerData, loadInfo.mWindowID, portIdentifier.release());

View File

@ -18,3 +18,7 @@ support-files =
file_use_counter_worker.js
file_use_counter_shared_worker.js
file_use_counter_service_worker.js
[browser_privilegedmozilla_remoteworker.js]
support-files =
file_service_worker.js
file_service_worker_container.html

View File

@ -0,0 +1,117 @@
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", true],
["browser.tabs.remote.separatedMozillaDomains", "example.org"],
["dom.ipc.processCount.web", 1],
["dom.ipc.processCount.privilegedmozilla", 1],
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true],
["dom.serviceworkers.parent_intercept", true],
],
});
});
// This test attempts to verify proper placement of spawned remoteworkers
// by spawning them and then verifying that they were spawned in the expected
// process by way of nsIWorkerDebuggerManager enumeration.
//
// Unfortunately, there's no other way to introspect where a worker global was
// spawned at this time. (devtools just ends up enumerating all workers in all
// processes and we don't want to depend on devtools in this test).
//
// As a result, this test currently only tests situations where it's known that
// a remote worker will be spawned in the same process that is initiating its
// spawning.
//
// This should be enhanced in the future.
add_task(async function test_serviceworker() {
const basePath = "browser/dom/workers/test";
const pagePath = `${basePath}/file_service_worker_container.html`;
const scriptPath = `${basePath}/file_service_worker.js`;
Services.ppmm.releaseCachedProcesses();
async function runWorkerInProcess() {
function getActiveWorkerURLs() {
const wdm = Cc[
"@mozilla.org/dom/workers/workerdebuggermanager;1"
].getService(Ci.nsIWorkerDebuggerManager);
const workerDebuggerUrls = Array.from(
wdm.getWorkerDebuggerEnumerator()
).map(wd => {
return wd.url;
});
return workerDebuggerUrls;
}
return new Promise(resolve => {
content.navigator.serviceWorker.ready.then(({ active }) => {
const { port1, port2 } = new content.MessageChannel();
active.postMessage("webpage->serviceworker", [port2]);
port1.onmessage = evt => {
resolve({
msg: evt.data,
workerUrls: getActiveWorkerURLs(),
});
};
});
}).then(async res => {
// Unregister the service worker used in this test.
const registration = await content.navigator.serviceWorker.ready;
await registration.unregister();
return res;
});
}
const testCaseList = [
// TODO: find a reasonable way to test the non-privileged scenario
// (because more than 1 process is usually available and the worker
// can be launched in a different one from the one where the tab
// is running).
/*{
remoteType: "web",
hostname: "example.com",
},*/
{
remoteType: "privilegedmozilla",
hostname: `example.org`,
},
];
for (const testCase of testCaseList) {
const { remoteType, hostname } = testCase;
info(`Test remote serviceworkers launch selects a ${remoteType} process`);
const tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
url: `https://${hostname}/${pagePath}`,
});
is(
tab.linkedBrowser.remoteType,
remoteType,
`Got the expected remoteType for ${hostname} tab`
);
const results = await SpecialPowers.spawn(
tab.linkedBrowser,
[],
runWorkerInProcess
);
Assert.deepEqual(
results,
{
msg: "serviceworker-reply",
workerUrls: [`https://${hostname}/${scriptPath}`],
},
`Got the expected results for ${hostname} tab`
);
BrowserTestUtils.removeTab(tab);
}
});

View File

@ -0,0 +1,3 @@
self.onmessage = evt => {
evt.ports[0].postMessage("serviceworker-reply");
};

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
(async function () {
await navigator.serviceWorker.register("file_service_worker.js");
await navigator.serviceWorker.ready;
})();
</script>
</head>
<body>
Service Worker Container
</body>
</html>

View File

@ -0,0 +1,57 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// This test attemps to verify that:
// - SharedWorkers can be created and successfully spawned by web extensions
// when web-extensions run in their own child process.
// - SharedWorkers cannot be created by web extensions when web extensions
// are being run in the main process (because SharedWorkers are only
// allowed to be spawned in the parent process if they have a system principal).
add_task(async function test_spawn_shared_worker() {
const background = WebExtensionPolicy.useRemoteWebExtensions
? async function() {
const worker = new SharedWorker("worker.js");
await new Promise(resolve => {
worker.port.onmessage = resolve;
worker.port.postMessage("bgpage->worker");
});
browser.test.sendMessage("test-shared-worker:done");
}
: function() {
// This test covers the builds where the extensions are still
// running in the main process (it just checks that we don't
// allow it).
browser.test.assertThrows(
() => {
try {
new SharedWorker("worker.js");
} catch (e) {
// assertThrows is currently failing to match the error message
// automatically, let's cheat a little bit for now.
throw new Error(`${e}`);
}
},
/NS_ERROR_ABORT/,
"Got the expected failure in non-remote mode"
);
browser.test.sendMessage("test-shared-worker:done");
};
const extension = ExtensionTestUtils.loadExtension({
background,
files: {
"worker.js": function() {
self.onconnect = evt => {
const port = evt.ports[0];
port.onmessage = () => port.postMessage("worker-reply");
};
},
},
});
await extension.startup();
await extension.awaitMessage("test-shared-worker:done");
await extension.unload();
});

View File

@ -140,6 +140,7 @@ skip-if = ccov && os == 'linux' # bug 1607581
[test_ext_same_site_cookies.js]
[test_ext_sandbox_var.js]
[test_ext_schema.js]
[test_ext_shared_workers.js]
[test_ext_shutdown_cleanup.js]
[test_ext_simple.js]
[test_ext_startupData.js]