зеркало из https://github.com/mozilla/gecko-dev.git
499 строки
17 KiB
C++
499 строки
17 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 "WorkerLoadInfo.h"
|
|
#include "WorkerPrivate.h"
|
|
|
|
#include "mozilla/BasePrincipal.h"
|
|
#include "mozilla/dom/nsCSPUtils.h"
|
|
#include "mozilla/dom/BrowserChild.h"
|
|
#include "mozilla/ipc/BackgroundUtils.h"
|
|
#include "mozilla/ipc/PBackgroundSharedTypes.h"
|
|
#include "mozilla/LoadContext.h"
|
|
#include "mozilla/StorageAccess.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsIContentSecurityPolicy.h"
|
|
#include "nsINetworkInterceptController.h"
|
|
#include "nsIProtocolHandler.h"
|
|
#include "nsIBrowserChild.h"
|
|
#include "nsScriptSecurityManager.h"
|
|
#include "nsNetUtil.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace ipc;
|
|
|
|
namespace dom {
|
|
|
|
namespace {
|
|
|
|
class MainThreadReleaseRunnable final : public Runnable {
|
|
nsTArray<nsCOMPtr<nsISupports>> mDoomed;
|
|
nsCOMPtr<nsILoadGroup> mLoadGroupToCancel;
|
|
|
|
public:
|
|
MainThreadReleaseRunnable(nsTArray<nsCOMPtr<nsISupports>>& aDoomed,
|
|
nsCOMPtr<nsILoadGroup>& aLoadGroupToCancel)
|
|
: mozilla::Runnable("MainThreadReleaseRunnable") {
|
|
mDoomed.SwapElements(aDoomed);
|
|
mLoadGroupToCancel.swap(aLoadGroupToCancel);
|
|
}
|
|
|
|
NS_INLINE_DECL_REFCOUNTING_INHERITED(MainThreadReleaseRunnable, Runnable)
|
|
|
|
NS_IMETHOD
|
|
Run() override {
|
|
if (mLoadGroupToCancel) {
|
|
mLoadGroupToCancel->Cancel(NS_BINDING_ABORTED);
|
|
mLoadGroupToCancel = nullptr;
|
|
}
|
|
|
|
mDoomed.Clear();
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
~MainThreadReleaseRunnable() {}
|
|
};
|
|
|
|
// Specialize this if there's some class that has multiple nsISupports bases.
|
|
template <class T>
|
|
struct ISupportsBaseInfo {
|
|
typedef T ISupportsBase;
|
|
};
|
|
|
|
template <template <class> class SmartPtr, class T>
|
|
inline void SwapToISupportsArray(SmartPtr<T>& aSrc,
|
|
nsTArray<nsCOMPtr<nsISupports>>& aDest) {
|
|
nsCOMPtr<nsISupports>* dest = aDest.AppendElement();
|
|
|
|
T* raw = nullptr;
|
|
aSrc.swap(raw);
|
|
|
|
nsISupports* rawSupports =
|
|
static_cast<typename ISupportsBaseInfo<T>::ISupportsBase*>(raw);
|
|
dest->swap(rawSupports);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
WorkerLoadInfoData::WorkerLoadInfoData()
|
|
: mLoadFlags(nsIRequest::LOAD_NORMAL),
|
|
mWindowID(UINT64_MAX),
|
|
mReferrerInfo(new ReferrerInfo(nullptr)),
|
|
mFromWindow(false),
|
|
mEvalAllowed(false),
|
|
mReportCSPViolations(false),
|
|
mXHRParamsAllowed(false),
|
|
mPrincipalIsSystem(false),
|
|
mWatchedByDevtools(false),
|
|
mStorageAccess(StorageAccess::eDeny),
|
|
mFirstPartyStorageAccessGranted(false),
|
|
mServiceWorkersTestingInWindow(false),
|
|
mSecureContext(eNotSet) {}
|
|
|
|
nsresult WorkerLoadInfo::SetPrincipalsAndCSPOnMainThread(
|
|
nsIPrincipal* aPrincipal, nsIPrincipal* aStoragePrincipal,
|
|
nsILoadGroup* aLoadGroup, nsIContentSecurityPolicy* aCsp) {
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadGroup, aPrincipal));
|
|
|
|
mPrincipal = aPrincipal;
|
|
mStoragePrincipal = aStoragePrincipal;
|
|
mPrincipalIsSystem = nsContentUtils::IsSystemPrincipal(aPrincipal);
|
|
|
|
mCSP = aCsp;
|
|
|
|
if (mCSP) {
|
|
mCSP->GetAllowsEval(&mReportCSPViolations, &mEvalAllowed);
|
|
mCSPInfo = new CSPInfo();
|
|
nsresult rv = CSPToCSPInfo(aCsp, mCSPInfo);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
mEvalAllowed = true;
|
|
mReportCSPViolations = false;
|
|
}
|
|
|
|
mLoadGroup = aLoadGroup;
|
|
|
|
mPrincipalInfo = new PrincipalInfo();
|
|
mStoragePrincipalInfo = new PrincipalInfo();
|
|
mOriginAttributes = nsContentUtils::GetOriginAttributes(aLoadGroup);
|
|
|
|
nsresult rv = PrincipalToPrincipalInfo(aPrincipal, mPrincipalInfo);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (aPrincipal->Equals(aStoragePrincipal)) {
|
|
*mStoragePrincipalInfo = *mPrincipalInfo;
|
|
} else {
|
|
mStoragePrincipalInfo = new PrincipalInfo();
|
|
rv = PrincipalToPrincipalInfo(aStoragePrincipal, mStoragePrincipalInfo);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
rv = nsContentUtils::GetUTFOrigin(aPrincipal, mOrigin);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult WorkerLoadInfo::GetPrincipalsAndLoadGroupFromChannel(
|
|
nsIChannel* aChannel, nsIPrincipal** aPrincipalOut,
|
|
nsIPrincipal** aStoragePrincipalOut, nsILoadGroup** aLoadGroupOut) {
|
|
AssertIsOnMainThread();
|
|
MOZ_DIAGNOSTIC_ASSERT(aChannel);
|
|
MOZ_DIAGNOSTIC_ASSERT(aPrincipalOut);
|
|
MOZ_DIAGNOSTIC_ASSERT(aStoragePrincipalOut);
|
|
MOZ_DIAGNOSTIC_ASSERT(aLoadGroupOut);
|
|
|
|
// Initial triggering principal should be set
|
|
NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
|
|
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
|
MOZ_DIAGNOSTIC_ASSERT(ssm);
|
|
|
|
nsCOMPtr<nsIPrincipal> channelPrincipal;
|
|
nsCOMPtr<nsIPrincipal> channelStoragePrincipal;
|
|
nsresult rv = ssm->GetChannelResultPrincipals(
|
|
aChannel, getter_AddRefs(channelPrincipal),
|
|
getter_AddRefs(channelStoragePrincipal));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Every time we call GetChannelResultPrincipal() it will return a different
|
|
// null principal for a data URL. We don't want to change the worker's
|
|
// principal again, though. Instead just keep the original null principal we
|
|
// first got from the channel.
|
|
//
|
|
// Note, we don't do this by setting principalToInherit on the channel's
|
|
// load info because we don't yet have the first null principal when we
|
|
// create the channel.
|
|
if (mPrincipal && mPrincipal->GetIsNullPrincipal() &&
|
|
channelPrincipal->GetIsNullPrincipal()) {
|
|
channelPrincipal = mPrincipal;
|
|
channelStoragePrincipal = mPrincipal;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadGroup> channelLoadGroup;
|
|
rv = aChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
MOZ_ASSERT(channelLoadGroup);
|
|
|
|
// If the loading principal is the system principal then the channel
|
|
// principal must also be the system principal (we do not allow chrome
|
|
// code to create workers with non-chrome scripts, and if we ever decide
|
|
// to change this we need to make sure we don't always set
|
|
// mPrincipalIsSystem to true in WorkerPrivate::GetLoadInfo()). Otherwise
|
|
// this channel principal must be same origin with the load principal (we
|
|
// check again here in case redirects changed the location of the script).
|
|
if (nsContentUtils::IsSystemPrincipal(mLoadingPrincipal)) {
|
|
if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) {
|
|
nsCOMPtr<nsIURI> finalURI;
|
|
rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalURI));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// See if this is a resource URI. Since JSMs usually come from
|
|
// resource:// URIs we're currently considering all URIs with the
|
|
// URI_IS_UI_RESOURCE flag as valid for creating privileged workers.
|
|
bool isResource;
|
|
rv = NS_URIChainHasFlags(finalURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
|
|
&isResource);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (isResource) {
|
|
// Assign the system principal to the resource:// worker only if it
|
|
// was loaded from code using the system principal.
|
|
channelPrincipal = mLoadingPrincipal;
|
|
channelStoragePrincipal = mLoadingPrincipal;
|
|
} else {
|
|
return NS_ERROR_DOM_BAD_URI;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The principal can change, but it should still match the original
|
|
// load group's browser element flag.
|
|
MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(channelLoadGroup, channelPrincipal));
|
|
|
|
channelPrincipal.forget(aPrincipalOut);
|
|
channelStoragePrincipal.forget(aStoragePrincipalOut);
|
|
channelLoadGroup.forget(aLoadGroupOut);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult WorkerLoadInfo::SetPrincipalsAndCSPFromChannel(nsIChannel* aChannel) {
|
|
AssertIsOnMainThread();
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsCOMPtr<nsIPrincipal> storagePrincipal;
|
|
nsCOMPtr<nsILoadGroup> loadGroup;
|
|
nsresult rv = GetPrincipalsAndLoadGroupFromChannel(
|
|
aChannel, getter_AddRefs(principal), getter_AddRefs(storagePrincipal),
|
|
getter_AddRefs(loadGroup));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Workers themselves can have their own CSP - Workers of an opaque origin
|
|
// however inherit the CSP of the document that spawned the worker.
|
|
nsCOMPtr<nsIContentSecurityPolicy> csp;
|
|
if (CSP_ShouldResponseInheritCSP(aChannel)) {
|
|
nsCOMPtr<nsILoadInfo> loadinfo = aChannel->LoadInfo();
|
|
csp = loadinfo->GetCsp();
|
|
}
|
|
return SetPrincipalsAndCSPOnMainThread(principal, storagePrincipal, loadGroup,
|
|
csp);
|
|
}
|
|
|
|
bool WorkerLoadInfo::FinalChannelPrincipalIsValid(nsIChannel* aChannel) {
|
|
AssertIsOnMainThread();
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsCOMPtr<nsIPrincipal> storagePrincipal;
|
|
nsCOMPtr<nsILoadGroup> loadGroup;
|
|
nsresult rv = GetPrincipalsAndLoadGroupFromChannel(
|
|
aChannel, getter_AddRefs(principal), getter_AddRefs(storagePrincipal),
|
|
getter_AddRefs(loadGroup));
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
// Verify that the channel is still a null principal. We don't care
|
|
// if these are the exact same null principal object, though. From
|
|
// the worker's perspective its the same effect.
|
|
if (principal->GetIsNullPrincipal() && mPrincipal->GetIsNullPrincipal()) {
|
|
return true;
|
|
}
|
|
|
|
// Otherwise we require exact equality. Redirects can happen, but they
|
|
// are not allowed to change our principal.
|
|
if (principal->Equals(mPrincipal)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
|
bool WorkerLoadInfo::PrincipalIsValid() const {
|
|
return mPrincipal && mPrincipalInfo &&
|
|
mPrincipalInfo->type() != PrincipalInfo::T__None &&
|
|
mPrincipalInfo->type() <= PrincipalInfo::T__Last &&
|
|
mStoragePrincipal && mStoragePrincipalInfo &&
|
|
mStoragePrincipalInfo->type() != PrincipalInfo::T__None &&
|
|
mStoragePrincipalInfo->type() <= PrincipalInfo::T__Last;
|
|
}
|
|
|
|
bool WorkerLoadInfo::PrincipalURIMatchesScriptURL() {
|
|
AssertIsOnMainThread();
|
|
|
|
nsAutoCString scheme;
|
|
nsresult rv = mBaseURI->GetScheme(scheme);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
// A system principal must either be a blob URL or a resource JSM.
|
|
if (mPrincipal->IsSystemPrincipal()) {
|
|
if (scheme == NS_LITERAL_CSTRING("blob")) {
|
|
return true;
|
|
}
|
|
|
|
bool isResource = false;
|
|
nsresult rv = NS_URIChainHasFlags(
|
|
mBaseURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isResource);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
return isResource;
|
|
}
|
|
|
|
// A null principal can occur for a data URL worker script or a blob URL
|
|
// worker script from a sandboxed iframe.
|
|
if (mPrincipal->GetIsNullPrincipal()) {
|
|
return scheme == NS_LITERAL_CSTRING("data") ||
|
|
scheme == NS_LITERAL_CSTRING("blob");
|
|
}
|
|
|
|
// The principal for a blob: URL worker script does not have a matching URL.
|
|
// This is likely a bug in our referer setting logic, but exempt it for now.
|
|
// This is another reason we should fix bug 1340694 so that referer does not
|
|
// depend on the principal URI.
|
|
if (scheme == NS_LITERAL_CSTRING("blob")) {
|
|
return true;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> principalURI;
|
|
rv = mPrincipal->GetURI(getter_AddRefs(principalURI));
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
NS_ENSURE_TRUE(principalURI, false);
|
|
|
|
if (nsScriptSecurityManager::SecurityCompareURIs(mBaseURI, principalURI)) {
|
|
return true;
|
|
}
|
|
|
|
// If strict file origin policy is in effect, local files will always fail
|
|
// SecurityCompareURIs unless they are identical. Explicitly check file origin
|
|
// policy, in that case.
|
|
if (nsScriptSecurityManager::GetStrictFileOriginPolicy() &&
|
|
NS_URIIsLocalFile(mBaseURI) &&
|
|
NS_RelaxStrictFileOriginPolicy(mBaseURI, principalURI)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
|
|
|
bool WorkerLoadInfo::ProxyReleaseMainThreadObjects(
|
|
WorkerPrivate* aWorkerPrivate) {
|
|
nsCOMPtr<nsILoadGroup> nullLoadGroup;
|
|
return ProxyReleaseMainThreadObjects(aWorkerPrivate, nullLoadGroup);
|
|
}
|
|
|
|
bool WorkerLoadInfo::ProxyReleaseMainThreadObjects(
|
|
WorkerPrivate* aWorkerPrivate, nsCOMPtr<nsILoadGroup>& aLoadGroupToCancel) {
|
|
static const uint32_t kDoomedCount = 11;
|
|
nsTArray<nsCOMPtr<nsISupports>> doomed(kDoomedCount);
|
|
|
|
SwapToISupportsArray(mWindow, doomed);
|
|
SwapToISupportsArray(mScriptContext, doomed);
|
|
SwapToISupportsArray(mBaseURI, doomed);
|
|
SwapToISupportsArray(mResolvedScriptURI, doomed);
|
|
SwapToISupportsArray(mPrincipal, doomed);
|
|
SwapToISupportsArray(mStoragePrincipal, doomed);
|
|
SwapToISupportsArray(mLoadingPrincipal, doomed);
|
|
SwapToISupportsArray(mChannel, doomed);
|
|
SwapToISupportsArray(mCSP, doomed);
|
|
SwapToISupportsArray(mLoadGroup, doomed);
|
|
SwapToISupportsArray(mInterfaceRequestor, doomed);
|
|
// Before adding anything here update kDoomedCount above!
|
|
|
|
MOZ_ASSERT(doomed.Length() == kDoomedCount);
|
|
|
|
RefPtr<MainThreadReleaseRunnable> runnable =
|
|
new MainThreadReleaseRunnable(doomed, aLoadGroupToCancel);
|
|
return NS_SUCCEEDED(aWorkerPrivate->DispatchToMainThread(runnable.forget()));
|
|
}
|
|
|
|
WorkerLoadInfo::InterfaceRequestor::InterfaceRequestor(
|
|
nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
// Look for an existing LoadContext. This is optional and it's ok if
|
|
// we don't find one.
|
|
nsCOMPtr<nsILoadContext> baseContext;
|
|
if (aLoadGroup) {
|
|
nsCOMPtr<nsIInterfaceRequestor> callbacks;
|
|
aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
|
|
if (callbacks) {
|
|
callbacks->GetInterface(NS_GET_IID(nsILoadContext),
|
|
getter_AddRefs(baseContext));
|
|
}
|
|
mOuterRequestor = callbacks;
|
|
}
|
|
|
|
mLoadContext = new LoadContext(aPrincipal, baseContext);
|
|
}
|
|
|
|
void WorkerLoadInfo::InterfaceRequestor::MaybeAddBrowserChild(
|
|
nsILoadGroup* aLoadGroup) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!aLoadGroup) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIInterfaceRequestor> callbacks;
|
|
aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
|
|
if (!callbacks) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIBrowserChild> browserChild;
|
|
callbacks->GetInterface(NS_GET_IID(nsIBrowserChild),
|
|
getter_AddRefs(browserChild));
|
|
if (!browserChild) {
|
|
return;
|
|
}
|
|
|
|
// Use weak references to the tab child. Holding a strong reference will
|
|
// not prevent an ActorDestroy() from being called on the BrowserChild.
|
|
// Therefore, we should let the BrowserChild destroy itself as soon as
|
|
// possible.
|
|
mBrowserChildList.AppendElement(do_GetWeakReference(browserChild));
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WorkerLoadInfo::InterfaceRequestor::GetInterface(const nsIID& aIID,
|
|
void** aSink) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mLoadContext);
|
|
|
|
if (aIID.Equals(NS_GET_IID(nsILoadContext))) {
|
|
nsCOMPtr<nsILoadContext> ref = mLoadContext;
|
|
ref.forget(aSink);
|
|
return NS_OK;
|
|
}
|
|
|
|
// If we still have an active nsIBrowserChild, then return it. Its possible,
|
|
// though, that all of the BrowserChild objects have been destroyed. In that
|
|
// case we return NS_NOINTERFACE.
|
|
if (aIID.Equals(NS_GET_IID(nsIBrowserChild))) {
|
|
nsCOMPtr<nsIBrowserChild> browserChild = GetAnyLiveBrowserChild();
|
|
if (!browserChild) {
|
|
return NS_NOINTERFACE;
|
|
}
|
|
browserChild.forget(aSink);
|
|
return NS_OK;
|
|
}
|
|
|
|
if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) &&
|
|
mOuterRequestor) {
|
|
// If asked for the network intercept controller, ask the outer requestor,
|
|
// which could be the docshell.
|
|
return mOuterRequestor->GetInterface(aIID, aSink);
|
|
}
|
|
|
|
return NS_NOINTERFACE;
|
|
}
|
|
|
|
already_AddRefed<nsIBrowserChild>
|
|
WorkerLoadInfo::InterfaceRequestor::GetAnyLiveBrowserChild() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Search our list of known BrowserChild objects for one that still exists.
|
|
while (!mBrowserChildList.IsEmpty()) {
|
|
nsCOMPtr<nsIBrowserChild> browserChild =
|
|
do_QueryReferent(mBrowserChildList.LastElement());
|
|
|
|
// Does this tab child still exist? If so, return it. We are done. If the
|
|
// PBrowser actor is no longer useful, don't bother returning this tab.
|
|
if (browserChild &&
|
|
!static_cast<BrowserChild*>(browserChild.get())->IsDestroyed()) {
|
|
return browserChild.forget();
|
|
}
|
|
|
|
// Otherwise remove the stale weak reference and check the next one
|
|
mBrowserChildList.RemoveLastElement();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
NS_IMPL_ADDREF(WorkerLoadInfo::InterfaceRequestor)
|
|
NS_IMPL_RELEASE(WorkerLoadInfo::InterfaceRequestor)
|
|
NS_IMPL_QUERY_INTERFACE(WorkerLoadInfo::InterfaceRequestor,
|
|
nsIInterfaceRequestor)
|
|
|
|
WorkerLoadInfo::WorkerLoadInfo() { MOZ_COUNT_CTOR(WorkerLoadInfo); }
|
|
|
|
WorkerLoadInfo::WorkerLoadInfo(WorkerLoadInfo&& aOther) noexcept
|
|
: WorkerLoadInfoData(std::move(aOther)) {
|
|
MOZ_COUNT_CTOR(WorkerLoadInfo);
|
|
}
|
|
|
|
WorkerLoadInfo::~WorkerLoadInfo() { MOZ_COUNT_DTOR(WorkerLoadInfo); }
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|