зеркало из https://github.com/mozilla/gecko-dev.git
420 строки
12 KiB
C++
420 строки
12 KiB
C++
/* -*- 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 "SharedWorker.h"
|
|
|
|
#include "mozilla/AsyncEventDispatcher.h"
|
|
#include "mozilla/EventDispatcher.h"
|
|
#include "mozilla/dom/ClientInfo.h"
|
|
#include "mozilla/dom/Event.h"
|
|
#include "mozilla/dom/MessageChannel.h"
|
|
#include "mozilla/dom/MessagePort.h"
|
|
#include "mozilla/dom/nsCSPUtils.h"
|
|
#include "mozilla/dom/PMessagePort.h"
|
|
#include "mozilla/dom/RemoteWorkerTypes.h"
|
|
#include "mozilla/dom/SharedWorkerBinding.h"
|
|
#include "mozilla/dom/SharedWorkerChild.h"
|
|
#include "mozilla/dom/WorkerBinding.h"
|
|
#include "mozilla/dom/WorkerLoadInfo.h"
|
|
#include "mozilla/dom/WorkerPrivate.h"
|
|
#include "mozilla/ipc/BackgroundChild.h"
|
|
#include "mozilla/ipc/BackgroundUtils.h"
|
|
#include "mozilla/ipc/PBackgroundChild.h"
|
|
#include "mozilla/ipc/URIUtils.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsGlobalWindowInner.h"
|
|
#include "nsPIDOMWindow.h"
|
|
|
|
#ifdef XP_WIN
|
|
#undef PostMessage
|
|
#endif
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::ipc;
|
|
|
|
namespace {
|
|
|
|
nsresult PopulateContentSecurityPolicies(
|
|
nsIContentSecurityPolicy* aCSP,
|
|
nsTArray<ContentSecurityPolicy>& aPolicies) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aCSP);
|
|
MOZ_ASSERT(aPolicies.IsEmpty());
|
|
|
|
uint32_t count = 0;
|
|
nsresult rv = aCSP->GetPolicyCount(&count);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
const nsCSPPolicy* policy = aCSP->GetPolicy(i);
|
|
MOZ_ASSERT(policy);
|
|
|
|
nsAutoString policyString;
|
|
policy->toString(policyString);
|
|
|
|
aPolicies.AppendElement(
|
|
ContentSecurityPolicy(policyString, policy->getReportOnlyFlag(),
|
|
policy->getDeliveredViaMetaTagFlag()));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult PopulateContentSecurityPolicyArray(
|
|
nsIPrincipal* aPrincipal, nsTArray<ContentSecurityPolicy>& policies,
|
|
nsTArray<ContentSecurityPolicy>& preloadPolicies) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
MOZ_ASSERT(policies.IsEmpty());
|
|
MOZ_ASSERT(preloadPolicies.IsEmpty());
|
|
|
|
nsCOMPtr<nsIContentSecurityPolicy> csp;
|
|
nsresult rv = BasePrincipal::Cast(aPrincipal)->GetCsp(getter_AddRefs(csp));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (csp) {
|
|
rv = PopulateContentSecurityPolicies(csp, policies);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = BasePrincipal::Cast(aPrincipal)->GetPreloadCsp(getter_AddRefs(csp));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (csp) {
|
|
rv = PopulateContentSecurityPolicies(csp, preloadPolicies);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
SharedWorker::SharedWorker(nsPIDOMWindowInner* aWindow,
|
|
SharedWorkerChild* aActor, MessagePort* aMessagePort)
|
|
: DOMEventTargetHelper(aWindow),
|
|
mWindow(aWindow),
|
|
mActor(aActor),
|
|
mMessagePort(aMessagePort),
|
|
mFrozen(false) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(aMessagePort);
|
|
}
|
|
|
|
SharedWorker::~SharedWorker() {
|
|
AssertIsOnMainThread();
|
|
Close();
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<SharedWorker> SharedWorker::Constructor(
|
|
const GlobalObject& aGlobal, const nsAString& aScriptURL,
|
|
const StringOrWorkerOptions& aOptions, ErrorResult& aRv) {
|
|
AssertIsOnMainThread();
|
|
|
|
nsCOMPtr<nsPIDOMWindowInner> window =
|
|
do_QueryInterface(aGlobal.GetAsSupports());
|
|
MOZ_ASSERT(window);
|
|
|
|
// If the window is blocked from accessing storage, do not allow it
|
|
// to connect to a SharedWorker. This would potentially allow it
|
|
// to communicate with other windows that do have storage access.
|
|
// Allow private browsing, however, as we handle that isolation
|
|
// via the principal.
|
|
auto storageAllowed = nsContentUtils::StorageAllowedForWindow(window);
|
|
if (storageAllowed != nsContentUtils::StorageAccess::eAllow &&
|
|
storageAllowed != nsContentUtils::StorageAccess::ePrivateBrowsing) {
|
|
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
|
return nullptr;
|
|
}
|
|
|
|
// Assert that the principal private browsing state matches the
|
|
// StorageAccess value.
|
|
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
|
if (storageAllowed == nsContentUtils::StorageAccess::ePrivateBrowsing) {
|
|
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
|
|
nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
|
|
uint32_t privateBrowsingId = 0;
|
|
if (principal) {
|
|
MOZ_ALWAYS_SUCCEEDS(principal->GetPrivateBrowsingId(&privateBrowsingId));
|
|
}
|
|
MOZ_DIAGNOSTIC_ASSERT(privateBrowsingId != 0);
|
|
}
|
|
#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
|
|
|
nsAutoString name;
|
|
if (aOptions.IsString()) {
|
|
name = aOptions.GetAsString();
|
|
} else {
|
|
MOZ_ASSERT(aOptions.IsWorkerOptions());
|
|
name = aOptions.GetAsWorkerOptions().mName;
|
|
}
|
|
|
|
JSContext* cx = aGlobal.Context();
|
|
|
|
WorkerLoadInfo loadInfo;
|
|
aRv = WorkerPrivate::GetLoadInfo(cx, window, nullptr, aScriptURL, false,
|
|
WorkerPrivate::OverrideLoadGroup,
|
|
WorkerTypeShared, &loadInfo);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
PrincipalInfo principalInfo;
|
|
aRv = PrincipalToPrincipalInfo(loadInfo.mPrincipal, &principalInfo);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsTArray<ContentSecurityPolicy> principalCSP;
|
|
nsTArray<ContentSecurityPolicy> principalPreloadCSP;
|
|
aRv = PopulateContentSecurityPolicyArray(loadInfo.mPrincipal, principalCSP,
|
|
principalPreloadCSP);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
PrincipalInfo loadingPrincipalInfo;
|
|
aRv = PrincipalToPrincipalInfo(loadInfo.mLoadingPrincipal,
|
|
&loadingPrincipalInfo);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsTArray<ContentSecurityPolicy> loadingPrincipalCSP;
|
|
nsTArray<ContentSecurityPolicy> loadingPrincipalPreloadCSP;
|
|
aRv = PopulateContentSecurityPolicyArray(loadInfo.mLoadingPrincipal,
|
|
loadingPrincipalCSP,
|
|
loadingPrincipalPreloadCSP);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
// We don't actually care about this MessageChannel, but we use it to 'steal'
|
|
// its 2 connected ports.
|
|
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window);
|
|
RefPtr<MessageChannel> channel = MessageChannel::Constructor(global, aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
MessagePortIdentifier portIdentifier;
|
|
channel->Port1()->CloneAndDisentangle(portIdentifier);
|
|
|
|
URIParams resolvedScriptURL;
|
|
SerializeURI(loadInfo.mResolvedScriptURI, resolvedScriptURL);
|
|
|
|
URIParams baseURL;
|
|
SerializeURI(loadInfo.mBaseURI, baseURL);
|
|
|
|
// Register this component to PBackground.
|
|
PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
|
|
|
|
bool isSecureContext = JS::GetIsSecureContext(js::GetContextRealm(cx));
|
|
|
|
OptionalIPCClientInfo ipcClientInfo;
|
|
Maybe<ClientInfo> clientInfo = window->GetClientInfo();
|
|
if (clientInfo.isSome()) {
|
|
ipcClientInfo = clientInfo.value().ToIPC();
|
|
} else {
|
|
ipcClientInfo = void_t();
|
|
}
|
|
|
|
RemoteWorkerData remoteWorkerData(
|
|
nsString(aScriptURL), baseURL, resolvedScriptURL, name,
|
|
loadingPrincipalInfo, loadingPrincipalCSP, loadingPrincipalPreloadCSP,
|
|
principalInfo, principalCSP, principalPreloadCSP, loadInfo.mDomain,
|
|
isSecureContext, ipcClientInfo, true /* sharedWorker */);
|
|
|
|
PSharedWorkerChild* pActor = actorChild->SendPSharedWorkerConstructor(
|
|
remoteWorkerData, loadInfo.mWindowID, portIdentifier);
|
|
|
|
RefPtr<SharedWorkerChild> actor = static_cast<SharedWorkerChild*>(pActor);
|
|
MOZ_ASSERT(actor);
|
|
|
|
RefPtr<SharedWorker> sharedWorker =
|
|
new SharedWorker(window, actor, channel->Port2());
|
|
|
|
// Let's inform the window about this SharedWorker.
|
|
nsGlobalWindowInner::Cast(window)->StoreSharedWorker(sharedWorker);
|
|
actor->SetParent(sharedWorker);
|
|
|
|
return sharedWorker.forget();
|
|
}
|
|
|
|
MessagePort* SharedWorker::Port() {
|
|
AssertIsOnMainThread();
|
|
return mMessagePort;
|
|
}
|
|
|
|
void SharedWorker::Freeze() {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(!IsFrozen());
|
|
|
|
if (mFrozen) {
|
|
return;
|
|
}
|
|
|
|
mFrozen = true;
|
|
|
|
if (mActor) {
|
|
mActor->SendFreeze();
|
|
}
|
|
}
|
|
|
|
void SharedWorker::Thaw() {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(IsFrozen());
|
|
|
|
if (!mFrozen) {
|
|
return;
|
|
}
|
|
|
|
mFrozen = false;
|
|
|
|
if (mActor) {
|
|
mActor->SendThaw();
|
|
}
|
|
|
|
if (!mFrozenEvents.IsEmpty()) {
|
|
nsTArray<RefPtr<Event>> events;
|
|
mFrozenEvents.SwapElements(events);
|
|
|
|
for (uint32_t index = 0; index < events.Length(); index++) {
|
|
RefPtr<Event>& event = events[index];
|
|
MOZ_ASSERT(event);
|
|
|
|
RefPtr<EventTarget> target = event->GetTarget();
|
|
ErrorResult rv;
|
|
target->DispatchEvent(*event, rv);
|
|
if (rv.Failed()) {
|
|
NS_WARNING("Failed to dispatch event!");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SharedWorker::QueueEvent(Event* aEvent) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aEvent);
|
|
MOZ_ASSERT(IsFrozen());
|
|
|
|
mFrozenEvents.AppendElement(aEvent);
|
|
}
|
|
|
|
void SharedWorker::Close() {
|
|
AssertIsOnMainThread();
|
|
|
|
if (mWindow) {
|
|
nsGlobalWindowInner::Cast(mWindow)->ForgetSharedWorker(this);
|
|
mWindow = nullptr;
|
|
}
|
|
|
|
if (mActor) {
|
|
mActor->SendClose();
|
|
mActor->SetParent(nullptr);
|
|
mActor = nullptr;
|
|
}
|
|
|
|
if (mMessagePort) {
|
|
mMessagePort->Close();
|
|
}
|
|
}
|
|
|
|
void SharedWorker::Suspend() {
|
|
if (mActor) {
|
|
mActor->SendSuspend();
|
|
}
|
|
}
|
|
|
|
void SharedWorker::Resume() {
|
|
if (mActor) {
|
|
mActor->SendResume();
|
|
}
|
|
}
|
|
|
|
void SharedWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
|
|
const Sequence<JSObject*>& aTransferable,
|
|
ErrorResult& aRv) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mMessagePort);
|
|
|
|
mMessagePort->PostMessage(aCx, aMessage, aTransferable, aRv);
|
|
}
|
|
|
|
NS_IMPL_ADDREF_INHERITED(SharedWorker, DOMEventTargetHelper)
|
|
NS_IMPL_RELEASE_INHERITED(SharedWorker, DOMEventTargetHelper)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SharedWorker)
|
|
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(SharedWorker)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SharedWorker,
|
|
DOMEventTargetHelper)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessagePort)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrozenEvents)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SharedWorker,
|
|
DOMEventTargetHelper)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessagePort)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrozenEvents)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
JSObject* SharedWorker::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
AssertIsOnMainThread();
|
|
|
|
return SharedWorker_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
void SharedWorker::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
|
|
AssertIsOnMainThread();
|
|
|
|
if (IsFrozen()) {
|
|
RefPtr<Event> event = aVisitor.mDOMEvent;
|
|
if (!event) {
|
|
event = EventDispatcher::CreateEvent(aVisitor.mEvent->mOriginalTarget,
|
|
aVisitor.mPresContext,
|
|
aVisitor.mEvent, EmptyString());
|
|
}
|
|
|
|
QueueEvent(event);
|
|
|
|
aVisitor.mCanHandle = false;
|
|
aVisitor.SetParentTarget(nullptr, false);
|
|
return;
|
|
}
|
|
|
|
DOMEventTargetHelper::GetEventTargetParent(aVisitor);
|
|
}
|
|
|
|
void SharedWorker::ErrorPropagation(nsresult aError) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mActor);
|
|
MOZ_ASSERT(NS_FAILED(aError));
|
|
|
|
RefPtr<AsyncEventDispatcher> errorEvent = new AsyncEventDispatcher(
|
|
this, NS_LITERAL_STRING("error"), CanBubble::eNo);
|
|
errorEvent->PostDOMEvent();
|
|
|
|
Close();
|
|
}
|