diff --git a/caps/DomainPolicy.cpp b/caps/DomainPolicy.cpp index ec0230cbe3..dcdad44e61 100644 --- a/caps/DomainPolicy.cpp +++ b/caps/DomainPolicy.cpp @@ -5,17 +5,50 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DomainPolicy.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/unused.h" +#include "nsIMessageManager.h" #include "nsScriptSecurityManager.h" namespace mozilla { +using namespace ipc; +using namespace dom; + NS_IMPL_ISUPPORTS(DomainPolicy, nsIDomainPolicy) -DomainPolicy::DomainPolicy() : mBlacklist(new DomainSet()) - , mSuperBlacklist(new DomainSet()) - , mWhitelist(new DomainSet()) - , mSuperWhitelist(new DomainSet()) -{} +static nsresult +BroadcastDomainSetChange(DomainSetType aSetType, DomainSetChangeType aChangeType, + nsIURI* aDomain = nullptr) +{ + MOZ_ASSERT(XRE_GetProcessType() == GoannaProcessType_Default, + "DomainPolicy should only be exposed to the chrome process."); + + nsTArray parents; + ContentParent::GetAll(parents); + if (!parents.Length()) { + return NS_OK; + } + + OptionalURIParams uri; + SerializeURI(aDomain, uri); + + for (uint32_t i = 0; i < parents.Length(); i++) { + unused << parents[i]->SendDomainSetChanged(aSetType, aChangeType, uri); + } + return NS_OK; +} + +DomainPolicy::DomainPolicy() : mBlacklist(new DomainSet(BLACKLIST)) + , mSuperBlacklist(new DomainSet(SUPER_BLACKLIST)) + , mWhitelist(new DomainSet(WHITELIST)) + , mSuperWhitelist(new DomainSet(SUPER_WHITELIST)) +{ + if (XRE_GetProcessType() == GoannaProcessType_Default) { + BroadcastDomainSetChange(NO_TYPE, ACTIVATE_POLICY); + } +} DomainPolicy::~DomainPolicy() { @@ -75,10 +108,47 @@ DomainPolicy::Deactivate() mSuperWhitelist = nullptr; // Inform the SSM. - nsScriptSecurityManager::GetScriptSecurityManager()->DeactivateDomainPolicy(); + nsScriptSecurityManager* ssm = nsScriptSecurityManager::GetScriptSecurityManager(); + if (ssm) { + ssm->DeactivateDomainPolicy(); + } + if (XRE_GetProcessType() == GoannaProcessType_Default) { + BroadcastDomainSetChange(NO_TYPE, DEACTIVATE_POLICY); + } return NS_OK; } +void +DomainPolicy::CloneDomainPolicy(DomainPolicyClone* aClone) +{ + aClone->active() = true; + static_cast(mBlacklist.get())->CloneSet(&aClone->blacklist()); + static_cast(mSuperBlacklist.get())->CloneSet(&aClone->superBlacklist()); + static_cast(mWhitelist.get())->CloneSet(&aClone->whitelist()); + static_cast(mSuperWhitelist.get())->CloneSet(&aClone->superWhitelist()); +} + +static +void +CopyURIs(const InfallibleTArray& aDomains, nsIDomainSet* aSet) +{ + for (uint32_t i = 0; i < aDomains.Length(); i++) { + nsCOMPtr uri = DeserializeURI(aDomains[i]); + aSet->Add(uri); + } +} + +void +DomainPolicy::ApplyClone(DomainPolicyClone* aClone) +{ + nsCOMPtr list; + + CopyURIs(aClone->blacklist(), mBlacklist); + CopyURIs(aClone->whitelist(), mWhitelist); + CopyURIs(aClone->superBlacklist(), mSuperBlacklist); + CopyURIs(aClone->superWhitelist(), mSuperWhitelist); +} + static already_AddRefed GetCanonicalClone(nsIURI* aURI) { @@ -100,6 +170,9 @@ DomainSet::Add(nsIURI* aDomain) nsCOMPtr clone = GetCanonicalClone(aDomain); NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); mHashTable.PutEntry(clone); + if (XRE_GetProcessType() == GoannaProcessType_Default) + return BroadcastDomainSetChange(mType, ADD_DOMAIN, aDomain); + return NS_OK; } @@ -109,6 +182,9 @@ DomainSet::Remove(nsIURI* aDomain) nsCOMPtr clone = GetCanonicalClone(aDomain); NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); mHashTable.RemoveEntry(clone); + if (XRE_GetProcessType() == GoannaProcessType_Default) + return BroadcastDomainSetChange(mType, REMOVE_DOMAIN, aDomain); + return NS_OK; } @@ -116,6 +192,9 @@ NS_IMETHODIMP DomainSet::Clear() { mHashTable.Clear(); + if (XRE_GetProcessType() == GoannaProcessType_Default) + return BroadcastDomainSetChange(mType, CLEAR_DOMAINS); + return NS_OK; } @@ -160,4 +239,31 @@ DomainSet::ContainsSuperDomain(nsIURI* aDomain, bool* aContains) } +NS_IMETHODIMP +DomainSet::GetType(uint32_t* aType) +{ + *aType = mType; + return NS_OK; +} + +static +PLDHashOperator +DomainEnumerator(nsURIHashKey* aEntry, void* aUserArg) +{ + InfallibleTArray* uris = static_cast*>(aUserArg); + nsIURI* key = aEntry->GetKey(); + + URIParams uri; + SerializeURI(key, uri); + + uris->AppendElement(uri); + return PL_DHASH_NEXT; +} + +void +DomainSet::CloneSet(InfallibleTArray* aDomains) +{ + mHashTable.EnumerateEntries(DomainEnumerator, aDomains); +} + } /* namespace mozilla */ diff --git a/caps/DomainPolicy.h b/caps/DomainPolicy.h index 43c1726d00..eea89d476f 100644 --- a/caps/DomainPolicy.h +++ b/caps/DomainPolicy.h @@ -13,6 +13,30 @@ namespace mozilla { +namespace dom { +class nsIContentParent; +}; + +namespace ipc { +class URIParams; +}; + +enum DomainSetChangeType{ + ACTIVATE_POLICY, + DEACTIVATE_POLICY, + ADD_DOMAIN, + REMOVE_DOMAIN, + CLEAR_DOMAINS +}; + +enum DomainSetType{ + NO_TYPE, + BLACKLIST, + SUPER_BLACKLIST, + WHITELIST, + SUPER_WHITELIST +}; + class DomainPolicy : public nsIDomainPolicy { public: @@ -35,11 +59,16 @@ public: NS_DECL_ISUPPORTS NS_DECL_NSIDOMAINSET - DomainSet() {} + explicit DomainSet(DomainSetType aType) + : mType(aType) + {} + + void CloneSet(InfallibleTArray* aDomains); protected: virtual ~DomainSet() {} nsTHashtable mHashTable; + DomainSetType mType; }; } /* namespace mozilla */ diff --git a/caps/nsIDomainPolicy.idl b/caps/nsIDomainPolicy.idl index e77c45681c..54b0de4dbf 100644 --- a/caps/nsIDomainPolicy.idl +++ b/caps/nsIDomainPolicy.idl @@ -8,6 +8,16 @@ interface nsIURI; interface nsIDomainSet; +%{ C++ +namespace mozilla { +namespace dom { +class DomainPolicyClone; +} +} +%} + +[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone); + /* * When a domain policy is instantiated by invoking activateDomainPolicy() on * nsIScriptSecurityManager, these domain sets are consulted when each new @@ -20,7 +30,7 @@ interface nsIDomainSet; * When deactivate() is invoked, the domain sets are emptied, and the * nsIDomainPolicy ceases to have any effect on the system. */ -[scriptable, builtinclass, uuid(27b10f54-f34b-42b7-8594-4348d3ad7953)] +[scriptable, builtinclass, uuid(82b24a20-6701-4d40-a0f9-f5dc7321b555)] interface nsIDomainPolicy : nsISupports { readonly attribute nsIDomainSet blacklist; @@ -29,11 +39,19 @@ interface nsIDomainPolicy : nsISupports readonly attribute nsIDomainSet superWhitelist; void deactivate(); + + [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone); + [noscript, notxpcom] void applyClone(in DomainPolicyClonePtr aClone); }; -[scriptable, builtinclass, uuid(946a01ff-6525-4007-a2c2-447ebe1875d3)] +[scriptable, builtinclass, uuid(665c981b-0a0f-4229-ac06-a826e02d4f69)] interface nsIDomainSet : nsISupports { + /* + * The type of the set. See: DomainSetType + */ + [noscript] readonly attribute uint32_t type; + /* * Add a domain to the set. No-op if it already exists. */ diff --git a/caps/nsIScriptSecurityManager.idl b/caps/nsIScriptSecurityManager.idl index 33503a33ef..76465abc70 100644 --- a/caps/nsIScriptSecurityManager.idl +++ b/caps/nsIScriptSecurityManager.idl @@ -14,12 +14,19 @@ interface nsILoadContext; %{ C++ #include "jspubtd.h" + +namespace mozilla { +namespace dom { +class DomainPolicyClone; +} +} %} [ptr] native JSContextPtr(JSContext); [ptr] native JSObjectPtr(JSObject); +[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone); -[scriptable, uuid(f649959d-dae3-4027-83fd-5b7f8c8a8815)] +[scriptable, uuid(ba602ca6-dc7a-457e-a57a-ee5b343fd863)] interface nsIScriptSecurityManager : nsISupports { /** @@ -240,6 +247,22 @@ interface nsIScriptSecurityManager : nsISupports nsIDomainPolicy activateDomainPolicy(); readonly attribute boolean domainPolicyActive; + /** + * Only the parent process can directly access domain policies, child + * processes only have a read-only mirror to the one in the parent. + * For child processes the mirror is updated via messages + * and ContentChild will hold the DomainPolicy by calling + * ActivateDomainPolicyInternal directly. New consumer to this + * function should not be addded. + */ + [noscript] nsIDomainPolicy activateDomainPolicyInternal(); + + /** + * This function is for internal use only. Every time a child process is spawned, we + * must clone any active domain policies in the parent to the new child. + */ + [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone); + /** * Query mechanism for the above policy. * diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp index d5a3692ad9..7d7755fb81 100644 --- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -1320,9 +1320,14 @@ static StaticRefPtr gScriptSecMan; nsScriptSecurityManager::~nsScriptSecurityManager(void) { Preferences::RemoveObservers(this, kObservedPrefs); - if (mDomainPolicy) + if (mDomainPolicy) { mDomainPolicy->Deactivate(); - MOZ_ASSERT(!mDomainPolicy); + } + // ContentChild might hold a reference to the domain policy, + // and it might release it only after the security manager is + // gone. But we can still assert this for the main process. + MOZ_ASSERT_IF(XRE_GetProcessType() == GoannaProcessType_Default, + !mDomainPolicy); } void @@ -1537,6 +1542,16 @@ nsScriptSecurityManager::GetDomainPolicyActive(bool *aRv) NS_IMETHODIMP nsScriptSecurityManager::ActivateDomainPolicy(nsIDomainPolicy** aRv) +{ + if (XRE_GetProcessType() != GoannaProcessType_Default) { + return NS_ERROR_SERVICE_NOT_AVAILABLE; + } + + return ActivateDomainPolicyInternal(aRv); +} + +NS_IMETHODIMP +nsScriptSecurityManager::ActivateDomainPolicyInternal(nsIDomainPolicy** aRv) { // We only allow one domain policy at a time. The holder of the previous // policy must explicitly deactivate it first. @@ -1558,6 +1573,17 @@ nsScriptSecurityManager::DeactivateDomainPolicy() mDomainPolicy = nullptr; } +void +nsScriptSecurityManager::CloneDomainPolicy(DomainPolicyClone* aClone) +{ + MOZ_ASSERT(aClone); + if (mDomainPolicy) { + mDomainPolicy->CloneDomainPolicy(aClone); + } else { + aClone->active() = false; + } +} + NS_IMETHODIMP nsScriptSecurityManager::PolicyAllowsScript(nsIURI* aURI, bool *aRv) { diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 278bdfddb1..6a7932a445 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -13154,8 +13154,8 @@ nsGlobalWindow::SuspendTimeouts(uint32_t aIncrease, } DisableGamepadUpdates(); - // Suspend all of the workers for this window. - mozilla::dom::workers::SuspendWorkersForWindow(this); + // Freeze all of the workers for this window. + mozilla::dom::workers::FreezeWorkersForWindow(this); TimeStamp now = TimeStamp::Now(); for (nsTimeout *t = mTimeouts.getFirst(); t; t = t->getNext()) { @@ -13243,8 +13243,8 @@ nsGlobalWindow::ResumeTimeouts(bool aThawChildren) mAudioContexts[i]->Resume(); } - // Resume all of the workers for this window. - mozilla::dom::workers::ResumeWorkersForWindow(this); + // Thaw all of the workers for this window. + mozilla::dom::workers::ThawWorkersForWindow(this); // Restore all of the timeouts, using the stored time remaining // (stored in timeout->mTimeRemaining). diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 845e1c8e67..8d51f3289c 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -1513,7 +1513,7 @@ DOMInterfaces = { 'headerFile': 'mozilla/dom/WorkerScope.h', 'nativeType': 'mozilla::dom::workers::WorkerDebuggerGlobalScope', 'implicitJSContext': [ - 'dump', 'global', + 'dump', 'global', 'setImmediate', 'reportError', ], }, diff --git a/dom/cache/Cache.cpp b/dom/cache/Cache.cpp index 8d33362491..c017705152 100644 --- a/dom/cache/Cache.cpp +++ b/dom/cache/Cache.cpp @@ -93,6 +93,7 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(mozilla::dom::cache::Cache) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Cache) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END Cache::Cache(nsIGlobalObject* aGlobal, CacheChild* aActor) diff --git a/dom/cache/CacheStorage.cpp b/dom/cache/CacheStorage.cpp index d576440233..39cef3d87d 100644 --- a/dom/cache/CacheStorage.cpp +++ b/dom/cache/CacheStorage.cpp @@ -55,6 +55,7 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(mozilla::dom::cache::CacheStorage) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CacheStorage) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIIPCBackgroundChildCreateCallback) NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback) NS_INTERFACE_MAP_END diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 286e9858e0..dc6897efd2 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -27,6 +27,7 @@ #include "mozilla/docshell/OfflineCacheUpdateChild.h" #include "mozilla/dom/ContentBridgeChild.h" #include "mozilla/dom/ContentBridgeParent.h" +#include "mozilla/dom/ContentParent.h" #include "mozilla/dom/DOMStorageIPC.h" #include "mozilla/dom/ExternalHelperAppChild.h" #include "mozilla/dom/ProcessGlobal.h" @@ -108,8 +109,9 @@ #include "mozilla/dom/PMemoryReportRequestChild.h" #include "mozilla/dom/PCycleCollectWithLogsChild.h" -#ifdef MOZ_PERMISSIONS #include "nsIScriptSecurityManager.h" + +#ifdef MOZ_PERMISSIONS #include "nsPermission.h" #include "nsPermissionManager.h" #endif @@ -166,6 +168,7 @@ #include "nsIPrincipal.h" #include "nsDeviceStorage.h" #include "AudioChannelService.h" +#include "DomainPolicy.h" #include "mozilla/dom/DataStoreService.h" #include "mozilla/dom/telephony/PTelephonyChild.h" #include "mozilla/dom/time/DateCacheCleaner.h" @@ -739,9 +742,21 @@ ContentChild::InitXPCOM() bool isOffline; ClipboardCapabilities clipboardCaps; - SendGetXPCOMProcessAttributes(&isOffline, &mAvailableDictionaries, &clipboardCaps); + DomainPolicyClone domainPolicy; + + SendGetXPCOMProcessAttributes(&isOffline, &mAvailableDictionaries, &clipboardCaps, &domainPolicy); RecvSetOffline(isOffline); + if (domainPolicy.active()) { + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + MOZ_ASSERT(ssm); + ssm->ActivateDomainPolicyInternal(getter_AddRefs(mPolicy)); + if (!mPolicy) { + MOZ_CRASH("Failed to activate domain policy."); + } + mPolicy->ApplyClone(&domainPolicy); + } + nsCOMPtr clipboard(do_GetService("@mozilla.org/widget/clipboard;1")); if (nsCOMPtr clipboardProxy = do_QueryInterface(clipboard)) { clipboardProxy->SetCapabilities(clipboardCaps); @@ -2528,9 +2543,83 @@ ContentChild::RecvAssociatePluginId(const uint32_t& aPluginId, return true; } +bool +ContentChild::RecvDomainSetChanged(const uint32_t& aSetType, const uint32_t& aChangeType, + const OptionalURIParams& aDomain) +{ + if (aChangeType == ACTIVATE_POLICY) { + if (mPolicy) { + return true; + } + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + MOZ_ASSERT(ssm); + ssm->ActivateDomainPolicyInternal(getter_AddRefs(mPolicy)); + return !!mPolicy; + } else if (!mPolicy) { + MOZ_ASSERT_UNREACHABLE("If the domain policy is not active yet," + " the first message should be ACTIVATE_POLICY"); + return false; + } + + NS_ENSURE_TRUE(mPolicy, false); + + if (aChangeType == DEACTIVATE_POLICY) { + mPolicy->Deactivate(); + mPolicy = nullptr; + return true; + } + + nsCOMPtr set; + switch(aSetType) { + case BLACKLIST: + mPolicy->GetBlacklist(getter_AddRefs(set)); + break; + case SUPER_BLACKLIST: + mPolicy->GetSuperBlacklist(getter_AddRefs(set)); + break; + case WHITELIST: + mPolicy->GetWhitelist(getter_AddRefs(set)); + break; + case SUPER_WHITELIST: + mPolicy->GetSuperWhitelist(getter_AddRefs(set)); + break; + default: + NS_NOTREACHED("Unexpected setType"); + return false; + } + + MOZ_ASSERT(set); + + nsCOMPtr uri = DeserializeURI(aDomain); + + switch(aChangeType) { + case ADD_DOMAIN: + NS_ENSURE_TRUE(uri, false); + set->Add(uri); + break; + case REMOVE_DOMAIN: + NS_ENSURE_TRUE(uri, false); + set->Remove(uri); + break; + case CLEAR_DOMAINS: + set->Clear(); + break; + default: + NS_NOTREACHED("Unexpected changeType"); + return false; + } + + return true; +} + bool ContentChild::RecvShutdown() { + if (mPolicy) { + mPolicy->Deactivate(); + mPolicy = nullptr; + } + nsCOMPtr os = services::GetObserverService(); if (os) { os->NotifyObservers(this, "content-child-shutdown", nullptr); diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index eee1985a5d..bc1fee1224 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -24,6 +24,7 @@ class nsIDOMBlob; class nsIObserver; struct ResourceMapping; struct OverrideMapping; +class nsIDomainPolicy; namespace mozilla { class RemoteSpellcheckEngineChild; @@ -378,6 +379,8 @@ public: nsTArray&& aThreadNameFilters) override; virtual bool RecvStopProfiler() override; virtual bool RecvGetProfile(nsCString* aProfile) override; + virtual bool RecvDomainSetChanged(const uint32_t& aSetType, const uint32_t& aChangeType, + const OptionalURIParams& aDomain) override; virtual bool RecvShutdown() override; #ifdef ANDROID @@ -475,6 +478,8 @@ private: static ContentChild* sSingleton; + nsCOMPtr mPolicy; + DISALLOW_EVIL_CONSTRUCTORS(ContentChild); }; diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 0a8e0879bf..cece4e4190 100755 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -2710,8 +2710,9 @@ ContentParent::RecvAddNewProcess(const uint32_t& aPid, bool isOffline; InfallibleTArray unusedDictionaries; ClipboardCapabilities clipboardCaps; + DomainPolicyClone domainPolicy; RecvGetXPCOMProcessAttributes(&isOffline, &unusedDictionaries, - &clipboardCaps); + &clipboardCaps, &domainPolicy); mozilla::unused << content->SendSetOffline(isOffline); MOZ_ASSERT(!clipboardCaps.supportsSelectionClipboard() && !clipboardCaps.supportsFindClipboard(), @@ -3022,7 +3023,8 @@ ContentParent::RecvGetProcessAttributes(ContentParentId* aCpId, bool ContentParent::RecvGetXPCOMProcessAttributes(bool* aIsOffline, InfallibleTArray* dictionaries, - ClipboardCapabilities* clipboardCaps) + ClipboardCapabilities* clipboardCaps, + DomainPolicyClone* domainPolicy) { nsCOMPtr io(do_GetIOService()); MOZ_ASSERT(io, "No IO service?"); @@ -3043,6 +3045,11 @@ ContentParent::RecvGetXPCOMProcessAttributes(bool* aIsOffline, rv = clipboard->SupportsFindClipboard(&clipboardCaps->supportsFindClipboard()); MOZ_ASSERT(NS_SUCCEEDED(rv)); + // Let's copy the domain policy from the parent to the child (if it's active). + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + NS_ENSURE_TRUE(ssm, false); + ssm->CloneDomainPolicy(domainPolicy); + return true; } diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index d1cd8b43ce..c79260bf31 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -497,7 +497,8 @@ private: bool* aIsForBrowser) override; virtual bool RecvGetXPCOMProcessAttributes(bool* aIsOffline, InfallibleTArray* dictionaries, - ClipboardCapabilities* clipboardCaps) + ClipboardCapabilities* clipboardCaps, + DomainPolicyClone* domainPolicy) override; virtual bool DeallocPJavaScriptParent(mozilla::jsipc::PJavaScriptParent*) override; diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 151e53676f..2495887d60 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -345,6 +345,15 @@ union OptionalContentId void_t; }; +struct DomainPolicyClone +{ + bool active; + URIParams[] blacklist; + URIParams[] whitelist; + URIParams[] superBlacklist; + URIParams[] superWhitelist; +}; + prio(normal upto urgent) sync protocol PContent { parent spawns PPluginModule; @@ -550,6 +559,8 @@ child: NuwaFreeze(); + async DomainSetChanged(uint32_t aSetType, uint32_t aChangeType, OptionalURIParams aDomain); + /** * Notify the child to shutdown. The child will in turn call FinishShutdown * and let the parent close the channel. @@ -584,7 +595,8 @@ parent: returns (ContentParentId cpId, bool isForApp, bool isForBrowser); sync GetXPCOMProcessAttributes() returns (bool isOffline, nsString[] dictionaries, - ClipboardCapabilities clipboardCaps); + ClipboardCapabilities clipboardCaps, + DomainPolicyClone domainPolicy); sync CreateChildProcess(IPCTabContext context, ProcessPriority priority, diff --git a/dom/ipc/moz.build b/dom/ipc/moz.build index 8bde442a02..9327f37cdb 100644 --- a/dom/ipc/moz.build +++ b/dom/ipc/moz.build @@ -147,6 +147,7 @@ for var in ('MOZ_PERMISSIONS', 'MOZ_CHILD_PERMISSIONS'): JAR_MANIFESTS += ['jar.mn'] +BROWSER_CHROME_MANIFESTS += ['tests/browser.ini'] MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini'] MOCHITEST_MANIFESTS += ['tests/mochitest.ini'] diff --git a/dom/webidl/WorkerDebuggerGlobalScope.webidl b/dom/webidl/WorkerDebuggerGlobalScope.webidl index 7f3fd45fb5..9ee1a89bd8 100644 --- a/dom/webidl/WorkerDebuggerGlobalScope.webidl +++ b/dom/webidl/WorkerDebuggerGlobalScope.webidl @@ -6,6 +6,24 @@ [Global=(WorkerDebugger), Exposed=WorkerDebugger] interface WorkerDebuggerGlobalScope : EventTarget { readonly attribute object global; + + object createSandbox(DOMString name, object prototype); + + [Throws] + void loadSubScript(DOMString url, optional object sandbox); + + void enterEventLoop(); + + void leaveEventLoop(); + + void postMessage(DOMString message); + + attribute EventHandler onmessage; + + [Throws] + void setImmediate(Function handler); + + void reportError(DOMString message); }; // So you can debug while you debug diff --git a/dom/workers/MessagePort.cpp b/dom/workers/MessagePort.cpp index 6010000bad..4a693ea926 100644 --- a/dom/workers/MessagePort.cpp +++ b/dom/workers/MessagePort.cpp @@ -280,7 +280,7 @@ MessagePort::PreHandleEvent(EventChainPreVisitor& aVisitor) if (IsClosed()) { preventDispatch = true; - } else if (NS_IsMainThread() && mSharedWorker->IsSuspended()) { + } else if (NS_IsMainThread() && mSharedWorker->IsFrozen()) { mSharedWorker->QueueEvent(event); preventDispatch = true; } else if (!mStarted) { diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index 3b556882ef..61adb46e78 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -60,6 +60,7 @@ #include "Principal.h" #include "SharedWorker.h" +#include "WorkerDebuggerManager.h" #include "WorkerPrivate.h" #include "WorkerRunnable.h" #include "WorkerThread.h" @@ -870,14 +871,14 @@ JSObject* Wrap(JSContext *cx, JS::HandleObject existing, JS::HandleObject obj) { JSObject* targetGlobal = JS::CurrentGlobalOrNull(cx); - if (!IsDebuggerGlobal(targetGlobal)) { + if (!IsDebuggerGlobal(targetGlobal) && !IsDebuggerSandbox(targetGlobal)) { MOZ_CRASH("There should be no edges from the debuggee to the debugger."); } JSObject* originGlobal = js::GetGlobalForObjectCrossCompartment(obj); const js::Wrapper* wrapper = nullptr; - if (IsDebuggerGlobal(originGlobal)) { + if (IsDebuggerGlobal(originGlobal) || IsDebuggerSandbox(originGlobal)) { wrapper = &js::CrossCompartmentWrapper::singleton; } else { if (obj != originGlobal) { @@ -1243,22 +1244,22 @@ CancelWorkersForWindow(nsPIDOMWindow* aWindow) } void -SuspendWorkersForWindow(nsPIDOMWindow* aWindow) +FreezeWorkersForWindow(nsPIDOMWindow* aWindow) { AssertIsOnMainThread(); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { - runtime->SuspendWorkersForWindow(aWindow); + runtime->FreezeWorkersForWindow(aWindow); } } void -ResumeWorkersForWindow(nsPIDOMWindow* aWindow) +ThawWorkersForWindow(nsPIDOMWindow* aWindow) { AssertIsOnMainThread(); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { - runtime->ResumeWorkersForWindow(aWindow); + runtime->ThawWorkersForWindow(aWindow); } } @@ -1918,6 +1919,12 @@ RuntimeService::Shutdown() // That's it, no more workers. mShuttingDown = true; + // Remove all listeners from the worker debugger manager to ensure that it + // gets properly destroyed. + if (NS_FAILED(ClearWorkerDebuggerManagerListeners())) { + NS_WARNING("Failed to clear worker debugger manager listeners!"); + } + nsCOMPtr obs = services::GetObserverService(); NS_WARN_IF_FALSE(obs, "Failed to get observer service?!"); @@ -2211,7 +2218,7 @@ RuntimeService::CancelWorkersForWindow(nsPIDOMWindow* aWindow) } void -RuntimeService::SuspendWorkersForWindow(nsPIDOMWindow* aWindow) +RuntimeService::FreezeWorkersForWindow(nsPIDOMWindow* aWindow) { AssertIsOnMainThread(); MOZ_ASSERT(aWindow); @@ -2227,7 +2234,7 @@ RuntimeService::SuspendWorkersForWindow(nsPIDOMWindow* aWindow) JSContext* cx = jsapi.cx(); for (uint32_t index = 0; index < workers.Length(); index++) { - if (!workers[index]->Suspend(cx, aWindow)) { + if (!workers[index]->Freeze(cx, aWindow)) { JS_ReportPendingException(cx); } } @@ -2235,7 +2242,7 @@ RuntimeService::SuspendWorkersForWindow(nsPIDOMWindow* aWindow) } void -RuntimeService::ResumeWorkersForWindow(nsPIDOMWindow* aWindow) +RuntimeService::ThawWorkersForWindow(nsPIDOMWindow* aWindow) { AssertIsOnMainThread(); MOZ_ASSERT(aWindow); @@ -2251,7 +2258,7 @@ RuntimeService::ResumeWorkersForWindow(nsPIDOMWindow* aWindow) JSContext* cx = jsapi.cx(); for (uint32_t index = 0; index < workers.Length(); index++) { - if (!workers[index]->SynchronizeAndResume(cx, aWindow)) { + if (!workers[index]->Thaw(cx, aWindow)) { JS_ReportPendingException(cx); } } diff --git a/dom/workers/RuntimeService.h b/dom/workers/RuntimeService.h index 00d598b681..8bd027d170 100644 --- a/dom/workers/RuntimeService.h +++ b/dom/workers/RuntimeService.h @@ -129,10 +129,10 @@ public: CancelWorkersForWindow(nsPIDOMWindow* aWindow); void - SuspendWorkersForWindow(nsPIDOMWindow* aWindow); + FreezeWorkersForWindow(nsPIDOMWindow* aWindow); void - ResumeWorkersForWindow(nsPIDOMWindow* aWindow); + ThawWorkersForWindow(nsPIDOMWindow* aWindow); nsresult CreateSharedWorker(const GlobalObject& aGlobal, diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp index caa3df9c26..aa3b8ac97c 100644 --- a/dom/workers/ScriptLoader.cpp +++ b/dom/workers/ScriptLoader.cpp @@ -53,7 +53,8 @@ ChannelFromScriptURL(nsIPrincipal* principal, nsIIOService* ios, nsIScriptSecurityManager* secMan, const nsAString& aScriptURL, - bool aIsWorkerScript, + bool aIsMainScript, + WorkerScriptType aWorkerScriptType, nsIChannel** aChannel) { AssertIsOnMainThread(); @@ -84,10 +85,22 @@ ChannelFromScriptURL(nsIPrincipal* principal, } } - // If this script loader is being used to make a new worker then we need - // to do a same-origin check. Otherwise we need to clear the load with the - // security manager. - if (aIsWorkerScript) { + if (aWorkerScriptType == DebuggerScript) { + bool isChrome = false; + NS_ENSURE_SUCCESS(uri->SchemeIs("chrome", &isChrome), + NS_ERROR_DOM_SECURITY_ERR); + + bool isResource = false; + NS_ENSURE_SUCCESS(uri->SchemeIs("resource", &isResource), + NS_ERROR_DOM_SECURITY_ERR); + + if (!isChrome && !isResource) { + return NS_ERROR_DOM_SECURITY_ERR; + } + } else if (aIsMainScript) { + // If this script loader is being used to make a new worker then we need + // to do a same-origin check. Otherwise we need to clear the load with the + // security manager. nsCString scheme; rv = uri->GetScheme(scheme); NS_ENSURE_SUCCESS(rv, rv); @@ -190,6 +203,9 @@ private: ~ScriptExecutorRunnable() { } + virtual bool + IsDebuggerRunnable() const override; + virtual bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override; @@ -214,7 +230,8 @@ class ScriptLoaderRunnable final : public WorkerFeature, WorkerPrivate* mWorkerPrivate; nsCOMPtr mSyncLoopTarget; nsTArray mLoadInfos; - bool mIsWorkerScript; + bool mIsMainScript; + WorkerScriptType mWorkerScriptType; bool mCanceled; bool mCanceledMainThread; @@ -224,14 +241,15 @@ public: ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aSyncLoopTarget, nsTArray& aLoadInfos, - bool aIsWorkerScript) + bool aIsMainScript, + WorkerScriptType aWorkerScriptType) : mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget), - mIsWorkerScript(aIsWorkerScript), mCanceled(false), - mCanceledMainThread(false) + mIsMainScript(aIsMainScript), mWorkerScriptType(aWorkerScriptType), + mCanceled(false), mCanceledMainThread(false) { aWorkerPrivate->AssertIsOnWorkerThread(); MOZ_ASSERT(aSyncLoopTarget); - MOZ_ASSERT_IF(aIsWorkerScript, aLoadInfos.Length() == 1); + MOZ_ASSERT_IF(aIsMainScript, aLoadInfos.Length() == 1); mLoadInfos.SwapElements(aLoadInfos); } @@ -300,6 +318,12 @@ private: return true; } + bool + IsMainWorkerScript() const + { + return mIsMainScript && mWorkerScriptType == WorkerScript; + } + void CancelMainThread() { @@ -338,7 +362,7 @@ private: nsCOMPtr loadGroup = mWorkerPrivate->GetLoadGroup(); if (!principal) { NS_ASSERTION(parentWorker, "Must have a principal!"); - NS_ASSERTION(mIsWorkerScript, "Must have a principal for importScripts!"); + NS_ASSERTION(mIsMainScript, "Must have a principal for importScripts!"); principal = parentWorker->GetPrincipal(); loadGroup = parentWorker->GetLoadGroup(); @@ -348,7 +372,7 @@ private: // Figure out our base URI. nsCOMPtr baseURI; - if (mIsWorkerScript) { + if (mIsMainScript) { if (parentWorker) { baseURI = parentWorker->GetBaseURI(); NS_ASSERTION(baseURI, "Should have been set already!"); @@ -367,7 +391,7 @@ private: nsCOMPtr parentDoc = mWorkerPrivate->GetDocument(); nsCOMPtr channel; - if (mIsWorkerScript) { + if (IsMainWorkerScript()) { // May be null. channel = mWorkerPrivate->ForgetWorkerChannel(); } @@ -383,8 +407,8 @@ private: if (!channel) { rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios, - secMan, loadInfo.mURL, mIsWorkerScript, - getter_AddRefs(channel)); + secMan, loadInfo.mURL, mIsMainScript, + mWorkerScriptType, getter_AddRefs(channel)); if (NS_FAILED(rv)) { return rv; } @@ -489,7 +513,7 @@ private: // Update the principal of the worker and its base URI if we just loaded the // worker's primary script. - if (mIsWorkerScript) { + if (IsMainWorkerScript()) { // Take care of the base URI first. mWorkerPrivate->SetBaseURI(finalURI); @@ -572,7 +596,7 @@ private: { AssertIsOnMainThread(); - if (mIsWorkerScript) { + if (IsMainWorkerScript()) { mWorkerPrivate->WorkerScriptLoaded(); } @@ -699,6 +723,15 @@ ScriptExecutorRunnable::ScriptExecutorRunnable( MOZ_ASSERT(aLastIndex < aScriptLoader.mLoadInfos.Length()); } +bool +ScriptExecutorRunnable::IsDebuggerRunnable() const +{ + // ScriptExecutorRunnable is used to execute both worker and debugger scripts. + // In the latter case, the runnable needs to be dispatched to the debugger + // queue. + return mScriptLoader.mWorkerScriptType == DebuggerScript; +} + bool ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { @@ -757,6 +790,10 @@ ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) options.setFileAndLine(filename.get(), 1) .setNoScriptRval(true); + if (mScriptLoader.mWorkerScriptType == DebuggerScript) { + options.setVersion(JSVERSION_LATEST); + } + JS::SourceBufferHolder srcBuf(loadInfo.mScriptTextBuf, loadInfo.mScriptTextLength, JS::SourceBufferHolder::GiveOwnership); @@ -814,7 +851,8 @@ ScriptExecutorRunnable::ShutdownScriptLoader(JSContext* aCx, bool LoadAllScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - nsTArray& aLoadInfos, bool aIsWorkerScript) + nsTArray& aLoadInfos, bool aIsMainScript, + WorkerScriptType aWorkerScriptType) { aWorkerPrivate->AssertIsOnWorkerThread(); NS_ASSERTION(!aLoadInfos.IsEmpty(), "Bad arguments!"); @@ -823,7 +861,7 @@ LoadAllScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate, nsRefPtr loader = new ScriptLoaderRunnable(aWorkerPrivate, syncLoop.EventTarget(), - aLoadInfos, aIsWorkerScript); + aLoadInfos, aIsMainScript, aWorkerScriptType); NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!"); @@ -863,7 +901,7 @@ ChannelFromScriptURLMainThread(nsIPrincipal* aPrincipal, NS_ASSERTION(secMan, "This should never be null!"); return ChannelFromScriptURL(aPrincipal, aBaseURI, aParentDoc, aLoadGroup, - ios, secMan, aScriptURL, true, aChannel); + ios, secMan, aScriptURL, true, WorkerScript, aChannel); } nsresult @@ -922,7 +960,8 @@ void ReportLoadError(JSContext* aCx, const nsAString& aURL, } bool -LoadWorkerScript(JSContext* aCx) +LoadMainScript(JSContext* aCx, const nsAString& aScriptURL, + WorkerScriptType aWorkerScriptType) { WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); NS_ASSERTION(worker, "This should never be null!"); @@ -930,14 +969,15 @@ LoadWorkerScript(JSContext* aCx) nsTArray loadInfos; ScriptLoadInfo* info = loadInfos.AppendElement(); - info->mURL = worker->ScriptURL(); + info->mURL = aScriptURL; - return LoadAllScripts(aCx, worker, loadInfos, true); + return LoadAllScripts(aCx, worker, loadInfos, true, aWorkerScriptType); } void Load(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - const Sequence& aScriptURLs, ErrorResult& aRv) + const nsTArray& aScriptURLs, WorkerScriptType aWorkerScriptType, + ErrorResult& aRv) { const uint32_t urlCount = aScriptURLs.Length(); @@ -957,7 +997,7 @@ Load(JSContext* aCx, WorkerPrivate* aWorkerPrivate, loadInfos[index].mURL = aScriptURLs[index]; } - if (!LoadAllScripts(aCx, aWorkerPrivate, loadInfos, false)) { + if (!LoadAllScripts(aCx, aWorkerPrivate, loadInfos, false, aWorkerScriptType)) { // LoadAllScripts can fail if we're shutting down. aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); } diff --git a/dom/workers/ScriptLoader.h b/dom/workers/ScriptLoader.h index 0b692acd9c..7331144132 100644 --- a/dom/workers/ScriptLoader.h +++ b/dom/workers/ScriptLoader.h @@ -29,6 +29,11 @@ class Sequence; BEGIN_WORKERS_NAMESPACE +enum WorkerScriptType { + WorkerScript, + DebuggerScript +}; + namespace scriptloader { nsresult @@ -48,11 +53,13 @@ ChannelFromScriptURLWorkerThread(JSContext* aCx, void ReportLoadError(JSContext* aCx, const nsAString& aURL, nsresult aLoadResult, bool aIsMainThread); -bool LoadWorkerScript(JSContext* aCx); +bool LoadMainScript(JSContext* aCx, const nsAString& aScriptURL, + WorkerScriptType aWorkerScriptType); void Load(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - const mozilla::dom::Sequence& aScriptURLs, + const nsTArray& aScriptURLs, + WorkerScriptType aWorkerScriptType, mozilla::ErrorResult& aRv); } // namespace scriptloader diff --git a/dom/workers/SharedWorker.cpp b/dom/workers/SharedWorker.cpp index 9e12dd2264..306b678d68 100644 --- a/dom/workers/SharedWorker.cpp +++ b/dom/workers/SharedWorker.cpp @@ -27,7 +27,7 @@ USING_WORKERS_NAMESPACE SharedWorker::SharedWorker(nsPIDOMWindow* aWindow, WorkerPrivate* aWorkerPrivate) : DOMEventTargetHelper(aWindow), mWorkerPrivate(aWorkerPrivate), - mSuspended(false) + mFrozen(false) { AssertIsOnMainThread(); MOZ_ASSERT(aWorkerPrivate); @@ -85,25 +85,25 @@ SharedWorker::Port() } void -SharedWorker::Suspend() +SharedWorker::Freeze() { AssertIsOnMainThread(); - MOZ_ASSERT(!IsSuspended()); + MOZ_ASSERT(!IsFrozen()); - mSuspended = true; + mFrozen = true; } void -SharedWorker::Resume() +SharedWorker::Thaw() { AssertIsOnMainThread(); - MOZ_ASSERT(IsSuspended()); + MOZ_ASSERT(IsFrozen()); - mSuspended = false; + mFrozen = false; - if (!mSuspendedEvents.IsEmpty()) { + if (!mFrozenEvents.IsEmpty()) { nsTArray> events; - mSuspendedEvents.SwapElements(events); + mFrozenEvents.SwapElements(events); for (uint32_t index = 0; index < events.Length(); index++) { nsCOMPtr& event = events[index]; @@ -127,9 +127,9 @@ SharedWorker::QueueEvent(nsIDOMEvent* aEvent) { AssertIsOnMainThread(); MOZ_ASSERT(aEvent); - MOZ_ASSERT(IsSuspended()); + MOZ_ASSERT(IsFrozen()); - mSuspendedEvents.AppendElement(aEvent); + mFrozenEvents.AppendElement(aEvent); } void @@ -181,14 +181,14 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(SharedWorker) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SharedWorker, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessagePort) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedEvents) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrozenEvents) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SharedWorker, DOMEventTargetHelper) tmp->Close(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessagePort) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedEvents) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrozenEvents) NS_IMPL_CYCLE_COLLECTION_UNLINK_END JSObject* @@ -206,7 +206,7 @@ SharedWorker::PreHandleEvent(EventChainPreVisitor& aVisitor) nsIDOMEvent*& event = aVisitor.mDOMEvent; - if (IsSuspended() && event) { + if (IsFrozen() && event) { QueueEvent(event); aVisitor.mCanHandle = false; diff --git a/dom/workers/SharedWorker.h b/dom/workers/SharedWorker.h index 149f38f36d..ebd1709304 100644 --- a/dom/workers/SharedWorker.h +++ b/dom/workers/SharedWorker.h @@ -35,9 +35,9 @@ class SharedWorker final : public DOMEventTargetHelper nsRefPtr mWorkerPrivate; nsRefPtr mMessagePort; - nsTArray> mSuspendedEvents; + nsTArray> mFrozenEvents; uint64_t mSerial; - bool mSuspended; + bool mFrozen; public: static already_AddRefed @@ -55,16 +55,16 @@ public: } bool - IsSuspended() const + IsFrozen() const { - return mSuspended; + return mFrozen; } void - Suspend(); + Freeze(); void - Resume(); + Thaw(); void QueueEvent(nsIDOMEvent* aEvent); diff --git a/dom/workers/WorkerDebuggerManager.cpp b/dom/workers/WorkerDebuggerManager.cpp index 48427cbd26..70fd4b9e30 100644 --- a/dom/workers/WorkerDebuggerManager.cpp +++ b/dom/workers/WorkerDebuggerManager.cpp @@ -141,6 +141,16 @@ WorkerDebuggerManager::RemoveListener( return NS_OK; } +void +WorkerDebuggerManager::ClearListeners() +{ + AssertIsOnMainThread(); + + MutexAutoLock lock(mMutex); + + mListeners.Clear(); +} + void WorkerDebuggerManager::RegisterDebugger(WorkerDebugger* aDebugger) { diff --git a/dom/workers/WorkerDebuggerManager.h b/dom/workers/WorkerDebuggerManager.h index 07eede1eca..a4e80f978e 100644 --- a/dom/workers/WorkerDebuggerManager.h +++ b/dom/workers/WorkerDebuggerManager.h @@ -41,9 +41,9 @@ public: static WorkerDebuggerManager* GetOrCreateService() { - nsCOMPtr wdm = + nsCOMPtr manager = do_GetService(WORKERDEBUGGERMANAGER_CONTRACTID); - return static_cast(wdm.get()); + return static_cast(manager.get()); } WorkerDebuggerManager(); @@ -51,6 +51,8 @@ public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIWORKERDEBUGGERMANAGER + void ClearListeners(); + void RegisterDebugger(WorkerDebugger* aDebugger); void UnregisterDebugger(WorkerDebugger* aDebugger); @@ -64,6 +66,19 @@ private: void UnregisterDebuggerOnMainThread(WorkerDebugger* aDebugger); }; +inline nsresult +ClearWorkerDebuggerManagerListeners() +{ + nsRefPtr manager = + WorkerDebuggerManager::GetOrCreateService(); + if (!manager) { + return NS_ERROR_FAILURE; + } + + manager->ClearListeners(); + return NS_OK; +} + inline nsresult RegisterWorkerDebugger(WorkerDebugger* aDebugger) { diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index d37ccfd4e6..363a6b0367 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -1042,9 +1042,13 @@ private: class CompileScriptRunnable final : public WorkerRunnable { + nsString mScriptURL; + public: - explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate) - : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) + explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate, + const nsAString& aScriptURL) + : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount), + mScriptURL(aScriptURL) { } private: @@ -1059,8 +1063,9 @@ private: } JS::Rooted global(aCx, globalScope->GetWrapper()); + JSAutoCompartment ac(aCx, global); - bool result = scriptloader::LoadWorkerScript(aCx); + bool result = scriptloader::LoadMainScript(aCx, mScriptURL, WorkerScript); if (result) { aWorkerPrivate->SetWorkerScriptExecutedSuccessfully(); } @@ -1068,6 +1073,35 @@ private: } }; +class CompileDebuggerScriptRunnable final : public WorkerDebuggerRunnable +{ + nsString mScriptURL; + +public: + CompileDebuggerScriptRunnable(WorkerPrivate* aWorkerPrivate, + const nsAString& aScriptURL) + : WorkerDebuggerRunnable(aWorkerPrivate), + mScriptURL(aScriptURL) + { } + +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + WorkerDebuggerGlobalScope* globalScope = + aWorkerPrivate->CreateDebuggerGlobalScope(aCx); + if (!globalScope) { + NS_WARNING("Failed to make global!"); + return false; + } + + JS::Rooted global(aCx, globalScope->GetWrapper()); + + JSAutoCompartment ac(aCx, global); + return scriptloader::LoadMainScript(aCx, mScriptURL, DebuggerScript); + } +}; + class CloseEventRunnable final : public WorkerRunnable { public: @@ -1232,7 +1266,7 @@ private: mClonedObjects); } - if (aWorkerPrivate->IsSuspended()) { + if (aWorkerPrivate->IsFrozen()) { aWorkerPrivate->QueueRunnable(this); return true; } @@ -1260,6 +1294,54 @@ private: } }; +class DebuggerMessageEventRunnable : public WorkerDebuggerRunnable { + nsString mMessage; + +public: + DebuggerMessageEventRunnable(WorkerPrivate* aWorkerPrivate, + const nsAString& aMessage) + : WorkerDebuggerRunnable(aWorkerPrivate), + mMessage(aMessage) + { + } + +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + WorkerDebuggerGlobalScope* globalScope = aWorkerPrivate->DebuggerGlobalScope(); + MOZ_ASSERT(globalScope); + + JS::Rooted message(aCx, JS_NewUCStringCopyN(aCx, mMessage.get(), + mMessage.Length())); + if (!message) { + return false; + } + JS::Rooted data(aCx, JS::StringValue(message)); + + nsRefPtr event = new MessageEvent(globalScope, nullptr, + nullptr); + nsresult rv = + event->InitMessageEvent(NS_LITERAL_STRING("message"), + false, // canBubble + true, // cancelable + data, + EmptyString(), + EmptyString(), + nullptr); + if (NS_FAILED(rv)) { + xpc::Throw(aCx, rv); + return false; + } + event->SetTrusted(true); + + nsCOMPtr domEvent = do_QueryObject(event); + nsEventStatus status = nsEventStatus_eIgnore; + globalScope->DispatchDOMEvent(nullptr, domEvent, nullptr, &status); + return true; + } +}; + class NotifyRunnable final : public WorkerControlRunnable { Status mStatus; @@ -1317,10 +1399,10 @@ private: } }; -class SuspendRunnable final : public WorkerControlRunnable +class FreezeRunnable final : public WorkerControlRunnable { public: - explicit SuspendRunnable(WorkerPrivate* aWorkerPrivate) + explicit FreezeRunnable(WorkerPrivate* aWorkerPrivate) : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) { } @@ -1328,14 +1410,14 @@ private: virtual bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { - return aWorkerPrivate->SuspendInternal(aCx); + return aWorkerPrivate->FreezeInternal(aCx); } }; -class ResumeRunnable final : public WorkerControlRunnable +class ThawRunnable final : public WorkerControlRunnable { public: - explicit ResumeRunnable(WorkerPrivate* aWorkerPrivate) + explicit ThawRunnable(WorkerPrivate* aWorkerPrivate) : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) { } @@ -1343,7 +1425,7 @@ private: virtual bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { - return aWorkerPrivate->ResumeInternal(aCx); + return aWorkerPrivate->ThawInternal(aCx); } }; @@ -1422,20 +1504,24 @@ public: nsIScriptGlobalObject* sgo; if (aWorkerPrivate) { - nsIDOMEventTarget* target = nullptr; WorkerGlobalScope* globalScope = nullptr; UNWRAP_WORKER_OBJECT(WorkerGlobalScope, global, globalScope); - if (globalScope) { - MOZ_ASSERT(global == globalScope->GetWrapperPreserveColor()); - target = static_cast(globalScope); - } else { + + if (!globalScope) { WorkerDebuggerGlobalScope* globalScope = nullptr; UNWRAP_OBJECT(WorkerDebuggerGlobalScope, global, globalScope); - MOZ_ASSERT(globalScope); - MOZ_ASSERT(global == globalScope->GetWrapperPreserveColor()); - target = static_cast(globalScope); + + MOZ_ASSERT_IF(globalScope, globalScope->GetWrapperPreserveColor() == global); + MOZ_ASSERT_IF(!globalScope, IsDebuggerSandbox(global)); + + aWorkerPrivate->ReportErrorToDebugger(aFilename, aLineNumber, + aMessage); + return true; } + MOZ_ASSERT(globalScope->GetWrapperPreserveColor() == global); + nsIDOMEventTarget* target = static_cast(globalScope); + nsRefPtr event = ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init); event->SetTrusted(true); @@ -1516,7 +1602,7 @@ private: else { AssertIsOnMainThread(); - if (aWorkerPrivate->IsSuspended()) { + if (aWorkerPrivate->IsFrozen()) { aWorkerPrivate->QueueRunnable(this); return true; } @@ -1591,6 +1677,54 @@ private: } }; +class DebuggerImmediateRunnable : public WorkerRunnable +{ + nsRefPtr mHandler; + +public: + explicit DebuggerImmediateRunnable(WorkerPrivate* aWorkerPrivate, + Function& aHandler) + : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), + mHandler(&aHandler) + { } + +private: + virtual bool + IsDebuggerRunnable() const override + { + return true; + } + + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + // Silence bad assertions. + return true; + } + + virtual void + PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) override + { + // Silence bad assertions. + } + + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); + JS::Rooted callable(aCx, JS::ObjectValue(*mHandler->Callable())); + JS::HandleValueArray args = JS::HandleValueArray::empty(); + JS::Rooted rval(aCx); + if (!JS_CallFunctionValue(aCx, global, callable, args, &rval) && + !JS_ReportPendingException(aCx)) { + return false; + } + + return true; + } +}; + void DummyCallback(nsITimer* aTimer, void* aClosure) { @@ -2226,62 +2360,6 @@ private: { } }; -template -class WorkerPrivateParent::SynchronizeAndResumeRunnable final - : public nsRunnable -{ - friend class nsRevocableEventPtr; - - WorkerPrivate* mWorkerPrivate; - nsCOMPtr mWindow; - -public: - SynchronizeAndResumeRunnable(WorkerPrivate* aWorkerPrivate, - nsPIDOMWindow* aWindow) - : mWorkerPrivate(aWorkerPrivate), mWindow(aWindow) - { - AssertIsOnMainThread(); - MOZ_ASSERT(aWorkerPrivate); - MOZ_ASSERT(aWindow); - MOZ_ASSERT(!aWorkerPrivate->GetParent()); - } - -private: - ~SynchronizeAndResumeRunnable() - { } - - NS_IMETHOD - Run() override - { - AssertIsOnMainThread(); - - if (mWorkerPrivate) { - AutoJSAPI jsapi; - if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(mWindow))) { - return NS_OK; - } - JSContext* cx = jsapi.cx(); - - if (!mWorkerPrivate->Resume(cx, mWindow)) { - JS_ReportPendingException(cx); - } - } - - return NS_OK; - } - - void - Revoke() - { - AssertIsOnMainThread(); - MOZ_ASSERT(mWorkerPrivate); - MOZ_ASSERT(mWindow); - - mWorkerPrivate = nullptr; - mWindow = nullptr; - } -}; - WorkerLoadInfo:: InterfaceRequestor::InterfaceRequestor(nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup) @@ -2595,7 +2673,7 @@ WorkerPrivateParent::WorkerPrivateParent( mMemoryReportCondVar(mMutex, "WorkerPrivateParent Memory Report CondVar"), mParent(aParent), mScriptURL(aScriptURL), mSharedWorkerName(aSharedWorkerName), mBusyCount(0), mMessagePortSerial(0), - mParentStatus(Pending), mParentSuspended(false), + mParentStatus(Pending), mParentFrozen(false), mIsChromeWorker(aIsChromeWorker), mMainThreadObjectsForgotten(false), mWorkerType(aWorkerType), mCreationTimeStamp(TimeStamp::Now()) @@ -2785,6 +2863,37 @@ WorkerPrivateParent::DispatchControlRunnable( return NS_OK; } +template +nsresult +WorkerPrivateParent::DispatchDebuggerRunnable( + WorkerRunnable *aDebuggerRunnable) +{ + // May be called on any thread! + + MOZ_ASSERT(aDebuggerRunnable); + + nsRefPtr runnable = aDebuggerRunnable; + + WorkerPrivate* self = ParentAsWorkerPrivate(); + + { + MutexAutoLock lock(mMutex); + + if (self->mStatus == Dead) { + NS_WARNING("A debugger runnable was posted to a worker that is already " + "shutting down!"); + return NS_ERROR_UNEXPECTED; + } + + // Transfer ownership to the debugger queue. + self->mDebuggerQueue.Push(runnable.forget().take()); + + mCondVar.Notify(); + } + + return NS_OK; +} + template already_AddRefed WorkerPrivateParent::MaybeWrapAsWorkerRunnable(nsIRunnable* aRunnable) @@ -2902,10 +3011,6 @@ WorkerPrivateParent::NotifyPrivate(JSContext* aCx, Status aStatus) return true; } - // Only top-level workers should have a synchronize runnable. - MOZ_ASSERT_IF(mSynchronizeRunnable.get(), !GetParent()); - mSynchronizeRunnable.Revoke(); - NS_ASSERTION(aStatus != Terminating || mQueuedRunnables.IsEmpty(), "Shouldn't have anything queued!"); @@ -2919,13 +3024,13 @@ WorkerPrivateParent::NotifyPrivate(JSContext* aCx, Status aStatus) template bool -WorkerPrivateParent::Suspend(JSContext* aCx, nsPIDOMWindow* aWindow) +WorkerPrivateParent::Freeze(JSContext* aCx, nsPIDOMWindow* aWindow) { AssertIsOnParentThread(); MOZ_ASSERT(aCx); - // Shared workers are only suspended if all of their owning documents are - // suspended. It can happen that mSharedWorkers is empty but this thread has + // Shared workers are only frozen if all of their owning documents are + // frozen. It can happen that mSharedWorkers is empty but this thread has // not been unregistered yet. if ((IsSharedWorker() || IsServiceWorker()) && mSharedWorkers.Count()) { AssertIsOnMainThread(); @@ -2933,17 +3038,17 @@ WorkerPrivateParent::Suspend(JSContext* aCx, nsPIDOMWindow* aWindow) struct Closure { nsPIDOMWindow* mWindow; - bool mAllSuspended; + bool mAllFrozen; explicit Closure(nsPIDOMWindow* aWindow) - : mWindow(aWindow), mAllSuspended(true) + : mWindow(aWindow), mAllFrozen(true) { AssertIsOnMainThread(); // aWindow may be null here. } static PLDHashOperator - Suspend(const uint64_t& aKey, + Freeze(const uint64_t& aKey, SharedWorker* aSharedWorker, void* aClosure) { @@ -2954,17 +3059,17 @@ WorkerPrivateParent::Suspend(JSContext* aCx, nsPIDOMWindow* aWindow) auto closure = static_cast(aClosure); if (closure->mWindow && aSharedWorker->GetOwner() == closure->mWindow) { - // Calling Suspend() may change the refcount, ensure that the worker + // Calling Freeze() may change the refcount, ensure that the worker // outlives this call. nsRefPtr kungFuDeathGrip = aSharedWorker; - aSharedWorker->Suspend(); + aSharedWorker->Freeze(); } else { MOZ_ASSERT_IF(aSharedWorker->GetOwner() && closure->mWindow, !SameCOMIdentity(aSharedWorker->GetOwner(), closure->mWindow)); - if (!aSharedWorker->IsSuspended()) { - closure->mAllSuspended = false; + if (!aSharedWorker->IsFrozen()) { + closure->mAllFrozen = false; } } return PL_DHASH_NEXT; @@ -2973,14 +3078,14 @@ WorkerPrivateParent::Suspend(JSContext* aCx, nsPIDOMWindow* aWindow) Closure closure(aWindow); - mSharedWorkers.EnumerateRead(Closure::Suspend, &closure); + mSharedWorkers.EnumerateRead(Closure::Freeze, &closure); - if (!closure.mAllSuspended || mParentSuspended) { + if (!closure.mAllFrozen || mParentFrozen) { return true; } } - mParentSuspended = true; + mParentFrozen = true; { MutexAutoLock lock(mMutex); @@ -2990,8 +3095,8 @@ WorkerPrivateParent::Suspend(JSContext* aCx, nsPIDOMWindow* aWindow) } } - nsRefPtr runnable = - new SuspendRunnable(ParentAsWorkerPrivate()); + nsRefPtr runnable = + new FreezeRunnable(ParentAsWorkerPrivate()); if (!runnable->Dispatch(aCx)) { return false; } @@ -3001,13 +3106,19 @@ WorkerPrivateParent::Suspend(JSContext* aCx, nsPIDOMWindow* aWindow) template bool -WorkerPrivateParent::Resume(JSContext* aCx, nsPIDOMWindow* aWindow) +WorkerPrivateParent::Thaw(JSContext* aCx, nsPIDOMWindow* aWindow) { AssertIsOnParentThread(); MOZ_ASSERT(aCx); - MOZ_ASSERT_IF(IsDedicatedWorker(), mParentSuspended); - // Shared workers are resumed if any of their owning documents are resumed. + if (IsDedicatedWorker() && !mParentFrozen) { + // If we are in here, it means that this worker has been created when the + // parent was actually suspended (maybe during a sync XHR), and in this case + // we don't need to thaw. + return true; + } + + // Shared workers are resumed if any of their owning documents are thawed. // It can happen that mSharedWorkers is empty but this thread has not been // unregistered yet. if ((IsSharedWorker() || IsServiceWorker()) && mSharedWorkers.Count()) { @@ -3026,7 +3137,7 @@ WorkerPrivateParent::Resume(JSContext* aCx, nsPIDOMWindow* aWindow) } static PLDHashOperator - Resume(const uint64_t& aKey, + Thaw(const uint64_t& aKey, SharedWorker* aSharedWorker, void* aClosure) { @@ -3037,17 +3148,17 @@ WorkerPrivateParent::Resume(JSContext* aCx, nsPIDOMWindow* aWindow) auto closure = static_cast(aClosure); if (closure->mWindow && aSharedWorker->GetOwner() == closure->mWindow) { - // Calling Resume() may change the refcount, ensure that the worker + // Calling Thaw() may change the refcount, ensure that the worker // outlives this call. nsRefPtr kungFuDeathGrip = aSharedWorker; - aSharedWorker->Resume(); + aSharedWorker->Thaw(); closure->mAnyRunning = true; } else { MOZ_ASSERT_IF(aSharedWorker->GetOwner() && closure->mWindow, !SameCOMIdentity(aSharedWorker->GetOwner(), closure->mWindow)); - if (!aSharedWorker->IsSuspended()) { + if (!aSharedWorker->IsFrozen()) { closure->mAnyRunning = true; } } @@ -3057,16 +3168,16 @@ WorkerPrivateParent::Resume(JSContext* aCx, nsPIDOMWindow* aWindow) Closure closure(aWindow); - mSharedWorkers.EnumerateRead(Closure::Resume, &closure); + mSharedWorkers.EnumerateRead(Closure::Thaw, &closure); - if (!closure.mAnyRunning || !mParentSuspended) { + if (!closure.mAnyRunning || !mParentFrozen) { return true; } } - MOZ_ASSERT(mParentSuspended); + MOZ_ASSERT(mParentFrozen); - mParentSuspended = false; + mParentFrozen = false; { MutexAutoLock lock(mMutex); @@ -3076,10 +3187,6 @@ WorkerPrivateParent::Resume(JSContext* aCx, nsPIDOMWindow* aWindow) } } - // Only top-level workers should have a synchronize runnable. - MOZ_ASSERT_IF(mSynchronizeRunnable.get(), !GetParent()); - mSynchronizeRunnable.Revoke(); - // Execute queued runnables before waking up the worker, otherwise the worker // could post new messages before we run those that have been queued. if (!mQueuedRunnables.IsEmpty()) { @@ -3094,8 +3201,8 @@ WorkerPrivateParent::Resume(JSContext* aCx, nsPIDOMWindow* aWindow) } } - nsRefPtr runnable = - new ResumeRunnable(ParentAsWorkerPrivate()); + nsRefPtr runnable = + new ThawRunnable(ParentAsWorkerPrivate()); if (!runnable->Dispatch(aCx)) { return false; } @@ -3103,39 +3210,6 @@ WorkerPrivateParent::Resume(JSContext* aCx, nsPIDOMWindow* aWindow) return true; } -template -bool -WorkerPrivateParent::SynchronizeAndResume( - JSContext* aCx, - nsPIDOMWindow* aWindow) -{ - AssertIsOnMainThread(); - MOZ_ASSERT(!GetParent()); - - if (IsDedicatedWorker() && !mParentSuspended) { - // If we are in here, it means that this worker has been created when the - // parent was actually suspended (maybe during a sync XHR), and in this case - // don't need to dispatch a SynchronizeAndResumeRunnable. - return true; - } - - // NB: There may be pending unqueued messages. If we resume here we will - // execute those messages out of order. Instead we post an event to the - // end of the event queue, allowing all of the outstanding messages to be - // queued up in order on the worker. Then and only then we execute all of - // the messages. - - nsRefPtr runnable = - new SynchronizeAndResumeRunnable(ParentAsWorkerPrivate(), aWindow); - if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { - JS_ReportError(aCx, "Failed to dispatch to current thread!"); - return false; - } - - mSynchronizeRunnable = runnable; - return true; -} - template bool WorkerPrivateParent::Close(JSContext* aCx) @@ -3611,8 +3685,8 @@ WorkerPrivateParent::RegisterSharedWorker(JSContext* aCx, mSharedWorkers.Put(aSharedWorker->Serial(), aSharedWorker); // If there were other SharedWorker objects attached to this worker then they - // may all have been suspended and this worker would need to be resumed. - if (mSharedWorkers.Count() > 1 && !Resume(aCx, nullptr)) { + // may all have been frozen and this worker would need to be thawed. + if (mSharedWorkers.Count() > 1 && !Thaw(aCx, nullptr)) { return false; } @@ -3640,10 +3714,10 @@ WorkerPrivateParent::UnregisterSharedWorker( mSharedWorkers.Remove(aSharedWorker->Serial()); // If there are still SharedWorker objects attached to this worker then they - // may all be suspended and this worker would need to be suspended. Otherwise, + // may all be frozen and this worker would need to be frozen. Otherwise, // if that was the last SharedWorker then it's time to cancel this worker. if (mSharedWorkers.Count()) { - if (!Suspend(aCx, nullptr)) { + if (!Freeze(aCx, nullptr)) { JS_ReportPendingException(aCx); } } else if (!Cancel(aCx)) { @@ -4100,11 +4174,48 @@ WorkerPrivateParent::AssertInnerWindowIsCorrect() const #endif +class ReportDebuggerErrorRunnable final : public nsIRunnable +{ + nsRefPtr mDebugger; + nsString mFilename; + uint32_t mLineno; + nsString mMessage; + +public: + ReportDebuggerErrorRunnable(WorkerDebugger* aDebugger, + const nsAString& aFilename, uint32_t aLineno, + const nsAString& aMessage) + : mDebugger(aDebugger), + mFilename(aFilename), + mLineno(aLineno), + mMessage(aMessage) + { + } + + NS_DECL_THREADSAFE_ISUPPORTS + +private: + ~ReportDebuggerErrorRunnable() + { } + + NS_IMETHOD + Run() override + { + mDebugger->ReportErrorToDebuggerOnMainThread(mFilename, mLineno, mMessage); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(ReportDebuggerErrorRunnable, nsIRunnable) + WorkerDebugger::WorkerDebugger(WorkerPrivate* aWorkerPrivate) : mMutex("WorkerDebugger::mMutex"), mCondVar(mMutex, "WorkerDebugger::mCondVar"), mWorkerPrivate(aWorkerPrivate), - mIsEnabled(false) + mIsEnabled(false), + mIsInitialized(false), + mIsFrozen(false) { mWorkerPrivate->AssertIsOnParentThread(); } @@ -4158,6 +4269,21 @@ WorkerDebugger::GetIsChrome(bool* aResult) return NS_OK; } +NS_IMETHODIMP +WorkerDebugger::GetIsFrozen(bool* aResult) +{ + AssertIsOnMainThread(); + + MutexAutoLock lock(mMutex); + + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + *aResult = mIsFrozen; + return NS_OK; +} + NS_IMETHODIMP WorkerDebugger::GetParent(nsIWorkerDebugger** aResult) { @@ -4233,6 +4359,48 @@ WorkerDebugger::GetWindow(nsIDOMWindow** aResult) return NS_OK; } +NS_IMETHODIMP +WorkerDebugger::Initialize(const nsAString& aURL, JSContext* aCx) +{ + AssertIsOnMainThread(); + + MutexAutoLock lock(mMutex); + + if (!mWorkerPrivate || mIsInitialized) { + return NS_ERROR_UNEXPECTED; + } + + nsRefPtr runnable = + new CompileDebuggerScriptRunnable(mWorkerPrivate, aURL); + if (!runnable->Dispatch(aCx)) { + return NS_ERROR_FAILURE; + } + + mIsInitialized = true; + + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::PostMessageMoz(const nsAString& aMessage, JSContext* aCx) +{ + AssertIsOnMainThread(); + + MutexAutoLock lock(mMutex); + + if (!mWorkerPrivate || !mIsInitialized) { + return NS_ERROR_UNEXPECTED; + } + + nsRefPtr runnable = + new DebuggerMessageEventRunnable(mWorkerPrivate, aMessage); + if (!runnable->Dispatch(aCx)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + NS_IMETHODIMP WorkerDebugger::AddListener(nsIWorkerDebuggerListener* aListener) { @@ -4313,6 +4481,116 @@ WorkerDebugger::Disable() NotifyIsEnabled(false); } +void +WorkerDebugger::Freeze() +{ + mWorkerPrivate->AssertIsOnWorkerThread(); + + nsCOMPtr runnable = + NS_NewRunnableMethod(this, &WorkerDebugger::FreezeOnMainThread); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))); +} + +void +WorkerDebugger::FreezeOnMainThread() +{ + AssertIsOnMainThread(); + + mIsFrozen = true; + + for (size_t index = 0; index < mListeners.Length(); ++index) { + mListeners[index]->OnFreeze(); + } +} + +void +WorkerDebugger::Thaw() +{ + mWorkerPrivate->AssertIsOnWorkerThread(); + + nsCOMPtr runnable = + NS_NewRunnableMethod(this, &WorkerDebugger::ThawOnMainThread); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))); +} + +void +WorkerDebugger::ThawOnMainThread() +{ + AssertIsOnMainThread(); + + mIsFrozen = false; + + for (size_t index = 0; index < mListeners.Length(); ++index) { + mListeners[index]->OnThaw(); + } +} + +void +WorkerDebugger::PostMessageToDebugger(const nsAString& aMessage) +{ + mWorkerPrivate->AssertIsOnWorkerThread(); + + nsCOMPtr runnable = + NS_NewRunnableMethodWithArg(this, + &WorkerDebugger::PostMessageToDebuggerOnMainThread, nsString(aMessage)); + NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); +} + +void +WorkerDebugger::PostMessageToDebuggerOnMainThread(const nsAString& aMessage) +{ + AssertIsOnMainThread(); + + nsTArray> listeners; + + { + MutexAutoLock lock(mMutex); + + listeners.AppendElements(mListeners); + } + + for (size_t index = 0; index < listeners.Length(); ++index) { + listeners[index]->OnMessage(aMessage); + } +} + +void +WorkerDebugger::ReportErrorToDebugger(const nsAString& aFilename, + uint32_t aLineno, + const nsAString& aMessage) +{ + mWorkerPrivate->AssertIsOnWorkerThread(); + + nsCOMPtr runnable = + new ReportDebuggerErrorRunnable(this, aFilename, aLineno, aMessage); + if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to report error to debugger on main thread!"); + } +} + +void +WorkerDebugger::ReportErrorToDebuggerOnMainThread(const nsAString& aFilename, + uint32_t aLineno, + const nsAString& aMessage) +{ + AssertIsOnMainThread(); + + nsTArray> listeners; + { + MutexAutoLock lock(mMutex); + + listeners.AppendElements(mListeners); + } + + for (size_t index = 0; index < listeners.Length(); ++index) { + listeners[index]->OnError(aFilename, aLineno, aMessage); + } + + LogErrorToConsole(aMessage, aFilename, nsString(), aLineno, 0, 0, 0); +} + WorkerPrivate::WorkerPrivate(JSContext* aCx, WorkerPrivate* aParent, const nsAString& aScriptURL, @@ -4324,10 +4602,11 @@ WorkerPrivate::WorkerPrivate(JSContext* aCx, aSharedWorkerName, aLoadInfo) , mJSContext(nullptr) , mPRThread(nullptr) + , mDebuggerEventLoopLevel(0) , mErrorHandlerRecursionCount(0) , mNextTimeoutId(1) , mStatus(Pending) - , mSuspended(false) + , mFrozen(false) , mTimerRunning(false) , mRunningExpiredTimeouts(false) , mCloseHandlerStarted(false) @@ -4495,7 +4774,8 @@ WorkerPrivate::Constructor(JSContext* aCx, worker->EnableDebugger(); - nsRefPtr compiler = new CompileScriptRunnable(worker); + nsRefPtr compiler = + new CompileScriptRunnable(worker, aScriptURL); if (!compiler->Dispatch(aCx)) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; @@ -4808,19 +5088,15 @@ WorkerPrivate::DoRunLoop(JSContext* aCx) Maybe workerCompartment; for (;;) { - // Workers lazily create a global object in CompileScriptRunnable. We need - // to enter the global's compartment as soon as it has been created. - if (!workerCompartment && GlobalScope()) { - workerCompartment.emplace(aCx, GlobalScope()->GetGlobalJSObject()); - } - Status currentStatus; + bool debuggerRunnablesPending = false; bool normalRunnablesPending = false; { MutexAutoLock lock(mMutex); while (mControlQueue.IsEmpty() && + !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) && !(normalRunnablesPending = NS_HasPendingEvents(mThread))) { WaitForWorkerEvents(); } @@ -4884,33 +5160,54 @@ WorkerPrivate::DoRunLoop(JSContext* aCx) } } - // Nothing to do here if we don't have any runnables in the main queue. - if (!normalRunnablesPending) { - SetGCTimerMode(IdleTimer); - continue; + if (debuggerRunnablesPending || normalRunnablesPending) { + // Start the periodic GC timer if it is not already running. + SetGCTimerMode(PeriodicTimer); } - MOZ_ASSERT(NS_HasPendingEvents(mThread)); + if (debuggerRunnablesPending) { + WorkerRunnable* runnable; - // Start the periodic GC timer if it is not already running. - SetGCTimerMode(PeriodicTimer); + { + MutexAutoLock lock(mMutex); - // Process a single runnable from the main queue. - MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false)); + mDebuggerQueue.Pop(runnable); + debuggerRunnablesPending = !mDebuggerQueue.IsEmpty(); + } - // Only perform the Promise microtask checkpoint on the outermost event - // loop. Don't run it, for example, during sync XHR or importScripts. - (void)Promise::PerformMicroTaskCheckpoint(); + MOZ_ASSERT(runnable); + static_cast(runnable)->Run(); + runnable->Release(); - if (NS_HasPendingEvents(mThread)) { - // Now *might* be a good time to GC. Let the JS engine make the decision. - if (workerCompartment) { + if (debuggerRunnablesPending) { + WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope(); + MOZ_ASSERT(globalScope); + + // Now *might* be a good time to GC. Let the JS engine make the decision. + JSAutoCompartment ac(aCx, globalScope->GetGlobalJSObject()); + JS_MaybeGC(aCx); + } + } else if (normalRunnablesPending) { + MOZ_ASSERT(NS_HasPendingEvents(mThread)); + + // Process a single runnable from the main queue. + MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false)); + + // Only perform the Promise microtask checkpoint on the outermost event + // loop. Don't run it, for example, during sync XHR or importScripts. + (void)Promise::PerformMicroTaskCheckpoint(); + + normalRunnablesPending = NS_HasPendingEvents(mThread); + if (normalRunnablesPending && GlobalScope()) { + // Now *might* be a good time to GC. Let the JS engine make the decision. + JSAutoCompartment ac(aCx, GlobalScope()->GetGlobalJSObject()); JS_MaybeGC(aCx); } } - else { - // The normal event queue has been exhausted, cancel the periodic GC timer - // and schedule the idle GC timer. + + if (!debuggerRunnablesPending && !normalRunnablesPending) { + // Both the debugger event queue and the normal event queue has been + // exhausted, cancel the periodic GC timer and schedule the idle GC timer. SetGCTimerMode(IdleTimer); } } @@ -5114,17 +5411,17 @@ WorkerPrivate::InterruptCallback(JSContext* aCx) // Run all control events now. mayContinue = ProcessAllControlRunnables(); - bool maySuspend = mSuspended; - if (maySuspend) { + bool mayFreeze = mFrozen; + if (mayFreeze) { MutexAutoLock lock(mMutex); - maySuspend = mStatus <= Running; + mayFreeze = mStatus <= Running; } - if (!mayContinue || !maySuspend) { + if (!mayContinue || !mayFreeze) { break; } - // Cancel the periodic GC timer here before suspending. The idle GC timer + // Cancel the periodic GC timer here before freezing. The idle GC timer // will clean everything up once it runs. if (!scheduledIdleGC) { SetGCTimerMode(IdleTimer); @@ -5444,24 +5741,26 @@ WorkerPrivate::RemainingRunTimeMS() const } bool -WorkerPrivate::SuspendInternal(JSContext* aCx) +WorkerPrivate::FreezeInternal(JSContext* aCx) { AssertIsOnWorkerThread(); - NS_ASSERTION(!mSuspended, "Already suspended!"); + NS_ASSERTION(!mFrozen, "Already frozen!"); - mSuspended = true; + mFrozen = true; + mDebugger->Freeze(); return true; } bool -WorkerPrivate::ResumeInternal(JSContext* aCx) +WorkerPrivate::ThawInternal(JSContext* aCx) { AssertIsOnWorkerThread(); - NS_ASSERTION(mSuspended, "Not yet suspended!"); + NS_ASSERTION(mFrozen, "Not yet frozen!"); - mSuspended = false; + mFrozen = false; + mDebugger->Thaw(); return true; } @@ -5911,6 +6210,103 @@ WorkerPrivate::PostMessageToParentMessagePort( aMessagePortSerial, aRv); } +void +WorkerPrivate::EnterDebuggerEventLoop() +{ + AssertIsOnWorkerThread(); + + JSContext* cx = GetJSContext(); + MOZ_ASSERT(cx); + + uint32_t currentEventLoopLevel = ++mDebuggerEventLoopLevel; + + while (currentEventLoopLevel <= mDebuggerEventLoopLevel) { + bool debuggerRunnablesPending = false; + + { + MutexAutoLock lock(mMutex); + + debuggerRunnablesPending = !mDebuggerQueue.IsEmpty(); + } + + // Don't block with the periodic GC timer running. + if (!debuggerRunnablesPending) { + SetGCTimerMode(IdleTimer); + } + + // Wait for something to do + { + MutexAutoLock lock(mMutex); + + while (mControlQueue.IsEmpty() && + !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty())) { + WaitForWorkerEvents(); + } + + ProcessAllControlRunnablesLocked(); + } + + if (debuggerRunnablesPending) { + // Start the periodic GC timer if it is not already running. + SetGCTimerMode(PeriodicTimer); + + WorkerRunnable* runnable; + + { + MutexAutoLock lock(mMutex); + + mDebuggerQueue.Pop(runnable); + } + + MOZ_ASSERT(runnable); + static_cast(runnable)->Run(); + runnable->Release(); + + // Now *might* be a good time to GC. Let the JS engine make the decision. + JS_MaybeGC(cx); + } + } +} + +void +WorkerPrivate::LeaveDebuggerEventLoop() +{ + AssertIsOnWorkerThread(); + + MutexAutoLock lock(mMutex); + + if (mDebuggerEventLoopLevel > 0) { + --mDebuggerEventLoopLevel; + } +} + +void +WorkerPrivate::PostMessageToDebugger(const nsAString& aMessage) +{ + mDebugger->PostMessageToDebugger(aMessage); +} + +void +WorkerPrivate::SetDebuggerImmediate(JSContext* aCx, Function& aHandler, + ErrorResult& aRv) +{ + AssertIsOnWorkerThread(); + + nsRefPtr runnable = + new DebuggerImmediateRunnable(this, aHandler); + if (!runnable->Dispatch(aCx)) { + aRv.Throw(NS_ERROR_FAILURE); + } +} + +void +WorkerPrivate::ReportErrorToDebugger(const nsAString& aFilename, + uint32_t aLineno, + const nsAString& aMessage) +{ + mDebugger->ReportErrorToDebugger(aFilename, aLineno, aMessage); +} + bool WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus) { diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index 8725c3d52a..09a5fe732b 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -56,6 +56,8 @@ class PrincipalInfo; struct PRThread; +class ReportDebuggerErrorRunnable; + BEGIN_WORKERS_NAMESPACE class AutoSyncLoopHolder; @@ -122,8 +124,6 @@ public: template class WorkerPrivateParent : public DOMEventTargetHelper { - class SynchronizeAndResumeRunnable; - protected: class EventTarget; friend class EventTarget; @@ -166,7 +166,6 @@ private: // Only used for top level workers. nsTArray> mQueuedRunnables; - nsRevocableEventPtr mSynchronizeRunnable; // Only for ChromeWorkers without window and only touched on the main thread. nsTArray mHostObjectURIs; @@ -181,7 +180,7 @@ private: uint64_t mBusyCount; uint64_t mMessagePortSerial; Status mParentStatus; - bool mParentSuspended; + bool mParentFrozen; bool mIsChromeWorker; bool mMainThreadObjectsForgotten; WorkerType mWorkerType; @@ -261,6 +260,9 @@ public: nsresult DispatchControlRunnable(WorkerControlRunnable* aWorkerControlRunnable); + nsresult + DispatchDebuggerRunnable(WorkerRunnable* aDebuggerRunnable); + already_AddRefed MaybeWrapAsWorkerRunnable(nsIRunnable* aRunnable); @@ -290,17 +292,13 @@ public: return Notify(aCx, Killing); } - // We can assume that an nsPIDOMWindow will be available for Suspend, Resume - // and SynchronizeAndResume as these are only used for globals going in and - // out of the bfcache. + // We can assume that an nsPIDOMWindow will be available for Freeze, Thaw + // as these are only used for globals going in and out of the bfcache. bool - Suspend(JSContext* aCx, nsPIDOMWindow* aWindow); + Freeze(JSContext* aCx, nsPIDOMWindow* aWindow); bool - Resume(JSContext* aCx, nsPIDOMWindow* aWindow); - - bool - SynchronizeAndResume(JSContext* aCx, nsPIDOMWindow* aWindow); + Thaw(JSContext* aCx, nsPIDOMWindow* aWindow); bool Terminate(JSContext* aCx) @@ -409,10 +407,10 @@ public: } bool - IsSuspended() const + IsFrozen() const { AssertIsOnParentThread(); - return mParentSuspended; + return mParentFrozen; } bool @@ -718,6 +716,8 @@ public: }; class WorkerDebugger : public nsIWorkerDebugger { + friend class ::ReportDebuggerErrorRunnable; + mozilla::Mutex mMutex; mozilla::CondVar mCondVar; @@ -726,6 +726,8 @@ class WorkerDebugger : public nsIWorkerDebugger { bool mIsEnabled; // Only touched on the main thread. + bool mIsInitialized; + bool mIsFrozen; nsTArray> mListeners; public: @@ -734,18 +736,51 @@ public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIWORKERDEBUGGER - void AssertIsOnParentThread(); + void + AssertIsOnParentThread(); - void WaitIsEnabled(bool aIsEnabled); + void + WaitIsEnabled(bool aIsEnabled); - void Enable(); + void + Enable(); - void Disable(); + void + Disable(); + + void + Freeze(); + + void + Thaw(); + + void + PostMessageToDebugger(const nsAString& aMessage); + + void + ReportErrorToDebugger(const nsAString& aFilename, uint32_t aLineno, + const nsAString& aMessage); private: - virtual ~WorkerDebugger(); + virtual + ~WorkerDebugger(); - void NotifyIsEnabled(bool aIsEnabled); + void + NotifyIsEnabled(bool aIsEnabled); + + void + FreezeOnMainThread(); + + void + ThawOnMainThread(); + + void + PostMessageToDebuggerOnMainThread(const nsAString& aMessage); + + void + ReportErrorToDebuggerOnMainThread(const nsAString& aFilename, + uint32_t aLineno, + const nsAString& aMessage); }; class WorkerPrivate : public WorkerPrivateParent @@ -771,6 +806,7 @@ class WorkerPrivate : public WorkerPrivateParent nsRefPtr mDebugger; Queue mControlQueue; + Queue mDebuggerQueue; // Touched on multiple threads, protected with mMutex. JSContext* mJSContext; @@ -785,6 +821,7 @@ class WorkerPrivate : public WorkerPrivateParent nsTArray mChildWorkers; nsTArray mFeatures; nsTArray> mTimeouts; + uint32_t mDebuggerEventLoopLevel; struct SyncLoopInfo { @@ -820,7 +857,7 @@ class WorkerPrivate : public WorkerPrivateParent uint32_t mErrorHandlerRecursionCount; uint32_t mNextTimeoutId; Status mStatus; - bool mSuspended; + bool mFrozen; bool mTimerRunning; bool mRunningExpiredTimeouts; bool mCloseHandlerStarted; @@ -895,10 +932,10 @@ public: } bool - SuspendInternal(JSContext* aCx); + FreezeInternal(JSContext* aCx); bool - ResumeInternal(JSContext* aCx); + ThawInternal(JSContext* aCx); void TraceTimeouts(const TraceCallbacks& aCallbacks, void* aClosure) const; @@ -945,6 +982,22 @@ public: const Optional>& aTransferable, ErrorResult& aRv); + void + EnterDebuggerEventLoop(); + + void + LeaveDebuggerEventLoop(); + + void + PostMessageToDebugger(const nsAString& aMessage); + + void + SetDebuggerImmediate(JSContext* aCx, Function& aHandler, ErrorResult& aRv); + + void + ReportErrorToDebugger(const nsAString& aFilename, uint32_t aLineno, + const nsAString& aMessage); + bool NotifyInternal(JSContext* aCx, Status aStatus); diff --git a/dom/workers/WorkerRunnable.cpp b/dom/workers/WorkerRunnable.cpp index ac0d8a48dc..da20badd2f 100644 --- a/dom/workers/WorkerRunnable.cpp +++ b/dom/workers/WorkerRunnable.cpp @@ -50,6 +50,22 @@ WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate, } #endif +bool +WorkerRunnable::IsDebuggerRunnable() const +{ + return false; +} + +nsIGlobalObject* +WorkerRunnable::DefaultGlobalObject() const +{ + if (IsDebuggerRunnable()) { + return mWorkerPrivate->DebuggerGlobalScope(); + } else { + return mWorkerPrivate->GlobalScope(); + } +} + bool WorkerRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { @@ -121,7 +137,11 @@ WorkerRunnable::DispatchInternal() { if (mBehavior == WorkerThreadModifyBusyCount || mBehavior == WorkerThreadUnchangedBusyCount) { - return NS_SUCCEEDED(mWorkerPrivate->Dispatch(this)); + if (IsDebuggerRunnable()) { + return NS_SUCCEEDED(mWorkerPrivate->DispatchDebuggerRunnable(this)); + } else { + return NS_SUCCEEDED(mWorkerPrivate->Dispatch(this)); + } } MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount); @@ -285,9 +305,13 @@ WorkerRunnable::Run() MOZ_ASSERT(isMainThread == NS_IsMainThread()); nsRefPtr kungFuDeathGrip; if (targetIsWorkerThread) { - globalObject = mWorkerPrivate->GlobalScope(); - } - else { + JSObject* global = JS::CurrentGlobalOrNull(GetCurrentThreadJSContext()); + if (global) { + globalObject = GetGlobalObjectForGlobal(global); + } else { + globalObject = DefaultGlobalObject(); + } + } else { kungFuDeathGrip = mWorkerPrivate; if (isMainThread) { globalObject = static_cast(mWorkerPrivate->GetWindow()); @@ -327,8 +351,8 @@ WorkerRunnable::Run() // In the case of CompileScriptRunnnable, WorkerRun above can cause us to // lazily create a global, so we construct aes here before calling PostRun. - if (targetIsWorkerThread && !aes && mWorkerPrivate->GlobalScope()) { - aes.emplace(mWorkerPrivate->GlobalScope(), false, GetCurrentThreadJSContext()); + if (targetIsWorkerThread && !aes && DefaultGlobalObject()) { + aes.emplace(DefaultGlobalObject(), false, GetCurrentThreadJSContext()); cx = aes->cx(); } @@ -349,6 +373,14 @@ WorkerRunnable::Cancel() return (canceledCount == 1) ? NS_OK : NS_ERROR_UNEXPECTED; } +void +WorkerDebuggerRunnable::PostDispatch(JSContext* aCx, + WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) +{ + MaybeReportMainThreadException(aCx, aDispatchResult); +} + WorkerSyncRunnable::WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aSyncLoopTarget) : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), diff --git a/dom/workers/WorkerRunnable.h b/dom/workers/WorkerRunnable.h index 2aa45e72d2..937348129b 100644 --- a/dom/workers/WorkerRunnable.h +++ b/dom/workers/WorkerRunnable.h @@ -99,6 +99,14 @@ protected: virtual ~WorkerRunnable() { } + // Returns true if this runnable should be dispatched to the debugger queue, + // and false otherwise. + virtual bool + IsDebuggerRunnable() const; + + nsIGlobalObject* + DefaultGlobalObject() const; + // By default asserts that Dispatch() is being called on the right thread // (ParentThread if |mTarget| is WorkerThread, or WorkerThread otherwise). // Also increments the busy count of |mWorkerPrivate| if targeting the @@ -133,6 +141,38 @@ protected: NS_DECL_NSIRUNNABLE }; +// This runnable is used to send a message to a worker debugger. +class WorkerDebuggerRunnable : public WorkerRunnable +{ +protected: + explicit WorkerDebuggerRunnable(WorkerPrivate* aWorkerPrivate) + : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) + { + } + + virtual ~WorkerDebuggerRunnable() + { } + +private: + virtual bool + IsDebuggerRunnable() const override + { + return true; + } + + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + AssertIsOnMainThread(); + + return true; + } + + virtual void + PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) override; +}; + // This runnable is used to send a message directly to a worker's sync loop. class WorkerSyncRunnable : public WorkerRunnable { diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index 0013b6bd1f..3b4c1b3832 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -208,7 +208,7 @@ WorkerGlobalScope::ImportScripts(JSContext* aCx, ErrorResult& aRv) { mWorkerPrivate->AssertIsOnWorkerThread(); - scriptloader::Load(aCx, mWorkerPrivate, aScriptURLs, aRv); + scriptloader::Load(aCx, mWorkerPrivate, aScriptURLs, WorkerScript, aRv); } int32_t @@ -513,6 +513,220 @@ WorkerDebuggerGlobalScope::GetGlobal(JSContext* aCx, aGlobal.set(mWorkerPrivate->GetOrCreateGlobalScope(aCx)->GetWrapper()); } +class WorkerDebuggerSandboxPrivate : public nsIGlobalObject, + public nsWrapperCache +{ +public: + explicit WorkerDebuggerSandboxPrivate(JSObject *global) + { + SetWrapper(global); + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(WorkerDebuggerSandboxPrivate, + nsIGlobalObject) + + virtual JSObject *GetGlobalJSObject() override + { + return GetWrapper(); + } + + virtual JSObject* WrapObject(JSContext* cx, + JS::Handle aGivenProto) override + { + MOZ_CRASH("WorkerDebuggerSandboxPrivate doesn't use DOM bindings!"); + } + +private: + virtual ~WorkerDebuggerSandboxPrivate() + { + ClearWrapper(); + } +}; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerDebuggerSandboxPrivate) +NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkerDebuggerSandboxPrivate) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkerDebuggerSandboxPrivate) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerDebuggerSandboxPrivate) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsIGlobalObject) +NS_INTERFACE_MAP_END + +static bool +workerdebuggersandbox_enumerate(JSContext *cx, JS::Handle obj) +{ + return JS_EnumerateStandardClasses(cx, obj); +} + +static bool +workerdebuggersandbox_resolve(JSContext *cx, JS::Handle obj, + JS::Handle id, bool *resolvedp) +{ + return JS_ResolveStandardClass(cx, obj, id, resolvedp); +} + +static bool +workerdebuggersandbox_convert(JSContext *cx, JS::Handle obj, + JSType type, JS::MutableHandle vp) +{ + if (type == JSTYPE_OBJECT) { + vp.set(OBJECT_TO_JSVAL(obj)); + return true; + } + + return JS::OrdinaryToPrimitive(cx, obj, type, vp); +} + +static void +workerdebuggersandbox_finalize(js::FreeOp *fop, JSObject *obj) +{ + nsIGlobalObject *globalObject = + static_cast(JS_GetPrivate(obj)); + NS_RELEASE(globalObject); +} + +static void +workerdebuggersandbox_moved(JSObject *obj, const JSObject *old) +{ +} + +const js::Class workerdebuggersandbox_class = { + "workerdebuggersandbox", + JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS, + nullptr, + nullptr, + nullptr, + nullptr, + workerdebuggersandbox_enumerate, + workerdebuggersandbox_resolve, + workerdebuggersandbox_convert, + workerdebuggersandbox_finalize, + nullptr, + nullptr, + nullptr, + JS_GlobalObjectTraceHook, + JS_NULL_CLASS_SPEC, { + nullptr, + nullptr, + false, + nullptr, + workerdebuggersandbox_moved + }, JS_NULL_OBJECT_OPS +}; + +void +WorkerDebuggerGlobalScope::CreateSandbox(JSContext* aCx, const nsAString& aName, + JS::Handle aPrototype, + JS::MutableHandle aResult) +{ + mWorkerPrivate->AssertIsOnWorkerThread(); + + JS::CompartmentOptions options; + options.setInvisibleToDebugger(true); + + JS::Rooted sandbox(aCx, + JS_NewGlobalObject(aCx, js::Jsvalify(&workerdebuggersandbox_class), nullptr, + JS::DontFireOnNewGlobalHook, options)); + if (!sandbox) { + JS_ReportError(aCx, "Can't create sandbox!"); + aResult.set(nullptr); + return; + } + + { + JSAutoCompartment ac(aCx, sandbox); + + JS::Rooted prototype(aCx, aPrototype); + if (!JS_WrapObject(aCx, &prototype)) { + JS_ReportError(aCx, "Can't wrap sandbox prototype!"); + aResult.set(nullptr); + return; + } + + if (!JS_SetPrototype(aCx, sandbox, prototype)) { + JS_ReportError(aCx, "Can't set sandbox prototype!"); + aResult.set(nullptr); + return; + } + + nsCOMPtr globalObject = + new WorkerDebuggerSandboxPrivate(sandbox); + + // Pass on ownership of globalObject to |sandbox|. + JS_SetPrivate(sandbox, globalObject.forget().take()); + } + + JS_FireOnNewGlobalObject(aCx, sandbox); + + if (!JS_WrapObject(aCx, &sandbox)) { + JS_ReportError(aCx, "Can't wrap sandbox!"); + aResult.set(nullptr); + return; + } + + aResult.set(sandbox); +} + +void +WorkerDebuggerGlobalScope::LoadSubScript(JSContext* aCx, + const nsAString& aURL, + const Optional>& aSandbox, + ErrorResult& aRv) +{ + mWorkerPrivate->AssertIsOnWorkerThread(); + + Maybe ac; + if (aSandbox.WasPassed()) { + JS::Rooted sandbox(aCx, js::CheckedUnwrap(aSandbox.Value())); + if (!IsDebuggerSandbox(sandbox)) { + aRv.Throw(NS_ERROR_INVALID_ARG); + return; + } + + ac.emplace(aCx, sandbox); + } + + nsTArray urls; + urls.AppendElement(aURL); + scriptloader::Load(aCx, mWorkerPrivate, urls, DebuggerScript, aRv); +} + +void +WorkerDebuggerGlobalScope::EnterEventLoop() +{ + mWorkerPrivate->EnterDebuggerEventLoop(); +} + +void +WorkerDebuggerGlobalScope::LeaveEventLoop() +{ + mWorkerPrivate->LeaveDebuggerEventLoop(); +} + +void +WorkerDebuggerGlobalScope::PostMessage(const nsAString& aMessage) +{ + mWorkerPrivate->PostMessageToDebugger(aMessage); +} + +void +WorkerDebuggerGlobalScope::SetImmediate(JSContext* aCx, Function& aHandler, + ErrorResult& aRv) +{ + mWorkerPrivate->SetDebuggerImmediate(aCx, aHandler, aRv); +} + +void +WorkerDebuggerGlobalScope::ReportError(JSContext* aCx, + const nsAString& aMessage) +{ + JS::AutoFilename afn; + uint32_t lineno = 0; + JS::DescribeScriptedCaller(aCx, &afn, &lineno); + nsString filename(NS_ConvertUTF8toUTF16(afn.get())); + mWorkerPrivate->ReportErrorToDebugger(filename, lineno, aMessage); +} + void WorkerDebuggerGlobalScope::Dump(JSContext* aCx, const Optional& aString) const @@ -525,10 +739,18 @@ GetGlobalObjectForGlobal(JSObject* global) { nsIGlobalObject* globalObject = nullptr; UNWRAP_WORKER_OBJECT(WorkerGlobalScope, global, globalObject); + if (!globalObject) { UNWRAP_OBJECT(WorkerDebuggerGlobalScope, global, globalObject); - MOZ_ASSERT(globalObject); + + if (!globalObject) { + MOZ_ASSERT(IsDebuggerSandbox(global)); + globalObject = static_cast(JS_GetPrivate(global)); + + MOZ_ASSERT(globalObject); + } } + return globalObject; } @@ -548,6 +770,12 @@ IsDebuggerGlobal(JSObject* object) globalObject)) && !!globalObject; } +bool +IsDebuggerSandbox(JSObject* object) +{ + return js::GetObjectClass(object) == &workerdebuggersandbox_class; +} + bool GetterOnlyJSNative(JSContext* aCx, unsigned aArgc, JS::Value* aVp) { diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h index 7e270d4a83..2102daa27e 100644 --- a/dom/workers/WorkerScope.h +++ b/dom/workers/WorkerScope.h @@ -264,6 +264,33 @@ public: void GetGlobal(JSContext* aCx, JS::MutableHandle aGlobal); + void + CreateSandbox(JSContext* aCx, const nsAString& aName, + JS::Handle aPrototype, + JS::MutableHandle aResult); + + void + LoadSubScript(JSContext* aCx, const nsAString& aURL, + const Optional>& aSandbox, + ErrorResult& aRv); + + void + EnterEventLoop(); + + void + LeaveEventLoop(); + + void + PostMessage(const nsAString& aMessage); + + IMPL_EVENT_HANDLER(message) + + void + SetImmediate(JSContext* aCx, Function& aHandler, ErrorResult& aRv); + + void + ReportError(JSContext* aCx, const nsAString& aMessage); + void Dump(JSContext* aCx, const Optional& aString) const; diff --git a/dom/workers/Workers.h b/dom/workers/Workers.h index 1e6f134467..d387f19b8a 100644 --- a/dom/workers/Workers.h +++ b/dom/workers/Workers.h @@ -262,10 +262,10 @@ void CancelWorkersForWindow(nsPIDOMWindow* aWindow); void -SuspendWorkersForWindow(nsPIDOMWindow* aWindow); +FreezeWorkersForWindow(nsPIDOMWindow* aWindow); void -ResumeWorkersForWindow(nsPIDOMWindow* aWindow); +ThawWorkersForWindow(nsPIDOMWindow* aWindow); class WorkerTask { @@ -338,6 +338,9 @@ IsWorkerGlobal(JSObject* global); bool IsDebuggerGlobal(JSObject* global); +bool +IsDebuggerSandbox(JSObject* object); + // Throws the JSMSG_GETTER_ONLY exception. This shouldn't be used going // forward -- getter-only properties should just use JS_PSG for the setter // (implying no setter at all), which will not throw when set in non-strict diff --git a/dom/workers/nsIWorkerDebugger.idl b/dom/workers/nsIWorkerDebugger.idl index deef971f75..e8052b226d 100644 --- a/dom/workers/nsIWorkerDebugger.idl +++ b/dom/workers/nsIWorkerDebugger.idl @@ -2,13 +2,22 @@ interface nsIDOMWindow; -[scriptable, uuid(54fd2dd3-c01b-4f71-888f-462f37a54f57)] +[scriptable, uuid(530db841-1b2c-485a-beeb-f2b1acb9714e)] interface nsIWorkerDebuggerListener : nsISupports { void onClose(); + + void onError(in DOMString filename, in unsigned long lineno, + in DOMString message); + + void onFreeze(); + + void onMessage(in DOMString message); + + void onThaw(); }; -[scriptable, builtinclass, uuid(0833b363-bffe-4cdb-ad50-1c4563e0C8ff)] +[scriptable, builtinclass, uuid(d7c73e54-3c41-4393-9d13-fa2ed4Ba6764)] interface nsIWorkerDebugger : nsISupports { const unsigned long TYPE_DEDICATED = 0; @@ -19,6 +28,8 @@ interface nsIWorkerDebugger : nsISupports readonly attribute bool isChrome; + readonly attribute bool isFrozen; + readonly attribute nsIWorkerDebugger parent; readonly attribute unsigned long type; @@ -27,6 +38,12 @@ interface nsIWorkerDebugger : nsISupports readonly attribute nsIDOMWindow window; + [implicit_jscontext] + void initialize(in DOMString url); + + [implicit_jscontext, binaryname(PostMessageMoz)] + void postMessage(in DOMString message); + void addListener(in nsIWorkerDebuggerListener listener); void removeListener(in nsIWorkerDebuggerListener listener); diff --git a/dom/workers/test/WorkerDebugger.initialize_childWorker.js b/dom/workers/test/WorkerDebugger.initialize_childWorker.js new file mode 100644 index 0000000000..a85764bd9c --- /dev/null +++ b/dom/workers/test/WorkerDebugger.initialize_childWorker.js @@ -0,0 +1,6 @@ +"use strict"; + +self.onmessage = function () {}; + +debugger; +postMessage("worker"); diff --git a/dom/workers/test/WorkerDebugger.initialize_debugger.js b/dom/workers/test/WorkerDebugger.initialize_debugger.js new file mode 100644 index 0000000000..f52e95b159 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.initialize_debugger.js @@ -0,0 +1,6 @@ +"use strict"; + +var dbg = new Debugger(global); +dbg.onDebuggerStatement = function (frame) { + frame.eval("postMessage('debugger');"); +}; diff --git a/dom/workers/test/WorkerDebugger.initialize_worker.js b/dom/workers/test/WorkerDebugger.initialize_worker.js new file mode 100644 index 0000000000..5a24efd3ac --- /dev/null +++ b/dom/workers/test/WorkerDebugger.initialize_worker.js @@ -0,0 +1,9 @@ +"use strict"; + +var worker = new Worker("WorkerDebugger.initialize_childWorker.js"); +worker.onmessage = function (event) { + postMessage("child:" + event.data); +}; + +debugger; +postMessage("worker"); diff --git a/dom/workers/test/WorkerDebugger.isFrozen_iframe1.html b/dom/workers/test/WorkerDebugger.isFrozen_iframe1.html new file mode 100644 index 0000000000..d971892708 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.isFrozen_iframe1.html @@ -0,0 +1,15 @@ + + + + + + + + This is page 1. + + diff --git a/dom/workers/test/WorkerDebugger.isFrozen_iframe2.html b/dom/workers/test/WorkerDebugger.isFrozen_iframe2.html new file mode 100644 index 0000000000..21752bf853 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.isFrozen_iframe2.html @@ -0,0 +1,15 @@ + + + + + + + + This is page 2. + + diff --git a/dom/workers/test/WorkerDebugger.isFrozen_worker1.js b/dom/workers/test/WorkerDebugger.isFrozen_worker1.js new file mode 100644 index 0000000000..371d2c064b --- /dev/null +++ b/dom/workers/test/WorkerDebugger.isFrozen_worker1.js @@ -0,0 +1,5 @@ +"use strict"; + +onmessage = function () {}; + +postMessage("ready"); diff --git a/dom/workers/test/WorkerDebugger.isFrozen_worker2.js b/dom/workers/test/WorkerDebugger.isFrozen_worker2.js new file mode 100644 index 0000000000..371d2c064b --- /dev/null +++ b/dom/workers/test/WorkerDebugger.isFrozen_worker2.js @@ -0,0 +1,5 @@ +"use strict"; + +onmessage = function () {}; + +postMessage("ready"); diff --git a/dom/workers/test/WorkerDebugger.postMessage_childWorker.js b/dom/workers/test/WorkerDebugger.postMessage_childWorker.js new file mode 100644 index 0000000000..8cee6809e5 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.postMessage_childWorker.js @@ -0,0 +1,3 @@ +"use strict"; + +self.onmessage = function () {}; diff --git a/dom/workers/test/WorkerDebugger.postMessage_debugger.js b/dom/workers/test/WorkerDebugger.postMessage_debugger.js new file mode 100644 index 0000000000..4a231b7ab9 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.postMessage_debugger.js @@ -0,0 +1,9 @@ +"use strict" + +this.onmessage = function (event) { + switch (event.data) { + case "ping": + postMessage("pong"); + break; + } +}; diff --git a/dom/workers/test/WorkerDebugger.postMessage_worker.js b/dom/workers/test/WorkerDebugger.postMessage_worker.js new file mode 100644 index 0000000000..8ddf6cf865 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.postMessage_worker.js @@ -0,0 +1,3 @@ +"use strict"; + +var worker = new Worker("WorkerDebugger.postMessage_childWorker.js"); diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_debugger.js new file mode 100644 index 0000000000..f5f78a13d5 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_debugger.js @@ -0,0 +1,9 @@ +"use strict"; + +const SANDBOX_URL = "WorkerDebuggerGlobalScope.createSandbox_sandbox.js"; + +let prototype = { + self: this, +}; +let sandbox = createSandbox(SANDBOX_URL, prototype); +loadSubScript(SANDBOX_URL, sandbox); diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_sandbox.js b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_sandbox.js new file mode 100644 index 0000000000..f94d650622 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_sandbox.js @@ -0,0 +1,9 @@ +"use strict"; + +self.addEventListener("message", function(event) { + switch (event.data) { + case "ping": + self.postMessage("pong"); + break; + } +}); diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_worker.js new file mode 100644 index 0000000000..8cee6809e5 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_worker.js @@ -0,0 +1,3 @@ +"use strict"; + +self.onmessage = function () {}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js new file mode 100644 index 0000000000..b76f45a4d3 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js @@ -0,0 +1,14 @@ +"use strict"; + +function f() { + debugger; +} + +self.onmessage = function (event) { + switch (event.data) { + case "ping": + debugger; + postMessage("pong"); + break; + }; +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js new file mode 100644 index 0000000000..523d300bcf --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js @@ -0,0 +1,29 @@ +"use strict"; + +let frames = []; + +var dbg = new Debugger(global); +dbg.onDebuggerStatement = function (frame) { + frames.push(frame); + postMessage("paused"); + enterEventLoop(); + frames.pop(); + postMessage("resumed"); +}; + +this.onmessage = function (event) { + switch (event.data) { + case "eval": + frames[frames.length - 1].eval("f()"); + postMessage("evalled"); + break; + + case "ping": + postMessage("pong"); + break; + + case "resume": + leaveEventLoop(); + break; + }; +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js new file mode 100644 index 0000000000..c437385161 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js @@ -0,0 +1,25 @@ +"use strict"; + +function f() { + debugger; +} + +var worker = new Worker("WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js"); + +worker.onmessage = function (event) { + postMessage("child:" + event.data); +}; + +self.onmessage = function (event) { + var message = event.data; + if (message.indexOf(":") >= 0) { + worker.postMessage(message.split(":")[1]); + return; + } + switch (message) { + case "ping": + debugger; + postMessage("pong"); + break; + }; +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js new file mode 100644 index 0000000000..823e7c477f --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js @@ -0,0 +1,5 @@ +"use strict"; + +self.onerror = function () { + postMessage("error"); +} diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js new file mode 100644 index 0000000000..67ea08de5a --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js @@ -0,0 +1,12 @@ +"use strict"; + +this.onmessage = function (event) { + switch (event.data) { + case "report": + reportError("reported"); + break; + case "throw": + throw new Error("thrown"); + break; + } +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js new file mode 100644 index 0000000000..67ccfc2ca0 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js @@ -0,0 +1,11 @@ +"use strict"; + +var worker = new Worker("WorkerDebuggerGlobalScope.reportError_childWorker.js"); + +worker.onmessage = function (event) { + postMessage("child:" + event.data); +}; + +self.onerror = function () { + postMessage("error"); +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_debugger.js new file mode 100644 index 0000000000..b5075c70fe --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_debugger.js @@ -0,0 +1,12 @@ +"use strict"; + +this.onmessage = function (event) { + switch (event.data) { + case "ping": + setImmediate(function () { + postMessage("pong1"); + }); + postMessage("pong2"); + break; + } +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_worker.js new file mode 100644 index 0000000000..5a72b0f24d --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_worker.js @@ -0,0 +1,3 @@ +"use strict" + +self.onmessage = function () {}; diff --git a/dom/workers/test/WorkerDebuggerManager_childWorker.js b/dom/workers/test/WorkerDebuggerManager_childWorker.js index 18f086ce67..8cee6809e5 100644 --- a/dom/workers/test/WorkerDebuggerManager_childWorker.js +++ b/dom/workers/test/WorkerDebuggerManager_childWorker.js @@ -1,3 +1,3 @@ "use strict"; -onmessage = function () {}; +self.onmessage = function () {}; diff --git a/dom/workers/test/WorkerDebuggerManager_parentWorker.js b/dom/workers/test/WorkerDebuggerManager_worker.js similarity index 100% rename from dom/workers/test/WorkerDebuggerManager_parentWorker.js rename to dom/workers/test/WorkerDebuggerManager_worker.js diff --git a/dom/workers/test/WorkerDebugger_childWorker.js b/dom/workers/test/WorkerDebugger_childWorker.js index 18f086ce67..8cee6809e5 100644 --- a/dom/workers/test/WorkerDebugger_childWorker.js +++ b/dom/workers/test/WorkerDebugger_childWorker.js @@ -1,3 +1,3 @@ "use strict"; -onmessage = function () {}; +self.onmessage = function () {}; diff --git a/dom/workers/test/WorkerDebugger_parentWorker.js b/dom/workers/test/WorkerDebugger_parentWorker.js deleted file mode 100644 index 229e9bc446..0000000000 --- a/dom/workers/test/WorkerDebugger_parentWorker.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; - -var worker = new Worker("WorkerDebugger_childWorker.js"); diff --git a/dom/workers/test/WorkerDebugger_sharedWorker.js b/dom/workers/test/WorkerDebugger_sharedWorker.js index d4f95eb20f..5ad97d4c50 100644 --- a/dom/workers/test/WorkerDebugger_sharedWorker.js +++ b/dom/workers/test/WorkerDebugger_sharedWorker.js @@ -1,6 +1,6 @@ "use strict"; -onconnect = function (event) { +self.onconnect = function (event) { event.ports[0].onmessage = function (event) { switch (event.data) { case "close": diff --git a/dom/workers/test/WorkerDebugger_worker.js b/dom/workers/test/WorkerDebugger_worker.js new file mode 100644 index 0000000000..f301ac1e85 --- /dev/null +++ b/dom/workers/test/WorkerDebugger_worker.js @@ -0,0 +1,8 @@ +"use strict"; + +var worker = new Worker("WorkerDebugger_childWorker.js"); +self.onmessage = function (event) { + postMessage("child:" + event.data); +}; +debugger; +postMessage("worker"); diff --git a/dom/workers/test/chrome.ini b/dom/workers/test/chrome.ini index 0aedd05e37..5f3a05cba9 100644 --- a/dom/workers/test/chrome.ini +++ b/dom/workers/test/chrome.ini @@ -1,9 +1,30 @@ [DEFAULT] support-files = + WorkerDebugger.initialize_childWorker.js + WorkerDebugger.initialize_debugger.js + WorkerDebugger.initialize_worker.js + WorkerDebugger.isFrozen_iframe1.html + WorkerDebugger.isFrozen_iframe2.html + WorkerDebugger.isFrozen_worker1.js + WorkerDebugger.isFrozen_worker2.js + WorkerDebugger.postMessage_childWorker.js + WorkerDebugger.postMessage_debugger.js + WorkerDebugger.postMessage_worker.js + WorkerDebuggerGlobalScope.createSandbox_debugger.js + WorkerDebuggerGlobalScope.createSandbox_sandbox.js + WorkerDebuggerGlobalScope.createSandbox_worker.js + WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js + WorkerDebuggerGlobalScope.enterEventLoop_debugger.js + WorkerDebuggerGlobalScope.enterEventLoop_worker.js + WorkerDebuggerGlobalScope.reportError_childWorker.js + WorkerDebuggerGlobalScope.reportError_debugger.js + WorkerDebuggerGlobalScope.reportError_worker.js + WorkerDebuggerGlobalScope.setImmediate_debugger.js + WorkerDebuggerGlobalScope.setImmediate_worker.js WorkerDebuggerManager_childWorker.js - WorkerDebuggerManager_parentWorker.js + WorkerDebuggerManager_worker.js WorkerDebugger_childWorker.js - WorkerDebugger_parentWorker.js + WorkerDebugger_worker.js WorkerDebugger_sharedWorker.js WorkerTest.jsm WorkerTest_subworker.js @@ -28,6 +49,13 @@ support-files = bug1062920_worker.js [test_WorkerDebugger.xul] +[test_WorkerDebugger.initialize.xul] +[test_WorkerDebugger.isFrozen.xul] +[test_WorkerDebugger.postMessage.xul] +[test_WorkerDebuggerGlobalScope.createSandbox.xul] +[test_WorkerDebuggerGlobalScope.enterEventLoop.xul] +[test_WorkerDebuggerGlobalScope.reportError.xul] +[test_WorkerDebuggerGlobalScope.setImmediate.xul] [test_WorkerDebuggerManager.xul] [test_bug883784.jsm] [test_bug883784.xul] diff --git a/dom/workers/test/dom_worker_helper.js b/dom/workers/test/dom_worker_helper.js index 9cbfa58916..edc404219c 100644 --- a/dom/workers/test/dom_worker_helper.js +++ b/dom/workers/test/dom_worker_helper.js @@ -13,6 +13,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); const wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"]. getService(Ci.nsIWorkerDebuggerManager); +const BASE_URL = "chrome://mochitests/content/chrome/dom/workers/test/"; + var gRemainingTests = 0; function waitForWorkerFinish() { @@ -47,50 +49,54 @@ function* generateDebuggers() { } } -function findDebugger(predicate) { +function findDebugger(url) { for (let dbg of generateDebuggers()) { - if (predicate(dbg)) { + if (dbg.url === url) { return dbg; } } return null; } -function waitForRegister(predicate = () => true) { +function waitForRegister(url, dbgUrl) { return new Promise(function (resolve) { wdm.addListener({ onRegister: function (dbg) { - if (!predicate(dbg)) { + if (dbg.url !== url) { return; } + ok(true, "Debugger with url " + url + " should be registered."); wdm.removeListener(this); + if (dbgUrl) { + info("Initializing worker debugger with url " + url + "."); + dbg.initialize(dbgUrl); + } resolve(dbg); } }); }); } -function waitForUnregister(predicate = () => true) { +function waitForUnregister(url) { return new Promise(function (resolve) { wdm.addListener({ onUnregister: function (dbg) { - if (!predicate(dbg)) { + if (dbg.url !== url) { return; } + ok(true, "Debugger with url " + url + " should be unregistered."); wdm.removeListener(this); - resolve(dbg); + resolve(); } }); }); } -function waitForDebuggerClose(dbg, predicate = () => true) { +function waitForDebuggerClose(dbg) { return new Promise(function (resolve) { dbg.addListener({ onClose: function () { - if (!predicate()) { - return; - } + ok(true, "Debugger should be closed."); dbg.removeListener(this); resolve(); } @@ -98,17 +104,92 @@ function waitForDebuggerClose(dbg, predicate = () => true) { }); } +function waitForDebuggerError(dbg) { + return new Promise(function (resolve) { + dbg.addListener({ + onError: function (filename, lineno, message) { + dbg.removeListener(this); + resolve(new Error(message, filename, lineno)); + } + }); + }); +} + +function waitForDebuggerMessage(dbg, message) { + return new Promise(function (resolve) { + dbg.addListener({ + onMessage: function (message1) { + if (message !== message1) { + return; + } + ok(true, "Should receive " + message + " message from debugger."); + dbg.removeListener(this); + resolve(); + } + }); + }); +} + +function waitForDebuggerFreeze(dbg) { + return new Promise(function (resolve) { + dbg.addListener({ + onFreeze: function () { + dbg.removeListener(this); + resolve(); + } + }); + }); +} + +function waitForDebuggerThaw(dbg) { + return new Promise(function (resolve) { + dbg.addListener({ + onThaw: function () { + dbg.removeListener(this); + resolve(); + } + }); + }); +} + +function waitForWindowMessage(window, message) { + return new Promise(function (resolve) { + let onmessage = function (event) { + if (event.data !== event.data) { + return; + } + window.removeEventListener("message", onmessage, false); + resolve(); + }; + window.addEventListener("message", onmessage, false); + }); +} + +function waitForWorkerMessage(worker, message) { + return new Promise(function (resolve) { + worker.addEventListener("message", function onmessage(event) { + if (event.data !== message) { + return; + } + ok(true, "Should receive " + message + " message from worker."); + worker.removeEventListener("message", onmessage); + resolve(); + }); + }); +} + function waitForMultiple(promises) { return new Promise(function (resolve) { - let results = []; + let values = []; for (let i = 0; i < promises.length; ++i) { - let promise = promises[i]; let index = i; - promise.then(function (result) { - is(results.length, index, "events should occur in the specified order"); - results.push(result); - if (results.length === promises.length) { - resolve(results); + promises[i].then(function (value) { + is(index + 1, values.length + 1, + "Promise " + (values.length + 1) + " out of " + promises.length + + " should be resolved."); + values.push(value); + if (values.length === promises.length) { + resolve(values); } }); } diff --git a/dom/workers/test/test_WorkerDebugger.initialize.xul b/dom/workers/test/test_WorkerDebugger.initialize.xul new file mode 100644 index 0000000000..9e40bb78c0 --- /dev/null +++ b/dom/workers/test/test_WorkerDebugger.initialize.xul @@ -0,0 +1,58 @@ + + + + + + + +

+ +

+  
+  
diff --git a/dom/workers/test/test_WorkerDebugger.isFrozen.xul b/dom/workers/test/test_WorkerDebugger.isFrozen.xul new file mode 100644 index 0000000000..10f56d316e --- /dev/null +++ b/dom/workers/test/test_WorkerDebugger.isFrozen.xul @@ -0,0 +1,98 @@ + + + + + + + +

+ +

+    
+  
+  
diff --git a/dom/workers/test/test_WorkerDebugger.postMessage.xul b/dom/workers/test/test_WorkerDebugger.postMessage.xul new file mode 100644 index 0000000000..7affbed21b --- /dev/null +++ b/dom/workers/test/test_WorkerDebugger.postMessage.xul @@ -0,0 +1,61 @@ + + + + + + + +

+ +

+  
+  
diff --git a/dom/workers/test/test_WorkerDebugger.xul b/dom/workers/test/test_WorkerDebugger.xul index 77c5e71a02..f3397bd54c 100644 --- a/dom/workers/test/test_WorkerDebugger.xul +++ b/dom/workers/test/test_WorkerDebugger.xul @@ -16,7 +16,7 @@ + + +

+ +

+  
+