From 80ebdbabd2c04d5531d168e71f9b5b03469c848e Mon Sep 17 00:00:00 2001 From: Ben Turner Date: Wed, 5 Jun 2013 07:04:23 -0700 Subject: [PATCH] Bug 643325 - Implement SharedWorker. r=khuey. --HG-- extra : transplant_source : %B6%DD%5B%B6%D1%2C%9D%A9%FCK%60%E8%8F%0C-xz%91%5E%9E --- .../test/test_all_synthetic_events.html | 4 + dom/base/nsGlobalWindow.cpp | 22 +- dom/bindings/BindingDeclarations.h | 2 +- dom/bindings/Bindings.conf | 24 +- .../mochitest/general/test_interfaces.html | 1 + dom/webidl/AbstractWorker.webidl | 10 + dom/webidl/ErrorEvent.webidl | 21 + dom/webidl/SharedWorker.webidl | 13 + dom/webidl/WorkerMessagePort.webidl | 18 + dom/webidl/moz.build | 5 +- dom/workers/DOMBindingInlines.h | 11 +- dom/workers/Events.cpp | 78 +- dom/workers/Events.h | 3 + dom/workers/FileReaderSync.cpp | 16 +- dom/workers/FileReaderSync.h | 8 +- dom/workers/Location.cpp | 3 - dom/workers/MessagePort.cpp | 209 +++ dom/workers/MessagePort.h | 112 ++ dom/workers/Navigator.cpp | 4 +- dom/workers/RuntimeService.cpp | 313 +++- dom/workers/RuntimeService.h | 55 +- dom/workers/ScriptLoader.cpp | 198 +-- dom/workers/ScriptLoader.h | 6 +- dom/workers/SharedWorker.cpp | 235 +++ dom/workers/SharedWorker.h | 104 ++ dom/workers/Worker.cpp | 128 +- dom/workers/Worker.h | 3 +- dom/workers/WorkerMessagePort.cpp | 225 +++ dom/workers/WorkerMessagePort.h | 103 ++ dom/workers/WorkerPrivate.cpp | 1565 ++++++++++++----- dom/workers/WorkerPrivate.h | 314 +++- dom/workers/WorkerScope.cpp | 479 ++++- dom/workers/WorkerScope.h | 7 +- dom/workers/Workers.h | 6 +- dom/workers/moz.build | 8 +- dom/workers/test/Makefile.in | 1 - dom/workers/test/mochitest.ini | 6 + .../test/multi_sharedWorker_frame.html | 52 + .../test/multi_sharedWorker_sharedWorker.js | 72 + dom/workers/test/sharedWorker_sharedWorker.js | 82 + dom/workers/test/test_multi_sharedWorker.html | 251 +++ .../test_multi_sharedWorker_lifetimes.html | 151 ++ dom/workers/test/test_sharedWorker.html | 76 + modules/libpref/src/init/all.js | 3 + 44 files changed, 4167 insertions(+), 840 deletions(-) create mode 100644 dom/webidl/AbstractWorker.webidl create mode 100644 dom/webidl/ErrorEvent.webidl create mode 100644 dom/webidl/SharedWorker.webidl create mode 100644 dom/webidl/WorkerMessagePort.webidl create mode 100644 dom/workers/MessagePort.cpp create mode 100644 dom/workers/MessagePort.h create mode 100644 dom/workers/SharedWorker.cpp create mode 100644 dom/workers/SharedWorker.h create mode 100644 dom/workers/WorkerMessagePort.cpp create mode 100644 dom/workers/WorkerMessagePort.h create mode 100644 dom/workers/test/multi_sharedWorker_frame.html create mode 100644 dom/workers/test/multi_sharedWorker_sharedWorker.js create mode 100644 dom/workers/test/sharedWorker_sharedWorker.js create mode 100644 dom/workers/test/test_multi_sharedWorker.html create mode 100644 dom/workers/test/test_multi_sharedWorker_lifetimes.html create mode 100644 dom/workers/test/test_sharedWorker.html diff --git a/content/events/test/test_all_synthetic_events.html b/content/events/test/test_all_synthetic_events.html index c4d17934b955..ad2a4b121684 100644 --- a/content/events/test/test_all_synthetic_events.html +++ b/content/events/test/test_all_synthetic_events.html @@ -135,6 +135,10 @@ const kEventConstructors = { return e; }, }, + ErrorEvent: { create: function (aName, aProps) { + return new ErrorEvent(aName, aProps); + }, + }, ElementReplaceEvent: { create: function (aName, aProps) { return new ElementReplaceEvent(aName, aProps); }, diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index af8012ca1caf..35761336aa9e 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1433,12 +1433,7 @@ nsGlobalWindow::FreeInnerObjects() NotifyDOMWindowDestroyed(this); // Kill all of the workers for this window. - // We push a cx so that exceptions get reported in the right DOM Window. - { - nsIScriptContext *scx = GetContextInternal(); - AutoPushJSContext cx(scx ? scx->GetNativeContext() : nsContentUtils::GetSafeJSContext()); - mozilla::dom::workers::CancelWorkersForWindow(cx, this); - } + mozilla::dom::workers::CancelWorkersForWindow(this); // Close all offline storages for this window. quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); @@ -11240,12 +11235,7 @@ nsGlobalWindow::SuspendTimeouts(uint32_t aIncrease, DisableGamepadUpdates(); // Suspend all of the workers for this window. - // We push a cx so that exceptions get reported in the right DOM Window. - { - nsIScriptContext *scx = GetContextInternal(); - AutoPushJSContext cx(scx ? scx->GetNativeContext() : nsContentUtils::GetSafeJSContext()); - mozilla::dom::workers::SuspendWorkersForWindow(cx, this); - } + mozilla::dom::workers::SuspendWorkersForWindow(this); TimeStamp now = TimeStamp::Now(); for (nsTimeout *t = mTimeouts.getFirst(); t; t = t->getNext()) { @@ -11334,10 +11324,7 @@ nsGlobalWindow::ResumeTimeouts(bool aThawChildren) } // Resume all of the workers for this window. - // We push a cx so that exceptions get reported in the right DOM Window. - nsIScriptContext *scx = GetContextInternal(); - AutoPushJSContext cx(scx ? scx->GetNativeContext() : nsContentUtils::GetSafeJSContext()); - mozilla::dom::workers::ResumeWorkersForWindow(scx, this); + mozilla::dom::workers::ResumeWorkersForWindow(this); // Restore all of the timeouts, using the stored time remaining // (stored in timeout->mTimeRemaining). @@ -12129,5 +12116,4 @@ nsGlobalWindow::DisableNetworkEvent(uint32_t aType) #undef WINDOW_ONLY_EVENT #undef BEFOREUNLOAD_EVENT #undef ERROR_EVENT -#undef EVENT - +#undef EVENT \ No newline at end of file diff --git a/dom/bindings/BindingDeclarations.h b/dom/bindings/BindingDeclarations.h index e8445869f77d..835070fe9d41 100644 --- a/dom/bindings/BindingDeclarations.h +++ b/dom/bindings/BindingDeclarations.h @@ -621,4 +621,4 @@ struct ParentObject { } // namespace dom } // namespace mozilla -#endif // mozilla_dom_BindingDeclarations_h__ +#endif // mozilla_dom_BindingDeclarations_h__ \ No newline at end of file diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 15525c1f7875..1a13d2dce938 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -83,6 +83,10 @@ DOMInterfaces = { 'nativeType': 'mozilla::dom::Activity', }, +'AbstractWorker': { + 'concrete': False +}, + 'AnimationEvent': { 'nativeType': 'nsDOMAnimationEvent', }, @@ -943,6 +947,12 @@ DOMInterfaces = { 'nativeType': 'nsDOMScrollAreaEvent', }, +'SharedWorker': { + 'nativeType': 'mozilla::dom::workers::SharedWorker', + 'headerFile': 'mozilla/dom/workers/bindings/SharedWorker.h', + 'implicitJSContext': [ 'constructor' ], +}, + 'SimpleGestureEvent': { 'nativeType': 'nsDOMSimpleGestureEvent', }, @@ -1438,6 +1448,18 @@ DOMInterfaces = { 'workers': True, }, +'WorkerMessagePort': [{ + 'nativeType': 'mozilla::dom::workers::MessagePort', + 'headerFile': 'mozilla/dom/workers/bindings/MessagePort.h', + 'implicitJSContext': [ 'postMessage' ], +}, +{ + 'nativeType': 'mozilla::dom::workers::WorkerMessagePort', + 'headerFile': 'mozilla/dom/workers/bindings/WorkerMessagePort.h', + 'workers': True, + 'nativeOwnership': 'worker', +}], + 'WorkerNavigator': { 'headerFile': 'mozilla/dom/workers/bindings/Navigator.h', 'workers': True, @@ -1863,4 +1885,4 @@ addExternalIface('CameraReleaseCallback', nativeType='nsICameraReleaseCallback', addExternalIface('CameraStartRecordingCallback', nativeType='nsICameraStartRecordingCallback', headerFile='nsIDOMCameraManager.h') addExternalIface('CameraPreviewStateChange', nativeType='nsICameraPreviewStateChange', headerFile='nsIDOMCameraManager.h') addExternalIface('CameraPreviewStreamCallback', nativeType='nsICameraPreviewStreamCallback', headerFile='nsIDOMCameraManager.h') -addExternalIface('CameraRecorderStateChange', nativeType='nsICameraRecorderStateChange', headerFile='nsIDOMCameraManager.h') +addExternalIface('CameraRecorderStateChange', nativeType='nsICameraRecorderStateChange', headerFile='nsIDOMCameraManager.h') \ No newline at end of file diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 99a29b7024f4..54713a890688 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -184,6 +184,7 @@ var interfaceNamesInGlobalScope = "DynamicsCompressorNode", "Element", "ElementReplaceEvent", + "ErrorEvent", "Event", "EventListenerInfo", "EventSource", diff --git a/dom/webidl/AbstractWorker.webidl b/dom/webidl/AbstractWorker.webidl new file mode 100644 index 000000000000..b453b48b7826 --- /dev/null +++ b/dom/webidl/AbstractWorker.webidl @@ -0,0 +1,10 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +[NoInterfaceObject] +interface AbstractWorker { + attribute EventHandler onerror; +}; diff --git a/dom/webidl/ErrorEvent.webidl b/dom/webidl/ErrorEvent.webidl new file mode 100644 index 000000000000..c4ca51ef5bd4 --- /dev/null +++ b/dom/webidl/ErrorEvent.webidl @@ -0,0 +1,21 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +[Constructor(DOMString type, optional ErrorEventInit eventInitDict)] +interface ErrorEvent : Event +{ + readonly attribute DOMString message; + readonly attribute DOMString filename; + readonly attribute unsigned long lineno; + readonly attribute unsigned long column; +}; + +dictionary ErrorEventInit : EventInit +{ + DOMString message = ""; + DOMString filename = ""; + unsigned long lineno = 0; + unsigned long column = 0; +}; diff --git a/dom/webidl/SharedWorker.webidl b/dom/webidl/SharedWorker.webidl new file mode 100644 index 000000000000..661d8d3c07d0 --- /dev/null +++ b/dom/webidl/SharedWorker.webidl @@ -0,0 +1,13 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +[PrefControlled, + Constructor(DOMString scriptURL, optional DOMString name)] +interface SharedWorker : EventTarget { + readonly attribute WorkerMessagePort port; +}; + +SharedWorker implements AbstractWorker; diff --git a/dom/webidl/WorkerMessagePort.webidl b/dom/webidl/WorkerMessagePort.webidl new file mode 100644 index 000000000000..f67a0218b7f9 --- /dev/null +++ b/dom/webidl/WorkerMessagePort.webidl @@ -0,0 +1,18 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// XXX Remove me soon! +[PrefControlled] +interface WorkerMessagePort : EventTarget { + [Throws] + void postMessage(any message, optional sequence transferable); + + void start(); + + void close(); + + [SetterThrows=Workers, GetterThrows=Workers] + attribute EventHandler onmessage; +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index f99f0f013523..6615fa65b4fd 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -16,6 +16,7 @@ PREPROCESSED_WEBIDL_FILES = [ ] WEBIDL_FILES = [ + 'AbstractWorker.webidl', 'AnalyserNode.webidl', 'AnimationEvent.webidl', 'ArchiveReader.webidl', @@ -366,6 +367,7 @@ WEBIDL_FILES = [ 'ScriptProcessorNode.webidl', 'ScrollAreaEvent.webidl', 'SettingsManager.webidl', + 'SharedWorker.webidl', 'SimpleGestureEvent.webidl', 'SourceBuffer.webidl', 'SourceBufferList.webidl', @@ -406,6 +408,7 @@ WEBIDL_FILES = [ 'WifiOptions.webidl', 'Window.webidl', 'WorkerLocation.webidl', + 'WorkerMessagePort.webidl', 'WorkerNavigator.webidl', 'XMLDocument.webidl', 'XMLHttpRequest.webidl', @@ -537,6 +540,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [ 'BlobEvent.webidl', 'DeviceLightEvent.webidl', 'DeviceProximityEvent.webidl', + 'ErrorEvent.webidl', 'MediaStreamEvent.webidl', 'MozInterAppMessageEvent.webidl', 'RTCDataChannelEvent.webidl', @@ -551,4 +555,3 @@ if CONFIG['MOZ_GAMEPAD']: 'GamepadEvent.webidl', ] - diff --git a/dom/workers/DOMBindingInlines.h b/dom/workers/DOMBindingInlines.h index 28f8054b38ae..4a5c45719c5a 100644 --- a/dom/workers/DOMBindingInlines.h +++ b/dom/workers/DOMBindingInlines.h @@ -6,17 +6,19 @@ #ifndef mozilla_dom_workers_dombindinginlines_h__ #define mozilla_dom_workers_dombindinginlines_h__ +#include "jsfriendapi.h" #include "mozilla/dom/JSSlots.h" +#include "mozilla/dom/URLBinding.h" +#include "mozilla/dom/WorkerMessagePortBinding.h" #include "mozilla/dom/XMLHttpRequestBinding.h" #include "mozilla/dom/XMLHttpRequestUploadBinding.h" -#include "mozilla/dom/URLBinding.h" -#include "jsfriendapi.h" BEGIN_WORKERS_NAMESPACE +class URL; +class WorkerMessagePort; class XMLHttpRequest; class XMLHttpRequestUpload; -class URL; namespace { @@ -45,9 +47,10 @@ struct WrapPrototypeTraits } \ }; +SPECIALIZE_PROTO_TRAITS(URL) +SPECIALIZE_PROTO_TRAITS(WorkerMessagePort) SPECIALIZE_PROTO_TRAITS(XMLHttpRequest) SPECIALIZE_PROTO_TRAITS(XMLHttpRequestUpload) -SPECIALIZE_PROTO_TRAITS(URL) #undef SPECIALIZE_PROTO_TRAITS diff --git a/dom/workers/Events.cpp b/dom/workers/Events.cpp index e903ac398250..9d42a6897ea4 100644 --- a/dom/workers/Events.cpp +++ b/dom/workers/Events.cpp @@ -414,8 +414,10 @@ public: } static JSObject* - Create(JSContext* aCx, JS::Handle aParent, JSAutoStructuredCloneBuffer& aData, - nsTArray >& aClonedObjects, bool aMainRuntime) + Create(JSContext* aCx, JS::Handle aParent, + JSAutoStructuredCloneBuffer& aData, + nsTArray >& aClonedObjects, + bool aMainRuntime) { JS::Rooted type(aCx, JS_InternString(aCx, "message")); if (!type) { @@ -429,16 +431,57 @@ public: return NULL; } + JS::Rooted ports(aCx, JS_NewArrayObject(aCx, 0, nullptr)); + if (!ports) { + return NULL; + } + MessageEvent* priv = new MessageEvent(aMainRuntime); SetJSPrivateSafeish(obj, priv); + InitMessageEventCommon(aCx, obj, priv, type, false, false, NULL, NULL, NULL, - true); + ports, true); + priv->mBuffer.swap(aData); priv->mClonedObjects.SwapElements(aClonedObjects); return obj; } + static JSObject* + Create(JSContext* aCx, JS::Handle aParent, + JS::Handle aType, bool aBubbles, bool aCancelable, + JS::Handle aData, JS::Handle aOrigin, + JS::Handle aSource, JS::Handle aMessagePort, + bool aIsTrusted) + { + JS::Rooted obj(aCx, + JS_NewObject(aCx, &sClass, nullptr, aParent)); + if (!obj) { + return nullptr; + } + + JS::Rooted ports(aCx); + if (aMessagePort) { + JS::Value port = OBJECT_TO_JSVAL(aMessagePort); + ports = JS_NewArrayObject(aCx, 1, &port); + } else { + ports = JS_NewArrayObject(aCx, 0, nullptr); + } + + if (!ports) { + return NULL; + } + + MessageEvent* priv = new MessageEvent(false); + SetJSPrivateSafeish(obj, priv); + + InitMessageEventCommon(aCx, obj, priv, aType, aBubbles, aCancelable, aData, + aOrigin, aSource, ports, aIsTrusted); + + return obj; + } + protected: MessageEvent(bool aMainRuntime) : mMainRuntime(aMainRuntime) @@ -455,6 +498,7 @@ protected: SLOT_data = Event::SLOT_COUNT, SLOT_origin, SLOT_source, + SLOT_ports, SLOT_COUNT, SLOT_FIRST = SLOT_data @@ -479,7 +523,7 @@ private: InitMessageEventCommon(JSContext* aCx, JSObject* aObj, Event* aEvent, JSString* aType, bool aBubbles, bool aCancelable, JSString* aData, JSString* aOrigin, JSObject* aSource, - bool aIsTrusted) + JS::Handle aMessagePorts, bool aIsTrusted) { jsval emptyString = JS_GetEmptyStringValue(aCx); @@ -490,6 +534,7 @@ private: JS_SetReservedSlot(aObj, SLOT_origin, aOrigin ? STRING_TO_JSVAL(aOrigin) : emptyString); JS_SetReservedSlot(aObj, SLOT_source, OBJECT_TO_JSVAL(aSource)); + JS_SetReservedSlot(aObj, SLOT_ports, OBJECT_TO_JSVAL(aMessagePorts)); } static bool @@ -591,7 +636,7 @@ private: } InitMessageEventCommon(aCx, obj, event, type, bubbles, cancelable, - data, origin, source, false); + data, origin, source, JS::NullPtr(), false); return true; } }; @@ -616,6 +661,8 @@ const JSPropertySpec MessageEvent::sProperties[] = { JSPROP_ENUMERATE), JS_PSGS("source", Property::Get, GetterOnlyJSNative, JSPROP_ENUMERATE), + JS_PSGS("ports", Property::Get, GetterOnlyJSNative, + JSPROP_ENUMERATE), JS_PS_END }; @@ -1049,6 +1096,25 @@ CreateProgressEvent(JSContext* aCx, JS::Handle aType, bool aLengthCom aTotal); } +JSObject* +CreateConnectEvent(JSContext* aCx, JS::Handle aMessagePort) +{ + JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); + + JS::Rooted type(aCx, JS_InternString(aCx, "connect")); + if (!type) { + return nullptr; + } + + JS::Rooted emptyStr(aCx, JS_GetEmptyString(JS_GetRuntime(aCx))); + if (!emptyStr) { + return nullptr; + } + + return MessageEvent::Create(aCx, global, type, false, false, emptyStr, + emptyStr, JS::NullPtr(), aMessagePort, true); +} + bool IsSupportedEventClass(JSObject* aEvent) { @@ -1100,4 +1166,4 @@ DispatchEventToTarget(JSContext* aCx, JS::Handle aTarget, } // namespace events -END_WORKERS_NAMESPACE +END_WORKERS_NAMESPACE \ No newline at end of file diff --git a/dom/workers/Events.h b/dom/workers/Events.h index 185734625e00..80920569cd6d 100644 --- a/dom/workers/Events.h +++ b/dom/workers/Events.h @@ -35,6 +35,9 @@ JSObject* CreateProgressEvent(JSContext* aCx, JS::Handle aType, bool aLengthComputable, double aLoaded, double aTotal); +JSObject* +CreateConnectEvent(JSContext* aCx, JS::Handle aMessagePort); + bool IsSupportedEventClass(JSObject* aEvent); diff --git a/dom/workers/FileReaderSync.cpp b/dom/workers/FileReaderSync.cpp index 7d9ec0e2678c..91aecc9220d9 100644 --- a/dom/workers/FileReaderSync.cpp +++ b/dom/workers/FileReaderSync.cpp @@ -6,6 +6,10 @@ #include "FileReaderSync.h" +#include "jsfriendapi.h" +#include "mozilla/Base64.h" +#include "mozilla/dom/EncodingUtils.h" +#include "mozilla/dom/FileReaderSyncBinding.h" #include "nsCExternalHandlerService.h" #include "nsComponentManagerUtils.h" #include "nsCOMPtr.h" @@ -21,12 +25,9 @@ #include "nsISupportsImpl.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" + #include "File.h" #include "RuntimeService.h" -#include "DOMBindingInlines.h" - -#include "mozilla/Base64.h" -#include "mozilla/dom/EncodingUtils.h" USING_WORKERS_NAMESPACE using namespace mozilla; @@ -35,6 +36,7 @@ using mozilla::dom::GlobalObject; NS_IMPL_ADDREF(FileReaderSync) NS_IMPL_RELEASE(FileReaderSync) + NS_INTERFACE_MAP_BEGIN(FileReaderSync) NS_INTERFACE_MAP_ENTRY(nsICharsetDetectionObserver) NS_INTERFACE_MAP_END @@ -48,6 +50,12 @@ FileReaderSync::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) return frs.forget(); } +JSObject* +FileReaderSync::WrapObject(JSContext* aCx, JS::HandleObject aScope) +{ + return FileReaderSyncBinding_workers::Wrap(aCx, aScope, this); +} + JSObject* FileReaderSync::ReadAsArrayBuffer(JSContext* aCx, JS::Handle aScopeObj, diff --git a/dom/workers/FileReaderSync.h b/dom/workers/FileReaderSync.h index 622339d749e8..3174068ab485 100644 --- a/dom/workers/FileReaderSync.h +++ b/dom/workers/FileReaderSync.h @@ -10,9 +10,6 @@ #include "Workers.h" #include "nsICharsetDetectionObserver.h" -#include "nsString.h" -#include "mozilla/Attributes.h" -#include "mozilla/dom/FileReaderSyncBinding.h" class nsIInputStream; class nsIDOMBlob; @@ -39,10 +36,7 @@ public: static already_AddRefed Constructor(const GlobalObject& aGlobal, ErrorResult& aRv); - JSObject* WrapObject(JSContext* aCx, JS::Handle aScope) - { - return FileReaderSyncBinding_workers::Wrap(aCx, aScope, this); - } + JSObject* WrapObject(JSContext* aCx, JS::HandleObject aScope); NS_DECL_ISUPPORTS diff --git a/dom/workers/Location.cpp b/dom/workers/Location.cpp index 9c3085f4ab5f..62df23421dec 100644 --- a/dom/workers/Location.cpp +++ b/dom/workers/Location.cpp @@ -5,9 +5,6 @@ #include "Location.h" -#include "DOMBindingInlines.h" - -#include "nsTraceRefcnt.h" #include "mozilla/dom/WorkerLocationBinding.h" BEGIN_WORKERS_NAMESPACE diff --git a/dom/workers/MessagePort.cpp b/dom/workers/MessagePort.cpp new file mode 100644 index 000000000000..82f42681c2ef --- /dev/null +++ b/dom/workers/MessagePort.cpp @@ -0,0 +1,209 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MessagePort.h" + +#include "mozilla/dom/WorkerMessagePortBinding.h" +#include "nsDOMEvent.h" +#include "nsEventDispatcher.h" + +#include "SharedWorker.h" + +using mozilla::dom::Optional; +using mozilla::dom::Sequence; + +USING_WORKERS_NAMESPACE + +namespace { + +class DelayedEventRunnable MOZ_FINAL : public nsIRunnable +{ + nsRefPtr mMessagePort; + nsCOMPtr mEvent; + +public: + DelayedEventRunnable(MessagePort* aMessagePort, + already_AddRefed aEvent) + : mMessagePort(aMessagePort), mEvent(aEvent) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aMessagePort); + MOZ_ASSERT(aEvent.get()); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIRUNNABLE +}; + +} // anonymous namespace + +MessagePort::MessagePort(nsPIDOMWindow* aWindow, SharedWorker* aSharedWorker, + uint64_t aSerial) +: nsDOMEventTargetHelper(aWindow), mSharedWorker(aSharedWorker), + mSerial(aSerial), mStarted(false) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aSharedWorker); +} + +MessagePort::~MessagePort() +{ + AssertIsOnMainThread(); + + Close(); +} + +// static +bool +MessagePort::PrefEnabled() +{ + AssertIsOnMainThread(); + + // Currently tied to the SharedWorker preference. + return SharedWorker::PrefEnabled(); +} + +void +MessagePort::PostMessage(JSContext* aCx, JS::HandleValue aMessage, + const Optional>& aTransferable, + ErrorResult& aRv) +{ + AssertIsOnMainThread(); + + if (IsClosed()) { + aRv = NS_ERROR_DOM_INVALID_STATE_ERR; + return; + } + + mSharedWorker->PostMessage(aCx, aMessage, aTransferable, aRv); +} + +void +MessagePort::Start() +{ + AssertIsOnMainThread(); + + if (IsClosed()) { + NS_WARNING("Called start() after calling close()!"); + return; + } + + if (mStarted) { + return; + } + + mStarted = true; + + if (!mQueuedEvents.IsEmpty()) { + for (uint32_t index = 0; index < mQueuedEvents.Length(); index++) { + nsCOMPtr runnable = + new DelayedEventRunnable(this, mQueuedEvents[index].forget()); + if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { + NS_WARNING("Failed to dispatch queued event!"); + } + } + mQueuedEvents.Clear(); + } +} + +void +MessagePort::QueueEvent(nsIDOMEvent* aEvent) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aEvent); + MOZ_ASSERT(!IsClosed()); + MOZ_ASSERT(!mStarted); + + mQueuedEvents.AppendElement(aEvent); +} + +void +MessagePort::CloseInternal() +{ + AssertIsOnMainThread(); + MOZ_ASSERT(!IsClosed()); + MOZ_ASSERT_IF(mStarted, mQueuedEvents.IsEmpty()); + + NS_WARN_IF_FALSE(mStarted, "Called close() before start()!"); + + if (!mStarted) { + mQueuedEvents.Clear(); + } + + mSharedWorker = nullptr; +} + +NS_IMPL_ADDREF_INHERITED(MessagePort, nsDOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(MessagePort, nsDOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MessagePort) +NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) + +NS_IMPL_CYCLE_COLLECTION_CLASS(MessagePort) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MessagePort, + nsDOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSharedWorker) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEvents) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessagePort, + nsDOMEventTargetHelper) + tmp->Close(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +JSObject* +MessagePort::WrapObject(JSContext* aCx, JS::HandleObject aScope) +{ + AssertIsOnMainThread(); + + return WorkerMessagePortBinding::Wrap(aCx, aScope, this); +} + +nsresult +MessagePort::PreHandleEvent(nsEventChainPreVisitor& aVisitor) +{ + AssertIsOnMainThread(); + + nsIDOMEvent*& event = aVisitor.mDOMEvent; + + if (event) { + bool preventDispatch = false; + + if (IsClosed()) { + preventDispatch = true; + } else if (mSharedWorker->IsSuspended()) { + mSharedWorker->QueueEvent(event); + preventDispatch = true; + } else if (!mStarted) { + QueueEvent(event); + preventDispatch = true; + } + + if (preventDispatch) { + aVisitor.mCanHandle = false; + aVisitor.mParentTarget = nullptr; + return NS_OK; + } + } + + return nsDOMEventTargetHelper::PreHandleEvent(aVisitor); +} + +NS_IMPL_ISUPPORTS1(DelayedEventRunnable, nsIRunnable) + +NS_IMETHODIMP +DelayedEventRunnable::Run() +{ + AssertIsOnMainThread(); + MOZ_ASSERT(mMessagePort); + MOZ_ASSERT(mEvent); + + bool ignored; + nsresult rv = mMessagePort->DispatchEvent(mEvent, &ignored); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} diff --git a/dom/workers/MessagePort.h b/dom/workers/MessagePort.h new file mode 100644 index 000000000000..1004989b95c6 --- /dev/null +++ b/dom/workers/MessagePort.h @@ -0,0 +1,112 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_messageport_h_ +#define mozilla_dom_workers_messageport_h_ + +#include "Workers.h" + +#include "mozilla/dom/BindingDeclarations.h" +#include "nsDOMEventTargetHelper.h" + +class nsIDOMEvent; +class nsPIDOMWindow; + +BEGIN_WORKERS_NAMESPACE + +class SharedWorker; + +class MessagePort MOZ_FINAL : public nsDOMEventTargetHelper +{ + friend class SharedWorker; + + typedef mozilla::ErrorResult ErrorResult; + + nsRefPtr mSharedWorker; + nsTArray> mQueuedEvents; + uint64_t mSerial; + bool mStarted; + +public: + static bool + PrefEnabled(); + + void + PostMessage(JSContext* aCx, JS::HandleValue aMessage, + const Optional>& aTransferable, + ErrorResult& aRv); + + void + Start(); + + void + Close() + { + AssertIsOnMainThread(); + + if (!IsClosed()) { + CloseInternal(); + } + } + + uint64_t + Serial() const + { + return mSerial; + } + + void + QueueEvent(nsIDOMEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MessagePort, nsDOMEventTargetHelper) + + EventHandlerNonNull* + GetOnmessage() + { + AssertIsOnMainThread(); + + return GetEventHandler(nsGkAtoms::onmessage, EmptyString()); + } + + void + SetOnmessage(EventHandlerNonNull* aCallback) + { + AssertIsOnMainThread(); + + SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback); + + Start(); + } + + bool + IsClosed() const + { + AssertIsOnMainThread(); + + return !mSharedWorker; + } + + virtual JSObject* + WrapObject(JSContext* aCx, JS::HandleObject aScope) MOZ_OVERRIDE; + + virtual nsresult + PreHandleEvent(nsEventChainPreVisitor& aVisitor) MOZ_OVERRIDE; + +private: + // This class can only be created by SharedWorker. + MessagePort(nsPIDOMWindow* aWindow, SharedWorker* aSharedWorker, + uint64_t aSerial); + + // This class is reference-counted and will be destroyed from Release(). + ~MessagePort(); + + void + CloseInternal(); +}; + +END_WORKERS_NAMESPACE + +#endif // mozilla_dom_workers_messageport_h_ diff --git a/dom/workers/Navigator.cpp b/dom/workers/Navigator.cpp index d2969bbfd3e2..6bff8c103d41 100644 --- a/dom/workers/Navigator.cpp +++ b/dom/workers/Navigator.cpp @@ -5,10 +5,10 @@ #include "Navigator.h" -#include "DOMBindingInlines.h" -#include "RuntimeService.h" #include "mozilla/dom/WorkerNavigatorBinding.h" +#include "RuntimeService.h" + BEGIN_WORKERS_NAMESPACE NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerNavigator) diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index d89e083f3f42..1845a0982cc9 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -6,15 +6,19 @@ #include "RuntimeService.h" +#include "nsIChannel.h" #include "nsIContentSecurityPolicy.h" +#include "nsIDocument.h" #include "nsIDOMChromeWindow.h" #include "nsIEffectiveTLDService.h" #include "nsIObserverService.h" #include "nsIPlatformCharset.h" #include "nsIPrincipal.h" +#include "nsIScriptContext.h" #include "nsIScriptSecurityManager.h" #include "nsISupportsPriority.h" #include "nsITimer.h" +#include "nsIURI.h" #include "nsPIDOMWindow.h" #include @@ -36,12 +40,14 @@ #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" +#include "nsTraceRefcnt.h" #include "nsXPCOM.h" #include "nsXPCOMPrivate.h" #include "OSFileConstants.h" #include "xpcpublic.h" #include "Events.h" +#include "SharedWorker.h" #include "Worker.h" #include "WorkerPrivate.h" @@ -1030,32 +1036,32 @@ ResolveWorkerClasses(JSContext* aCx, JS::Handle aObj, JS::HandleCancelWorkersForWindow(aCx, aWindow); + runtime->CancelWorkersForWindow(aWindow); } } void -SuspendWorkersForWindow(JSContext* aCx, nsPIDOMWindow* aWindow) +SuspendWorkersForWindow(nsPIDOMWindow* aWindow) { AssertIsOnMainThread(); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { - runtime->SuspendWorkersForWindow(aCx, aWindow); + runtime->SuspendWorkersForWindow(aWindow); } } void -ResumeWorkersForWindow(nsIScriptContext* aCx, nsPIDOMWindow* aWindow) +ResumeWorkersForWindow(nsPIDOMWindow* aWindow) { AssertIsOnMainThread(); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { - runtime->ResumeWorkersForWindow(aCx, aWindow); + runtime->ResumeWorkersForWindow(aWindow); } } @@ -1205,11 +1211,32 @@ RuntimeService::RegisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) } } + bool isSharedWorker = aWorkerPrivate->IsSharedWorker(); + + const nsString& sharedWorkerName = aWorkerPrivate->SharedWorkerName(); + nsCString sharedWorkerScriptSpec; + + if (isSharedWorker) { + AssertIsOnMainThread(); + + nsCOMPtr scriptURI = aWorkerPrivate->GetResolvedScriptURI(); + NS_ASSERTION(scriptURI, "Null script URI!"); + + nsresult rv = scriptURI->GetSpec(sharedWorkerScriptSpec); + if (NS_FAILED(rv)) { + NS_WARNING("GetSpec failed?!"); + xpc::Throw(aCx, rv); + return false; + } + + NS_ASSERTION(!sharedWorkerScriptSpec.IsEmpty(), "Empty spec!"); + } + + const nsCString& domain = aWorkerPrivate->Domain(); + WorkerDomainInfo* domainInfo; bool queued = false; { - const nsCString& domain = aWorkerPrivate->Domain(); - MutexAutoLock lock(mMutex); if (!mDomainMap.Get(domain, &domainInfo)) { @@ -1220,26 +1247,29 @@ RuntimeService::RegisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) mDomainMap.Put(domain, domainInfo); } - if (domainInfo) { - queued = gMaxWorkersPerDomain && - domainInfo->ActiveWorkerCount() >= gMaxWorkersPerDomain && - !domain.IsEmpty(); + queued = gMaxWorkersPerDomain && + domainInfo->ActiveWorkerCount() >= gMaxWorkersPerDomain && + !domain.IsEmpty(); - if (queued) { - domainInfo->mQueuedWorkers.AppendElement(aWorkerPrivate); - } - else if (parent) { - domainInfo->mChildWorkerCount++; - } - else { - domainInfo->mActiveWorkers.AppendElement(aWorkerPrivate); - } + if (queued) { + domainInfo->mQueuedWorkers.AppendElement(aWorkerPrivate); + } + else if (parent) { + domainInfo->mChildWorkerCount++; + } + else { + domainInfo->mActiveWorkers.AppendElement(aWorkerPrivate); } - } - if (!domainInfo) { - JS_ReportOutOfMemory(aCx); - return false; + if (isSharedWorker) { + MOZ_ASSERT(!domainInfo->mSharedWorkerInfos.Get(sharedWorkerScriptSpec)); + + SharedWorkerInfo* sharedWorkerInfo = + new SharedWorkerInfo(aWorkerPrivate, sharedWorkerScriptSpec, + sharedWorkerName); + domainInfo->mSharedWorkerInfos.Put(sharedWorkerScriptSpec, + sharedWorkerInfo); + } } // From here on out we must call UnregisterWorker if something fails! @@ -1267,15 +1297,15 @@ RuntimeService::RegisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) nsTArray* windowArray; if (!mWindowMap.Get(window, &windowArray)) { - NS_ASSERTION(!parent, "Shouldn't have a parent here!"); - windowArray = new nsTArray(1); mWindowMap.Put(window, windowArray); } - NS_ASSERTION(!windowArray->Contains(aWorkerPrivate), - "Already know about this worker!"); - windowArray->AppendElement(aWorkerPrivate); + if (!windowArray->Contains(aWorkerPrivate)) { + windowArray->AppendElement(aWorkerPrivate); + } else { + MOZ_ASSERT(aWorkerPrivate->IsSharedWorker()); + } } if (!queued && !ScheduleWorker(aCx, aWorkerPrivate)) { @@ -1295,10 +1325,10 @@ RuntimeService::UnregisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) AssertIsOnMainThread(); } + const nsCString& domain = aWorkerPrivate->Domain(); + WorkerPrivate* queuedWorker = nullptr; { - const nsCString& domain = aWorkerPrivate->Domain(); - MutexAutoLock lock(mMutex); WorkerDomainInfo* domainInfo; @@ -1322,6 +1352,17 @@ RuntimeService::UnregisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) domainInfo->mActiveWorkers.RemoveElement(aWorkerPrivate); } + if (aWorkerPrivate->IsSharedWorker()) { + MatchSharedWorkerInfo match(aWorkerPrivate); + domainInfo->mSharedWorkerInfos.EnumerateRead(FindSharedWorkerInfo, + &match); + + if (match.mSharedWorkerInfo) { + domainInfo->mSharedWorkerInfos.Remove( + match.mSharedWorkerInfo->mScriptSpec); + } + } + // See if there's a queued worker we can schedule. if (domainInfo->ActiveWorkerCount() < gMaxWorkersPerDomain && !domainInfo->mQueuedWorkers.IsEmpty()) { @@ -1342,23 +1383,39 @@ RuntimeService::UnregisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) } } + if (aWorkerPrivate->IsSharedWorker()) { + AssertIsOnMainThread(); + + nsAutoTArray, 5> sharedWorkersToNotify; + aWorkerPrivate->GetAllSharedWorkers(sharedWorkersToNotify); + + for (uint32_t index = 0; index < sharedWorkersToNotify.Length(); index++) { + MOZ_ASSERT(sharedWorkersToNotify[index]); + sharedWorkersToNotify[index]->NoteDeadWorker(aCx); + } + } + if (parent) { parent->RemoveChildWorker(aCx, aWorkerPrivate); } + else if (aWorkerPrivate->IsSharedWorker()) { + mWindowMap.Enumerate(RemoveSharedWorkerFromWindowMap, aWorkerPrivate); + } else { + // May be null. nsPIDOMWindow* window = aWorkerPrivate->GetWindow(); nsTArray* windowArray; if (!mWindowMap.Get(window, &windowArray)) { - NS_ERROR("Don't have an entry for this window!"); + MOZ_ASSERT(false, "Don't have an entry for this window!"); } - NS_ASSERTION(windowArray->Contains(aWorkerPrivate), - "Don't know about this worker!"); - windowArray->RemoveElement(aWorkerPrivate); + if (!windowArray->RemoveElement(aWorkerPrivate)) { + MOZ_ASSERT(false, "Worker wasn't in the correct window array!"); + } if (windowArray->IsEmpty()) { - NS_ASSERTION(!queuedWorker, "How can this be?!"); + MOZ_ASSERT(!queuedWorker, "queuedWorker should be in this array!"); mWindowMap.Remove(window); } } @@ -1794,6 +1851,48 @@ RuntimeService::AddAllTopLevelWorkersToArray(const nsACString& aKey, return PL_DHASH_NEXT; } +// static +PLDHashOperator +RuntimeService::RemoveSharedWorkerFromWindowMap( + nsPIDOMWindow* aKey, + nsAutoPtr >& aData, + void* aUserArg) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aData.get()); + MOZ_ASSERT(aUserArg); + + auto workerPrivate = static_cast(aUserArg); + + MOZ_ASSERT(workerPrivate->IsSharedWorker()); + + if (aData->RemoveElement(workerPrivate)) { + MOZ_ASSERT(!aData->Contains(workerPrivate), "Added worker more than once!"); + + if (aData->IsEmpty()) { + return PL_DHASH_REMOVE; + } + } + + return PL_DHASH_NEXT; +} + +// static +PLDHashOperator +RuntimeService::FindSharedWorkerInfo(const nsACString& aKey, + SharedWorkerInfo* aData, + void* aUserArg) +{ + auto match = static_cast(aUserArg); + + if (aData->mWorkerPrivate == match->mWorkerPrivate) { + match->mSharedWorkerInfo = aData; + return PL_DHASH_STOP; + } + + return PL_DHASH_NEXT; +} + void RuntimeService::GetWorkersForWindow(nsPIDOMWindow* aWindow, nsTArray& aWorkers) @@ -1811,59 +1910,169 @@ RuntimeService::GetWorkersForWindow(nsPIDOMWindow* aWindow, } void -RuntimeService::CancelWorkersForWindow(JSContext* aCx, - nsPIDOMWindow* aWindow) +RuntimeService::CancelWorkersForWindow(nsPIDOMWindow* aWindow) { AssertIsOnMainThread(); - nsAutoTArray workers; + nsAutoTArray workers; GetWorkersForWindow(aWindow, workers); if (!workers.IsEmpty()) { + nsCOMPtr sgo = do_QueryInterface(aWindow); + MOZ_ASSERT(sgo); + + nsIScriptContext* scx = sgo->GetContext(); + + AutoPushJSContext cx(scx ? + scx->GetNativeContext() : + nsContentUtils::GetSafeJSContext()); + for (uint32_t index = 0; index < workers.Length(); index++) { - if (!workers[index]->Cancel(aCx)) { - NS_WARNING("Failed to cancel worker!"); + WorkerPrivate*& worker = workers[index]; + + if (worker->IsSharedWorker()) { + worker->CloseSharedWorkersForWindow(aWindow); + } else if (!worker->Cancel(cx)) { + JS_ReportPendingException(cx); } } } } void -RuntimeService::SuspendWorkersForWindow(JSContext* aCx, - nsPIDOMWindow* aWindow) +RuntimeService::SuspendWorkersForWindow(nsPIDOMWindow* aWindow) { AssertIsOnMainThread(); + MOZ_ASSERT(aWindow); - nsAutoTArray workers; + nsAutoTArray workers; GetWorkersForWindow(aWindow, workers); if (!workers.IsEmpty()) { + nsCOMPtr sgo = do_QueryInterface(aWindow); + MOZ_ASSERT(sgo); + + nsIScriptContext* scx = sgo->GetContext(); + + AutoPushJSContext cx(scx ? + scx->GetNativeContext() : + nsContentUtils::GetSafeJSContext()); + for (uint32_t index = 0; index < workers.Length(); index++) { - if (!workers[index]->Suspend(aCx)) { - NS_WARNING("Failed to cancel worker!"); + if (!workers[index]->Suspend(cx, aWindow)) { + JS_ReportPendingException(cx); } } } } void -RuntimeService::ResumeWorkersForWindow(nsIScriptContext* aCx, - nsPIDOMWindow* aWindow) +RuntimeService::ResumeWorkersForWindow(nsPIDOMWindow* aWindow) { AssertIsOnMainThread(); + MOZ_ASSERT(aWindow); - nsAutoTArray workers; + nsAutoTArray workers; GetWorkersForWindow(aWindow, workers); if (!workers.IsEmpty()) { + nsCOMPtr sgo = do_QueryInterface(aWindow); + MOZ_ASSERT(sgo); + + nsIScriptContext* scx = sgo->GetContext(); + + AutoPushJSContext cx(scx ? + scx->GetNativeContext() : + nsContentUtils::GetSafeJSContext()); + for (uint32_t index = 0; index < workers.Length(); index++) { - if (!workers[index]->SynchronizeAndResume(aCx)) { - NS_WARNING("Failed to cancel worker!"); + if (!workers[index]->SynchronizeAndResume(cx, aWindow, scx)) { + JS_ReportPendingException(cx); } } } } +nsresult +RuntimeService::CreateSharedWorker(JSContext* aCx, nsPIDOMWindow* aWindow, + const nsAString& aScriptURL, + const nsAString& aName, + SharedWorker** aSharedWorker) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aCx); + MOZ_ASSERT(aWindow); + + WorkerPrivate::LoadInfo loadInfo; + nsresult rv = WorkerPrivate::GetLoadInfo(aCx, aWindow, nullptr, aScriptURL, + false, &loadInfo); + NS_ENSURE_SUCCESS(rv, rv); + + MOZ_ASSERT(loadInfo.mResolvedScriptURI); + + nsCString scriptSpec; + rv = loadInfo.mResolvedScriptURI->GetSpec(scriptSpec); + NS_ENSURE_SUCCESS(rv, rv); + + WorkerPrivate* workerPrivate = nullptr; + { + MutexAutoLock lock(mMutex); + + WorkerDomainInfo* domainInfo; + SharedWorkerInfo* sharedWorkerInfo; + + if (mDomainMap.Get(loadInfo.mDomain, &domainInfo) && + domainInfo->mSharedWorkerInfos.Get(scriptSpec, &sharedWorkerInfo) && + sharedWorkerInfo->mName == aName) { + workerPrivate = sharedWorkerInfo->mWorkerPrivate; + } + } + + bool created = false; + + if (!workerPrivate) { + nsRefPtr newWorkerPrivate = + WorkerPrivate::Create(aCx, JS::NullPtr(), nullptr, aScriptURL, false, + true, aName, &loadInfo); + NS_ENSURE_TRUE(newWorkerPrivate, NS_ERROR_FAILURE); + + if (!RegisterWorker(aCx, newWorkerPrivate)) { + NS_WARNING("Failed to register worker!"); + return NS_ERROR_FAILURE; + } + + created = true; + newWorkerPrivate.forget(&workerPrivate); + } + + MOZ_ASSERT(workerPrivate->IsSharedWorker()); + + nsRefPtr sharedWorker = + new SharedWorker(aWindow, workerPrivate); + + if (!workerPrivate->RegisterSharedWorker(aCx, sharedWorker)) { + NS_WARNING("Failed to dispatch to worker!"); + return NS_ERROR_FAILURE; + } + + // This is normally handled in RegisterWorker, but that wasn't called if the + // worker already existed. + if (!created) { + nsTArray* windowArray; + if (!mWindowMap.Get(aWindow, &windowArray)) { + windowArray = new nsTArray(1); + mWindowMap.Put(aWindow, windowArray); + } + + if (!windowArray->Contains(workerPrivate)) { + windowArray->AppendElement(workerPrivate); + } + } + + sharedWorker.forget(aSharedWorker); + return NS_OK; +} + void RuntimeService::NoteIdleThread(nsIThread* aThread) { diff --git a/dom/workers/RuntimeService.h b/dom/workers/RuntimeService.h index 5f2f1dd33a94..dbbcc7cc80b5 100644 --- a/dom/workers/RuntimeService.h +++ b/dom/workers/RuntimeService.h @@ -14,9 +14,11 @@ #include "mozilla/Attributes.h" #include "mozilla/Mutex.h" #include "mozilla/TimeStamp.h" +#include "mozilla/dom/BindingDeclarations.h" #include "nsAutoPtr.h" #include "nsClassHashtable.h" #include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" #include "nsHashKeys.h" #include "nsString.h" #include "nsTArray.h" @@ -27,18 +29,35 @@ class nsPIDOMWindow; BEGIN_WORKERS_NAMESPACE +class SharedWorker; class WorkerPrivate; class RuntimeService MOZ_FINAL : public nsIObserver { + struct SharedWorkerInfo + { + WorkerPrivate* mWorkerPrivate; + nsCString mScriptSpec; + nsString mName; + + SharedWorkerInfo(WorkerPrivate* aWorkerPrivate, + const nsACString& aScriptSpec, + const nsAString& aName) + : mWorkerPrivate(aWorkerPrivate), mScriptSpec(aScriptSpec), mName(aName) + { } + }; + struct WorkerDomainInfo { nsCString mDomain; nsTArray mActiveWorkers; nsTArray mQueuedWorkers; + nsClassHashtable mSharedWorkerInfos; uint32_t mChildWorkerCount; - WorkerDomainInfo() : mActiveWorkers(1), mChildWorkerCount(0) { } + WorkerDomainInfo() + : mActiveWorkers(1), mChildWorkerCount(0) + { } uint32_t ActiveWorkerCount() const @@ -53,6 +72,16 @@ class RuntimeService MOZ_FINAL : public nsIObserver mozilla::TimeStamp mExpirationTime; }; + struct MatchSharedWorkerInfo + { + WorkerPrivate* mWorkerPrivate; + SharedWorkerInfo* mSharedWorkerInfo; + + MatchSharedWorkerInfo(WorkerPrivate* aWorkerPrivate) + : mWorkerPrivate(aWorkerPrivate), mSharedWorkerInfo(nullptr) + { } + }; + mozilla::Mutex mMutex; // Protected by mMutex. @@ -62,7 +91,8 @@ class RuntimeService MOZ_FINAL : public nsIObserver nsTArray mIdleThreadArray; // *Not* protected by mMutex. - nsClassHashtable, nsTArray > mWindowMap; + nsClassHashtable, + nsTArray > mWindowMap; // Only used on the main thread. nsCOMPtr mIdleThreadTimer; @@ -106,13 +136,18 @@ public: UnregisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate); void - CancelWorkersForWindow(JSContext* aCx, nsPIDOMWindow* aWindow); + CancelWorkersForWindow(nsPIDOMWindow* aWindow); void - SuspendWorkersForWindow(JSContext* aCx, nsPIDOMWindow* aWindow); + SuspendWorkersForWindow(nsPIDOMWindow* aWindow); void - ResumeWorkersForWindow(nsIScriptContext* aCx, nsPIDOMWindow* aWindow); + ResumeWorkersForWindow(nsPIDOMWindow* aWindow); + + nsresult + CreateSharedWorker(JSContext* aCx, nsPIDOMWindow* aWindow, + const nsAString& aScriptURL, const nsAString& aName, + SharedWorker** aSharedWorker); const nsACString& GetDetectorName() const @@ -219,6 +254,16 @@ private: WorkerDomainInfo* aData, void* aUserArg); + static PLDHashOperator + RemoveSharedWorkerFromWindowMap(nsPIDOMWindow* aKey, + nsAutoPtr >& aData, + void* aUserArg); + + static PLDHashOperator + FindSharedWorkerInfo(const nsACString& aKey, + SharedWorkerInfo* aData, + void* aUserArg); + void GetWorkersForWindow(nsPIDOMWindow* aWindow, nsTArray& aWorkers); diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp index 811f9f5296f9..4b799d8c149d 100644 --- a/dom/workers/ScriptLoader.cpp +++ b/dom/workers/ScriptLoader.cpp @@ -38,10 +38,12 @@ #define MAX_CONCURRENT_SCRIPTS 1000 -BEGIN_WORKERS_NAMESPACE +USING_WORKERS_NAMESPACE + +using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult; + +namespace { -namespace scriptloader { -static nsresult ChannelFromScriptURL(nsIPrincipal* principal, nsIURI* baseURI, @@ -49,19 +51,85 @@ ChannelFromScriptURL(nsIPrincipal* principal, nsILoadGroup* loadGroup, nsIIOService* ios, nsIScriptSecurityManager* secMan, - const nsString& aScriptURL, + const nsAString& aScriptURL, bool aIsWorkerScript, - nsIChannel** aChannel); + nsIChannel** aChannel) +{ + AssertIsOnMainThread(); -} // namespace scriptloader + nsresult rv; + nsCOMPtr uri; + rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), + aScriptURL, parentDoc, + baseURI); + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } -END_WORKERS_NAMESPACE + // If we're part of a document then check the content load policy. + if (parentDoc) { + int16_t shouldLoad = nsIContentPolicy::ACCEPT; + rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT, uri, + principal, parentDoc, + NS_LITERAL_CSTRING("text/javascript"), + nullptr, &shouldLoad, + nsContentUtils::GetContentPolicy(), + secMan); + if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { + if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) { + return rv = NS_ERROR_CONTENT_BLOCKED; + } + return rv = NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; + } + } -USING_WORKERS_NAMESPACE + // 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) { + nsCString scheme; + rv = uri->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); -using mozilla::dom::Throw; + // We pass true as the 3rd argument to checkMayLoad here. + // This allows workers in sandboxed documents to load data URLs + // (and other URLs that inherit their principal from their + // creator.) + rv = principal->CheckMayLoad(uri, false, true); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); + } + else { + rv = secMan->CheckLoadURIWithPrincipal(principal, uri, 0); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); + } -namespace { + // Get Content Security Policy from parent document to pass into channel. + nsCOMPtr csp; + rv = principal->GetCsp(getter_AddRefs(csp)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr channelPolicy; + if (csp) { + channelPolicy = do_CreateInstance(NSCHANNELPOLICY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = channelPolicy->SetContentSecurityPolicy(csp); + NS_ENSURE_SUCCESS(rv, rv); + + rv = channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SCRIPT); + NS_ENSURE_SUCCESS(rv, rv); + } + + uint32_t flags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI; + + nsCOMPtr channel; + rv = NS_NewChannel(getter_AddRefs(channel), uri, ios, loadGroup, nullptr, + flags, channelPolicy); + NS_ENSURE_SUCCESS(rv, rv); + + channel.forget(aChannel); + return rv; +} class ScriptLoaderRunnable; @@ -275,7 +343,7 @@ public: nsCOMPtr channel; if (mIsWorkerScript) { // May be null. - channel = mWorkerPrivate->GetChannel(); + channel = mWorkerPrivate->ForgetWorkerChannel(); } // All of these can potentially be null, but that should be ok. We'll either @@ -295,9 +363,8 @@ public: nsresult& rv = loadInfo.mLoadResult; if (!channel) { - rv = scriptloader::ChannelFromScriptURL(principal, baseURI, parentDoc, - loadGroup, ios, secMan, - loadInfo.mURL, mIsWorkerScript, + rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios, + secMan, loadInfo.mURL, mIsWorkerScript, getter_AddRefs(channel)); if (NS_FAILED(rv)) { return rv; @@ -481,6 +548,12 @@ public: void ExecuteFinishedScripts() { + AssertIsOnMainThread(); + + if (mIsWorkerScript) { + mWorkerPrivate->WorkerScriptLoaded(); + } + uint32_t firstIndex = UINT32_MAX; uint32_t lastIndex = UINT32_MAX; @@ -544,14 +617,14 @@ class ChannelGetterRunnable MOZ_FINAL : public nsRunnable { WorkerPrivate* mParentWorker; uint32_t mSyncQueueKey; - const nsString& mScriptURL; + const nsAString& mScriptURL; nsIChannel** mChannel; nsresult mResult; public: ChannelGetterRunnable(WorkerPrivate* aParentWorker, uint32_t aSyncQueueKey, - const nsString& aScriptURL, + const nsAString& aScriptURL, nsIChannel** aChannel) : mParentWorker(aParentWorker), mSyncQueueKey(aSyncQueueKey), mScriptURL(aScriptURL), mChannel(aChannel), mResult(NS_ERROR_FAILURE) @@ -723,98 +796,11 @@ BEGIN_WORKERS_NAMESPACE namespace scriptloader { -// static -nsresult -ChannelFromScriptURL(nsIPrincipal* principal, - nsIURI* baseURI, - nsIDocument* parentDoc, - nsILoadGroup* loadGroup, - nsIIOService* ios, - nsIScriptSecurityManager* secMan, - const nsString& aScriptURL, - bool aIsWorkerScript, - nsIChannel** aChannel) -{ - nsresult rv; - nsCOMPtr uri; - rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), - aScriptURL, parentDoc, - baseURI); - if (NS_FAILED(rv)) { - return NS_ERROR_DOM_SYNTAX_ERR; - } - - // If we're part of a document then check the content load policy. - if (parentDoc) { - int16_t shouldLoad = nsIContentPolicy::ACCEPT; - rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT, uri, - principal, parentDoc, - NS_LITERAL_CSTRING("text/javascript"), - nullptr, &shouldLoad, - nsContentUtils::GetContentPolicy(), - secMan); - if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { - if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) { - return rv = NS_ERROR_CONTENT_BLOCKED; - } - return rv = NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; - } - } - - // 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) { - nsCString scheme; - rv = uri->GetScheme(scheme); - NS_ENSURE_SUCCESS(rv, rv); - - // We pass true as the 3rd argument to checkMayLoad here. - // This allows workers in sandboxed documents to load data URLs - // (and other URLs that inherit their principal from their - // creator.) - rv = principal->CheckMayLoad(uri, false, true); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); - } - else { - rv = secMan->CheckLoadURIWithPrincipal(principal, uri, 0); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); - } - - // Get Content Security Policy from parent document to pass into channel. - nsCOMPtr csp; - rv = principal->GetCsp(getter_AddRefs(csp)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr channelPolicy; - if (csp) { - channelPolicy = do_CreateInstance(NSCHANNELPOLICY_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - rv = channelPolicy->SetContentSecurityPolicy(csp); - NS_ENSURE_SUCCESS(rv, rv); - - rv = channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SCRIPT); - NS_ENSURE_SUCCESS(rv, rv); - } - - uint32_t flags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI; - - nsCOMPtr channel; - rv = NS_NewChannel(getter_AddRefs(channel), uri, ios, loadGroup, nullptr, - flags, channelPolicy); - NS_ENSURE_SUCCESS(rv, rv); - - channel.forget(aChannel); - return rv; -} - - nsresult ChannelFromScriptURLMainThread(nsIPrincipal* aPrincipal, nsIURI* aBaseURI, nsIDocument* aParentDoc, - const nsString& aScriptURL, + const nsAString& aScriptURL, nsIChannel** aChannel) { AssertIsOnMainThread(); @@ -836,7 +822,7 @@ ChannelFromScriptURLMainThread(nsIPrincipal* aPrincipal, nsresult ChannelFromScriptURLWorkerThread(JSContext* aCx, WorkerPrivate* aParent, - const nsString& aScriptURL, + const nsAString& aScriptURL, nsIChannel** aChannel) { aParent->AssertIsOnWorkerThread(); @@ -859,7 +845,7 @@ ChannelFromScriptURLWorkerThread(JSContext* aCx, return getter->GetResult(); } -void ReportLoadError(JSContext* aCx, const nsString& aURL, +void ReportLoadError(JSContext* aCx, const nsAString& aURL, nsresult aLoadResult, bool aIsMainThread) { NS_LossyConvertUTF16toASCII url(aURL); diff --git a/dom/workers/ScriptLoader.h b/dom/workers/ScriptLoader.h index 8c34b25f0727..f6155c162f04 100644 --- a/dom/workers/ScriptLoader.h +++ b/dom/workers/ScriptLoader.h @@ -22,16 +22,16 @@ nsresult ChannelFromScriptURLMainThread(nsIPrincipal* aPrincipal, nsIURI* aBaseURI, nsIDocument* aParentDoc, - const nsString& aScriptURL, + const nsAString& aScriptURL, nsIChannel** aChannel); nsresult ChannelFromScriptURLWorkerThread(JSContext* aCx, WorkerPrivate* aParent, - const nsString& aScriptURL, + const nsAString& aScriptURL, nsIChannel** aChannel); -void ReportLoadError(JSContext* aCx, const nsString& aURL, +void ReportLoadError(JSContext* aCx, const nsAString& aURL, nsresult aLoadResult, bool aIsMainThread); bool LoadWorkerScript(JSContext* aCx); diff --git a/dom/workers/SharedWorker.cpp b/dom/workers/SharedWorker.cpp new file mode 100644 index 000000000000..acf015fb0de7 --- /dev/null +++ b/dom/workers/SharedWorker.cpp @@ -0,0 +1,235 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SharedWorker.h" + +#include "nsPIDOMWindow.h" + +#include "mozilla/Preferences.h" +#include "mozilla/dom/SharedWorkerBinding.h" +#include "nsContentUtils.h" +#include "nsDOMEvent.h" +#include "nsEventDispatcher.h" +#include "nsIClassInfoImpl.h" + +#include "MessagePort.h" +#include "RuntimeService.h" +#include "Worker.h" +#include "WorkerPrivate.h" + +using mozilla::dom::Optional; +using mozilla::dom::Sequence; + +USING_WORKERS_NAMESPACE + +namespace { + +const char kSharedWorkersEnabledPref[] = "dom.workers.sharedWorkers.enabled"; + +} // anonymous namespace + +SharedWorker::SharedWorker(nsPIDOMWindow* aWindow, + WorkerPrivate* aWorkerPrivate) +: nsDOMEventTargetHelper(aWindow), mWorkerPrivate(aWorkerPrivate), + mSuspended(false) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aWorkerPrivate); + + mSerial = aWorkerPrivate->NextMessagePortSerial(); + + mMessagePort = new MessagePort(aWindow, this, mSerial); +} + +SharedWorker::~SharedWorker() +{ + AssertIsOnMainThread(); + MOZ_ASSERT(!mWorkerPrivate); +} + +//static +bool +SharedWorker::PrefEnabled() +{ + AssertIsOnMainThread(); + + return mozilla::Preferences::GetBool(kSharedWorkersEnabledPref, false); +} + +// static +already_AddRefed +SharedWorker::Constructor(const GlobalObject& aGlobal, JSContext* aCx, + const nsAString& aScriptURL, + const mozilla::dom::Optional& aName, + ErrorResult& aRv) +{ + AssertIsOnMainThread(); + + nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); + MOZ_ASSERT(window); + + RuntimeService* rts = RuntimeService::GetOrCreateService(); + if (!rts) { + aRv = NS_ERROR_NOT_AVAILABLE; + return nullptr; + } + + nsString name; + if (aName.WasPassed()) { + name = aName.Value(); + } + + nsRefPtr sharedWorker; + nsresult rv = rts->CreateSharedWorker(aCx, window, aScriptURL, name, + getter_AddRefs(sharedWorker)); + if (NS_FAILED(rv)) { + aRv = rv; + return nullptr; + } + + return sharedWorker.forget(); +} + +already_AddRefed +SharedWorker::Port() +{ + AssertIsOnMainThread(); + + nsRefPtr messagePort = mMessagePort; + return messagePort.forget(); +} + +void +SharedWorker::Suspend() +{ + AssertIsOnMainThread(); + MOZ_ASSERT(!IsSuspended()); + + mSuspended = true; +} + +void +SharedWorker::Resume() +{ + AssertIsOnMainThread(); + MOZ_ASSERT(IsSuspended()); + + mSuspended = false; + + if (!mSuspendedEvents.IsEmpty()) { + nsTArray> events; + mSuspendedEvents.SwapElements(events); + + for (uint32_t index = 0; index < events.Length(); index++) { + nsCOMPtr& event = events[index]; + MOZ_ASSERT(event); + + nsCOMPtr target; + if (NS_SUCCEEDED(event->GetTarget(getter_AddRefs(target)))) { + bool ignored; + if (NS_FAILED(target->DispatchEvent(event, &ignored))) { + NS_WARNING("Failed to dispatch event!"); + } + } else { + NS_WARNING("Failed to get target!"); + } + } + } +} + +void +SharedWorker::QueueEvent(nsIDOMEvent* aEvent) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aEvent); + MOZ_ASSERT(IsSuspended()); + + mSuspendedEvents.AppendElement(aEvent); +} + +void +SharedWorker::Close() +{ + AssertIsOnMainThread(); + + if (mMessagePort) { + mMessagePort->Close(); + } + + if (mWorkerPrivate) { + AutoSafeJSContext cx; + NoteDeadWorker(cx); + } +} + +void +SharedWorker::PostMessage(JSContext* aCx, JS::HandleValue aMessage, + const Optional>& aTransferable, + ErrorResult& aRv) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(mWorkerPrivate); + MOZ_ASSERT(mMessagePort); + + mWorkerPrivate->PostMessageToMessagePort(aCx, mMessagePort->Serial(), + aMessage, aTransferable, aRv); +} + +void +SharedWorker::NoteDeadWorker(JSContext* aCx) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(mWorkerPrivate); + + mWorkerPrivate->UnregisterSharedWorker(aCx, this); + mWorkerPrivate = nullptr; +} + +NS_IMPL_ADDREF_INHERITED(SharedWorker, nsDOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(SharedWorker, nsDOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SharedWorker) +NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) + +NS_IMPL_CYCLE_COLLECTION_CLASS(SharedWorker) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SharedWorker, + nsDOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessagePort) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedEvents) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SharedWorker, + nsDOMEventTargetHelper) + tmp->Close(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessagePort) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedEvents) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +JSObject* +SharedWorker::WrapObject(JSContext* aCx, JS::HandleObject aScope) +{ + AssertIsOnMainThread(); + + return SharedWorkerBinding::Wrap(aCx, aScope, this); +} + +nsresult +SharedWorker::PreHandleEvent(nsEventChainPreVisitor& aVisitor) +{ + AssertIsOnMainThread(); + + nsIDOMEvent*& event = aVisitor.mDOMEvent; + + if (IsSuspended() && event) { + QueueEvent(event); + + aVisitor.mCanHandle = false; + aVisitor.mParentTarget = nullptr; + return NS_OK; + } + + return nsDOMEventTargetHelper::PreHandleEvent(aVisitor); +} diff --git a/dom/workers/SharedWorker.h b/dom/workers/SharedWorker.h new file mode 100644 index 000000000000..1c261542636f --- /dev/null +++ b/dom/workers/SharedWorker.h @@ -0,0 +1,104 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_sharedworker_h__ +#define mozilla_dom_workers_sharedworker_h__ + +#include "Workers.h" + +#include "mozilla/dom/BindingDeclarations.h" +#include "nsDOMEventTargetHelper.h" + +class nsIDOMEvent; +class nsPIDOMWindow; + +BEGIN_WORKERS_NAMESPACE + +class MessagePort; +class RuntimeService; +class WorkerPrivate; + +class SharedWorker MOZ_FINAL : public nsDOMEventTargetHelper +{ + friend class MessagePort; + friend class RuntimeService; + + typedef mozilla::ErrorResult ErrorResult; + typedef mozilla::dom::GlobalObject GlobalObject; + + WorkerPrivate* mWorkerPrivate; + nsRefPtr mMessagePort; + nsTArray> mSuspendedEvents; + uint64_t mSerial; + bool mSuspended; + +public: + static bool + PrefEnabled(); + + static already_AddRefed + Constructor(const GlobalObject& aGlobal, JSContext* aCx, + const nsAString& aScriptURL, const Optional& aName, + ErrorResult& aRv); + + already_AddRefed + Port(); + + uint64_t + Serial() const + { + return mSerial; + } + + bool + IsSuspended() const + { + return mSuspended; + } + + void + Suspend(); + + void + Resume(); + + void + QueueEvent(nsIDOMEvent* aEvent); + + void + Close(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SharedWorker, nsDOMEventTargetHelper) + + IMPL_EVENT_HANDLER(error) + + virtual JSObject* + WrapObject(JSContext* aCx, JS::HandleObject aScope) MOZ_OVERRIDE; + + virtual nsresult + PreHandleEvent(nsEventChainPreVisitor& aVisitor) MOZ_OVERRIDE; + +private: + // This class can only be created from the RuntimeService. + SharedWorker(nsPIDOMWindow* aWindow, WorkerPrivate* aWorkerPrivate); + + // This class is reference-counted and will be destroyed from Release(). + ~SharedWorker(); + + // Only called by MessagePort. + void + PostMessage(JSContext* aCx, JS::HandleValue aMessage, + const Optional>& aTransferable, + ErrorResult& aRv); + + // Only called by RuntimeService. + void + NoteDeadWorker(JSContext* aCx); +}; + +END_WORKERS_NAMESPACE + +#endif // mozilla_dom_workers_sharedworker_h__ diff --git a/dom/workers/Worker.cpp b/dom/workers/Worker.cpp index a3c4407799e6..1edd5643feba 100644 --- a/dom/workers/Worker.cpp +++ b/dom/workers/Worker.cpp @@ -8,6 +8,7 @@ #include "mozilla/dom/DOMJSClass.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/EventHandlerBinding.h" +#include "nsJSUtils.h" #include "jsapi.h" #include "EventTarget.h" @@ -91,69 +92,43 @@ public: static WorkerPrivate* GetInstancePrivate(JSContext* aCx, JSObject* aObj, const char* aFunctionName); + static JSObject* + Create(JSContext* aCx, WorkerPrivate* aParentObj, const nsAString& aScriptURL, + bool aIsChromeWorker, bool aIsSharedWorker, + const nsAString& aSharedWorkerName); + protected: static bool - ConstructInternal(JSContext* aCx, unsigned aArgc, jsval* aVp, - bool aIsChromeWorker, const JSClass* aClass) + ConstructInternal(JSContext* aCx, JS::CallArgs aArgs, bool aIsChromeWorker) { - if (!aArgc) { + if (!aArgs.length()) { JS_ReportError(aCx, "Constructor requires at least one argument!"); return false; } - JS::Rooted scriptURL(aCx, JS_ValueToString(aCx, JS_ARGV(aCx, aVp)[0])); - if (!scriptURL) { + nsDependentJSString scriptURL; + if (!scriptURL.init(aCx, aArgs[0])) { return false; } - jsval priv = js::GetFunctionNativeReserved(JSVAL_TO_OBJECT(JS_CALLEE(aCx, aVp)), - CONSTRUCTOR_SLOT_PARENT); + JS::Rooted priv(aCx, + js::GetFunctionNativeReserved(&aArgs.callee(), CONSTRUCTOR_SLOT_PARENT)); - RuntimeService* runtimeService; WorkerPrivate* parent; - - if (JSVAL_IS_VOID(priv)) { - runtimeService = RuntimeService::GetOrCreateService(); - if (!runtimeService) { - JS_ReportError(aCx, "Failed to create runtime service!"); - return false; - } + if (priv.isUndefined()) { parent = NULL; - } - else { - runtimeService = RuntimeService::GetService(); - parent = static_cast(JSVAL_TO_PRIVATE(priv)); + } else { + parent = static_cast(priv.get().toPrivate()); parent->AssertIsOnWorkerThread(); } - JS::Rooted obj(aCx, JS_NewObject(aCx, aClass, nullptr, nullptr)); + JS::Rooted obj(aCx, + Create(aCx, parent, scriptURL, aIsChromeWorker, false, EmptyString())); if (!obj) { return false; } - // Ensure that the DOM_OBJECT_SLOT always has a PrivateValue set, as this - // will be accessed in the Trace() method if WorkerPrivate::Create() - // triggers a GC. - js::SetReservedSlot(obj, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr)); - - nsRefPtr worker = - WorkerPrivate::Create(aCx, obj, parent, scriptURL, aIsChromeWorker); - if (!worker) { - return false; - } - - // Worker now owned by the JS object. - NS_ADDREF(worker.get()); - js::SetReservedSlot(obj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL(worker)); - - if (!runtimeService->RegisterWorker(aCx, worker)) { - return false; - } - - // Worker now also owned by its thread. - NS_ADDREF(worker.get()); - - JS_SET_RVAL(aCx, aVp, OBJECT_TO_JSVAL(obj)); + aArgs.rval().setObject(*obj); return true; } @@ -281,9 +256,10 @@ private: } static bool - Construct(JSContext* aCx, unsigned aArgc, jsval* aVp) + Construct(JSContext* aCx, unsigned aArgc, JS::Value* aVp) { - return ConstructInternal(aCx, aArgc, aVp, false, Class()); + JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp); + return ConstructInternal(aCx, args, false); } static void @@ -489,9 +465,10 @@ private: } static bool - Construct(JSContext* aCx, unsigned aArgc, jsval* aVp) + Construct(JSContext* aCx, unsigned aArgc, JS::Value* aVp) { - return ConstructInternal(aCx, aArgc, aVp, true, Class()); + JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp); + return ConstructInternal(aCx, args, true); } static void @@ -574,6 +551,63 @@ Worker::GetInstancePrivate(JSContext* aCx, JSObject* aObj, return NULL; } +JSObject* +Worker::Create(JSContext* aCx, WorkerPrivate* aParent, + const nsAString& aScriptURL, bool aIsChromeWorker, + bool aIsSharedWorker, const nsAString& aSharedWorkerName) +{ + MOZ_ASSERT_IF(aIsSharedWorker, !aSharedWorkerName.IsVoid()); + MOZ_ASSERT_IF(!aIsSharedWorker, aSharedWorkerName.IsEmpty()); + + RuntimeService* runtimeService; + if (aParent) { + runtimeService = RuntimeService::GetService(); + NS_ASSERTION(runtimeService, "Null runtime service!"); + } + else { + runtimeService = RuntimeService::GetOrCreateService(); + if (!runtimeService) { + JS_ReportError(aCx, "Failed to create runtime service!"); + return nullptr; + } + } + + const JSClass* classPtr = aIsChromeWorker ? ChromeWorker::Class() : Class(); + + JS::Rooted obj(aCx, + JS_NewObject(aCx, const_cast(classPtr), nullptr, nullptr)); + if (!obj) { + return nullptr; + } + + // Ensure that the DOM_OBJECT_SLOT always has a PrivateValue set, as this will + // be accessed in the Trace() method if WorkerPrivate::Create() triggers a GC. + js::SetReservedSlot(obj, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr)); + + nsRefPtr worker = + WorkerPrivate::Create(aCx, obj, aParent, aScriptURL, aIsChromeWorker, + aIsSharedWorker, aSharedWorkerName); + if (!worker) { + // It'd be better if we could avoid allocating the JSObject until after we + // make sure we have a WorkerPrivate, but failing that we should at least + // make sure that the DOM_OBJECT_SLOT always has a PrivateValue. + return nullptr; + } + + // Worker now owned by the JS object. + NS_ADDREF(worker.get()); + js::SetReservedSlot(obj, DOM_OBJECT_SLOT, JS::PrivateValue(worker)); + + if (!runtimeService->RegisterWorker(aCx, worker)) { + return nullptr; + } + + // Worker now also owned by its thread. + NS_ADDREF(worker.get()); + + return obj; +} + } // anonymous namespace BEGIN_WORKERS_NAMESPACE diff --git a/dom/workers/Worker.h b/dom/workers/Worker.h index bc6b38a0ea0a..84c9a81ac5a7 100644 --- a/dom/workers/Worker.h +++ b/dom/workers/Worker.h @@ -8,6 +8,7 @@ #include "Workers.h" + BEGIN_WORKERS_NAMESPACE namespace worker { @@ -31,4 +32,4 @@ ClassIsWorker(const JSClass* aClass); END_WORKERS_NAMESPACE -#endif /* mozilla_dom_workers_worker_h__ */ +#endif /* mozilla_dom_workers_worker_h__ */ \ No newline at end of file diff --git a/dom/workers/WorkerMessagePort.cpp b/dom/workers/WorkerMessagePort.cpp new file mode 100644 index 000000000000..37473eff348b --- /dev/null +++ b/dom/workers/WorkerMessagePort.cpp @@ -0,0 +1,225 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WorkerMessagePort.h" + +#include "DOMBindingInlines.h" +#include "Events.h" +#include "WorkerPrivate.h" + +using mozilla::dom::Optional; +using mozilla::dom::Sequence; + +USING_WORKERS_NAMESPACE +using namespace mozilla::dom::workers::events; + +namespace { + +bool +DispatchMessageEvent(JSContext* aCx, JS::HandleObject aMessagePort, + JSAutoStructuredCloneBuffer& aBuffer, + nsTArray>& aClonedObjects) +{ + MOZ_ASSERT(aMessagePort); + + JSAutoStructuredCloneBuffer buffer; + aBuffer.swap(buffer); + + nsTArray> clonedObjects; + aClonedObjects.SwapElements(clonedObjects); + + JS::Rooted event(aCx, + CreateMessageEvent(aCx, buffer, clonedObjects, false)); + if (!event) { + return false; + } + + bool dummy; + return DispatchEventToTarget(aCx, aMessagePort, event, &dummy); +} + + +class QueuedMessageEventRunnable : public WorkerRunnable +{ + JSAutoStructuredCloneBuffer mBuffer; + nsTArray> mClonedObjects; + nsRefPtr mMessagePort; + JSObject* mMessagePortObject; + +public: + QueuedMessageEventRunnable(WorkerPrivate* aWorkerPrivate, + JSAutoStructuredCloneBuffer& aBuffer, + nsTArray>& aClonedObjects) + : WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount, + RunWhenClearing), + mMessagePortObject(nullptr) + { + aWorkerPrivate->AssertIsOnWorkerThread(); + mBuffer.swap(aBuffer); + mClonedObjects.SwapElements(aClonedObjects); + } + + bool + Hold(JSContext* aCx, WorkerMessagePort* aMessagePort) + { + mWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(aMessagePort); + MOZ_ASSERT(aMessagePort->GetJSObject()); + MOZ_ASSERT(!mMessagePortObject); + + if (!JS_AddNamedObjectRoot(aCx, &mMessagePortObject, + "WorkerMessagePort::MessageEventRunnable::" + "mMessagePortObject")) { + return false; + } + + mMessagePortObject = aMessagePort->GetJSObject(); + mMessagePort = aMessagePort; + return true; + } + + bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + { + aWorkerPrivate->AssertIsOnWorkerThread(); + return true; + } + + void + PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) + { + aWorkerPrivate->AssertIsOnWorkerThread(); + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + { + JS::Rooted messagePortObject(aCx, mMessagePortObject); + mMessagePortObject = nullptr; + + JS_RemoveObjectRoot(aCx, &mMessagePortObject); + + nsRefPtr messagePort; + mMessagePort.swap(messagePort); + + return DispatchMessageEvent(aCx, messagePortObject, mBuffer, + mClonedObjects); + } +}; + +} // anonymous namespace + +void +WorkerMessagePort::_trace(JSTracer* aTrc) +{ + EventTarget::_trace(aTrc); +} + +void +WorkerMessagePort::_finalize(JSFreeOp* aFop) +{ + EventTarget::_finalize(aFop); +} + +void +WorkerMessagePort::PostMessage( + JSContext* /* aCx */, JS::HandleValue aMessage, + const Optional>& aTransferable, + ErrorResult& aRv) +{ + if (mClosed) { + aRv = NS_ERROR_DOM_INVALID_STATE_ERR; + return; + } + + JSContext* cx = GetJSContext(); + + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx); + MOZ_ASSERT(workerPrivate); + + workerPrivate->PostMessageToParentMessagePort(cx, Serial(), aMessage, + aTransferable, aRv); +} + +void +WorkerMessagePort::Start() +{ + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(GetJSContext()); + MOZ_ASSERT(workerPrivate); + + if (mClosed) { + NS_WARNING("Called start() after calling close()!"); + return; + } + + if (mStarted) { + return; + } + + mStarted = true; + + if (!mQueuedMessages.IsEmpty()) { + for (uint32_t index = 0; index < mQueuedMessages.Length(); index++) { + MessageInfo& info = mQueuedMessages[index]; + + nsRefPtr runnable = + new QueuedMessageEventRunnable(workerPrivate, info.mBuffer, + info.mClonedObjects); + + JSContext* cx = GetJSContext(); + + if (!runnable->Hold(cx, this) || + !runnable->Dispatch(cx)) { + NS_WARNING("Failed to dispatch queued event!"); + break; + } + } + mQueuedMessages.Clear(); + } +} + +bool +WorkerMessagePort::MaybeDispatchEvent( + JSContext* aCx, + JSAutoStructuredCloneBuffer& aBuffer, + nsTArray>& aClonedObjects) +{ + if (mClosed) { + NS_WARNING("Not going to ever run this event!"); + aBuffer.clear(); + aClonedObjects.Clear(); + return true; + } + + if (!mStarted) { + // Queue the message for later. + MessageInfo* info = mQueuedMessages.AppendElement(); + info->mBuffer.swap(aBuffer); + info->mClonedObjects.SwapElements(aClonedObjects); + return true; + } + + // Go ahead and dispatch the event. + JS::Rooted target(aCx, GetJSObject()); + return DispatchMessageEvent(aCx, target, aBuffer, aClonedObjects); +} + +void +WorkerMessagePort::CloseInternal() +{ + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(GetJSContext()); + MOZ_ASSERT(workerPrivate); + MOZ_ASSERT(!IsClosed()); + MOZ_ASSERT_IF(mStarted, mQueuedMessages.IsEmpty()); + + mClosed = true; + + workerPrivate->DisconnectMessagePort(Serial()); + + if (!mStarted) { + mQueuedMessages.Clear(); + } +} diff --git a/dom/workers/WorkerMessagePort.h b/dom/workers/WorkerMessagePort.h new file mode 100644 index 000000000000..5d29def45260 --- /dev/null +++ b/dom/workers/WorkerMessagePort.h @@ -0,0 +1,103 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_workermessageport_h__ +#define mozilla_dom_workers_workermessageport_h__ + +#include "js/StructuredClone.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/workers/bindings/EventTarget.h" + +BEGIN_WORKERS_NAMESPACE + +class WorkerPrivate; + +class WorkerMessagePort : public EventTarget +{ + friend class WorkerPrivate; + + typedef mozilla::ErrorResult ErrorResult; + + struct MessageInfo + { + JSAutoStructuredCloneBuffer mBuffer; + nsTArray> mClonedObjects; + }; + + nsTArray mQueuedMessages; + uint64_t mSerial; + bool mStarted; + bool mClosed; + +public: + virtual void + _trace(JSTracer* aTrc) MOZ_OVERRIDE; + + virtual void + _finalize(JSFreeOp* aFop) MOZ_OVERRIDE; + + void + PostMessage(JSContext* aCx, JS::HandleValue aMessage, + const Optional>& aTransferable, + ErrorResult& aRv); + + void + Start(); + + void + Close() + { + if (!IsClosed()) { + CloseInternal(); + } + } + + already_AddRefed + GetOnmessage(ErrorResult& aRv) + { + return GetEventListener(NS_LITERAL_STRING("message"), aRv); + } + + void + SetOnmessage(EventHandlerNonNull* aListener, ErrorResult& aRv) + { + SetEventListener(NS_LITERAL_STRING("message"), aListener, aRv); + if (!aRv.Failed()) { + Start(); + } + } + + uint64_t + Serial() const + { + return mSerial; + } + + bool + MaybeDispatchEvent(JSContext* aCx, JSAutoStructuredCloneBuffer& aBuffer, + nsTArray>& aClonedObjects); + + bool + IsClosed() const + { + return mClosed; + } + +private: + // Only created by WorkerPrivate. + WorkerMessagePort(JSContext* aCx, uint64_t aSerial) + : EventTarget(aCx), mSerial(aSerial), mStarted(false), mClosed(false) + { } + + virtual ~WorkerMessagePort() + { } + + void + CloseInternal(); +}; + +END_WORKERS_NAMESPACE + +#endif // mozilla_dom_workers_workermessageport_h__ diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index 1d350c60f1dd..43e0053aed65 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -11,6 +11,7 @@ #include "nsIContentSecurityPolicy.h" #include "nsIConsoleService.h" #include "nsIDOMDOMException.h" +#include "nsIDOMEvent.h" #include "nsIDOMFile.h" #include "nsIDocument.h" #include "nsIDocShell.h" @@ -25,10 +26,9 @@ #include "nsIURI.h" #include "nsIURL.h" #include "nsIXPConnect.h" -#include "nsPrintfCString.h" -#include "nsHostObjectProtocolHandler.h" #include +#include "GeneratedEvents.h" #include "jsfriendapi.h" #include "js/OldDebugAPI.h" #include "js/MemoryMetrics.h" @@ -36,16 +36,21 @@ #include "mozilla/ContentEvents.h" #include "mozilla/Likely.h" #include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/ErrorEvent.h" #include "mozilla/dom/ImageData.h" #include "mozilla/dom/ImageDataBinding.h" +#include "mozilla/Util.h" #include "nsAlgorithm.h" #include "nsContentUtils.h" #include "nsCxPusher.h" #include "nsError.h" +#include "nsDOMMessageEvent.h" #include "nsDOMJSUtils.h" +#include "nsHostObjectProtocolHandler.h" #include "nsJSEnvironment.h" #include "nsJSUtils.h" #include "nsNetUtil.h" +#include "nsPrintfCString.h" #include "nsProxyRelease.h" #include "nsSandboxFlags.h" #include "nsThreadUtils.h" @@ -55,14 +60,18 @@ #include #endif +#include "DOMBindingInlines.h" #include "Events.h" #include "mozilla/dom/Exceptions.h" #include "File.h" +#include "MessagePort.h" #include "Principal.h" #include "RuntimeService.h" #include "ScriptLoader.h" +#include "SharedWorker.h" #include "Worker.h" #include "WorkerFeature.h" +#include "WorkerMessagePort.h" #include "WorkerScope.h" // GC will run once every thirty seconds during normal execution. @@ -131,6 +140,85 @@ SwapToISupportsArray(SmartPtr& aSrc, dest->swap(rawSupports); } +struct WindowAction +{ + nsPIDOMWindow* mWindow; + JSContext* mJSContext; + bool mDefaultAction; + + WindowAction(nsPIDOMWindow* aWindow, JSContext* aJSContext) + : mWindow(aWindow), mJSContext(aJSContext), mDefaultAction(true) + { + MOZ_ASSERT(aJSContext); + } + + WindowAction(nsPIDOMWindow* aWindow) + : mWindow(aWindow), mJSContext(nullptr), mDefaultAction(true) + { } + + bool + operator==(const WindowAction& aOther) const + { + return mWindow == aOther.mWindow; + } +}; + +void +LogErrorToConsole(const nsAString& aMessage, + const nsAString& aFilename, + const nsAString& aLine, + uint32_t aLineNumber, + uint32_t aColumnNumber, + uint32_t aFlags, + uint64_t aInnerWindowId) +{ + AssertIsOnMainThread(); + + nsCOMPtr scriptError = + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + NS_WARN_IF_FALSE(scriptError, "Failed to create script error!"); + + if (scriptError) { + if (NS_FAILED(scriptError->InitWithWindowID(aMessage, aFilename, aLine, + aLineNumber, aColumnNumber, + aFlags, "Web Worker", + aInnerWindowId))) { + NS_WARNING("Failed to init script error!"); + scriptError = nullptr; + } + } + + nsCOMPtr consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + NS_WARN_IF_FALSE(consoleService, "Failed to get console service!"); + + if (consoleService) { + if (scriptError) { + if (NS_SUCCEEDED(consoleService->LogMessage(scriptError))) { + return; + } + NS_WARNING("LogMessage failed!"); + } else if (NS_SUCCEEDED(consoleService->LogStringMessage( + aMessage.BeginReading()))) { + return; + } + NS_WARNING("LogStringMessage failed!"); + } + + NS_ConvertUTF16toUTF8 msg(aMessage); + NS_ConvertUTF16toUTF8 filename(aFilename); + + static const char kErrorString[] = "JS error in Web Worker: %s [%s:%u]"; + +#ifdef ANDROID + __android_log_print(ANDROID_LOG_INFO, "Gecko", kErrorString, msg.get(), + filename.get(), aLineNumber); +#endif + + fprintf(stderr, kErrorString, msg.get(), filename.get(), aLineNumber); + fflush(stderr); +} + struct WorkerStructuredCloneCallbacks { static JSObject* @@ -719,7 +807,7 @@ public: bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { - JS::Rooted global(aCx, CreateDedicatedWorkerGlobalScope(aCx)); + JS::Rooted global(aCx, CreateGlobalScope(aCx)); if (!global) { NS_WARNING("Failed to make global!"); return false; @@ -779,15 +867,19 @@ class MessageEventRunnable : public WorkerRunnable { JSAutoStructuredCloneBuffer mBuffer; nsTArray > mClonedObjects; + uint64_t mMessagePortSerial; + bool mToMessagePort; public: MessageEventRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget, JSAutoStructuredCloneBuffer& aData, - nsTArray >& aClonedObjects) + nsTArray >& aClonedObjects, + bool aToMessagePort, uint64_t aMessagePortSerial) : WorkerRunnable(aWorkerPrivate, aTarget, aTarget == WorkerThread ? ModifyBusyCount : UnchangedBusyCount, - SkipWhenClearing) + SkipWhenClearing), + mMessagePortSerial(aMessagePortSerial), mToMessagePort(aToMessagePort) { mBuffer.swap(aData); mClonedObjects.SwapElements(aClonedObjects); @@ -796,6 +888,8 @@ public: bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { + MOZ_ASSERT_IF(mToMessagePort, aWorkerPrivate->IsSharedWorker()); + bool mainRuntime; JS::Rooted target(aCx); if (mTarget == ParentThread) { @@ -805,6 +899,14 @@ public: return true; } + if (mToMessagePort) { + return + aWorkerPrivate->DispatchMessageEventToMessagePort(aCx, + mMessagePortSerial, + mBuffer, + mClonedObjects); + } + mainRuntime = !aWorkerPrivate->GetParent(); target = aWorkerPrivate->GetJSObject(); @@ -820,6 +922,16 @@ public: else { NS_ASSERTION(aWorkerPrivate == GetWorkerPrivateFromContext(aCx), "Badness!"); + if (mToMessagePort) { + WorkerMessagePort* port = + aWorkerPrivate->GetMessagePort(mMessagePortSerial); + if (!port) { + // Must have been closed already. + return true; + } + return port->MaybeDispatchEvent(aCx, mBuffer, mClonedObjects); + } + mainRuntime = false; target = JS::CurrentGlobalOrNull(aCx); } @@ -958,6 +1070,7 @@ public: JS::Rooted target(aCx, aWorkerPrivate->GetJSObject()); uint64_t innerWindowId; + bool fireAtScope = true; WorkerPrivate* parent = aWorkerPrivate->GetParent(); if (parent) { @@ -971,14 +1084,21 @@ public: return true; } + if (aWorkerPrivate->IsSharedWorker()) { + aWorkerPrivate->BroadcastErrorToSharedWorkers(aCx, mMessage, mFilename, + mLine, mLineNumber, + mColumnNumber, mFlags); + return true; + } + aWorkerPrivate->AssertInnerWindowIsCorrect(); innerWindowId = aWorkerPrivate->GetInnerWindowId(); } - return ReportErrorRunnable::ReportError(aCx, parent, true, target, mMessage, - mFilename, mLine, mLineNumber, - mColumnNumber, mFlags, + return ReportErrorRunnable::ReportError(aCx, parent, fireAtScope, target, + mMessage, mFilename, mLine, + mLineNumber, mColumnNumber, mFlags, mErrorNumber, innerWindowId); } @@ -989,12 +1109,12 @@ public: static bool ReportError(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aFireAtScope, JSObject* target, const nsString& aMessage, + bool aFireAtScope, JSObject* aTarget, const nsString& aMessage, const nsString& aFilename, const nsString& aLine, uint32_t aLineNumber, uint32_t aColumnNumber, uint32_t aFlags, uint32_t aErrorNumber, uint64_t aInnerWindowId) { - JS::Rooted aTarget(aCx, target); + JS::Rooted target(aCx, aTarget); if (aWorkerPrivate) { aWorkerPrivate->AssertIsOnWorkerThread(); } @@ -1018,7 +1138,7 @@ public: // they show up in the error console. if (!JSREPORT_IS_WARNING(aFlags)) { // First fire an ErrorEvent at the worker. - if (aTarget) { + if (target) { JS::Rooted event(aCx, CreateErrorEvent(aCx, message, filename, aLineNumber, !aWorkerPrivate)); if (!event) { @@ -1026,7 +1146,7 @@ public: } bool preventDefaultCalled; - if (!DispatchEventToTarget(aCx, aTarget, event, &preventDefaultCalled)) { + if (!DispatchEventToTarget(aCx, target, event, &preventDefaultCalled)) { return false; } @@ -1037,15 +1157,15 @@ public: // Now fire an event at the global object, but don't do that if the error // code is too much recursion and this is the same script threw the error. - if (aFireAtScope && (aTarget || aErrorNumber != JSMSG_OVER_RECURSED)) { - aTarget = JS::CurrentGlobalOrNull(aCx); - NS_ASSERTION(aTarget, "This should never be null!"); + if (aFireAtScope && (target || aErrorNumber != JSMSG_OVER_RECURSED)) { + target = JS::CurrentGlobalOrNull(aCx); + NS_ASSERTION(target, "This should never be null!"); bool preventDefaultCalled; nsIScriptGlobalObject* sgo; if (aWorkerPrivate || - !(sgo = nsJSUtils::GetStaticScriptGlobal(aTarget))) { + !(sgo = nsJSUtils::GetStaticScriptGlobal(target))) { // Fire a normal ErrorEvent if we're running on a worker thread. JS::Rooted event(aCx, CreateErrorEvent(aCx, message, filename, aLineNumber, false)); @@ -1053,7 +1173,7 @@ public: return false; } - if (!DispatchEventToTarget(aCx, aTarget, event, + if (!DispatchEventToTarget(aCx, target, event, &preventDefaultCalled)) { return false; } @@ -1090,56 +1210,8 @@ public: } // Otherwise log an error to the error console. - nsCOMPtr scriptError = - do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); - NS_WARN_IF_FALSE(scriptError, "Failed to create script error!"); - - if (scriptError) { - if (NS_FAILED(scriptError->InitWithWindowID(aMessage, - aFilename, - aLine, aLineNumber, - aColumnNumber, aFlags, - "Web Worker", - aInnerWindowId))) { - NS_WARNING("Failed to init script error!"); - scriptError = nullptr; - } - } - - nsCOMPtr consoleService = - do_GetService(NS_CONSOLESERVICE_CONTRACTID); - NS_WARN_IF_FALSE(consoleService, "Failed to get console service!"); - - bool logged = false; - - if (consoleService) { - if (scriptError) { - if (NS_SUCCEEDED(consoleService->LogMessage(scriptError))) { - logged = true; - } - else { - NS_WARNING("Failed to log script error!"); - } - } - else if (NS_SUCCEEDED(consoleService->LogStringMessage(aMessage.get()))) { - logged = true; - } - else { - NS_WARNING("Failed to log script error!"); - } - } - - if (!logged || nsContentUtils::DOMWindowDumpEnabled()) { - NS_ConvertUTF16toUTF8 msg(aMessage); - NS_ConvertUTF16toUTF8 fname(aFilename); -#ifdef ANDROID - __android_log_print(ANDROID_LOG_INFO, "Gecko", "JS error in worker: %s, %s:%u", - msg.get(), fname.get(), aLineNumber); -#endif - fprintf(stderr, "JS error in worker: %s, %s:%u\n", msg.get(), fname.get(), aLineNumber); - fflush(stderr); - } - + LogErrorToConsole(aMessage, aFilename, aLine, aLineNumber, aColumnNumber, + aFlags, aInnerWindowId); return true; } }; @@ -1440,24 +1512,32 @@ class SynchronizeAndResumeRunnable : public nsRunnable { protected: WorkerPrivate* mWorkerPrivate; - nsCOMPtr mCx; + nsCOMPtr mWindow; + nsCOMPtr mScriptContext; public: SynchronizeAndResumeRunnable(WorkerPrivate* aWorkerPrivate, - nsIScriptContext* aCx) - : mWorkerPrivate(aWorkerPrivate), mCx(aCx) + nsPIDOMWindow* aWindow, + nsIScriptContext* aScriptContext) + : mWorkerPrivate(aWorkerPrivate), mWindow(aWindow), + mScriptContext(aScriptContext) { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + AssertIsOnMainThread(); + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(aWindow); } NS_IMETHOD Run() { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + AssertIsOnMainThread(); - AutoPushJSContext cx(mCx ? mCx->GetNativeContext() : + AutoPushJSContext cx(mScriptContext ? + mScriptContext->GetNativeContext() : nsContentUtils::GetSafeJSContext()); - JSAutoRequest ar(cx); - mWorkerPrivate->Resume(cx); + + if (!mWorkerPrivate->Resume(cx, mWindow)) { + JS_ReportPendingException(cx); + } return NS_OK; } @@ -1525,13 +1605,40 @@ public: } }; +class MessagePortRunnable : public WorkerRunnable +{ + uint64_t mMessagePortSerial; + bool mConnect; + +public: + MessagePortRunnable(WorkerPrivate* aWorkerPrivate, + uint64_t aMessagePortSerial, + bool aConnect) + : WorkerRunnable(aWorkerPrivate, WorkerThread, + aConnect ? ModifyBusyCount : UnchangedBusyCount, + SkipWhenClearing), + mMessagePortSerial(aMessagePortSerial), mConnect(aConnect) + { } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + { + if (mConnect) { + return aWorkerPrivate->ConnectMessagePort(aCx, mMessagePortSerial); + } + + aWorkerPrivate->DisconnectMessagePort(mMessagePortSerial); + return true; + } +}; + } /* anonymous namespace */ #ifdef DEBUG void mozilla::dom::workers::AssertIsOnMainThread() { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); } WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget, @@ -1888,43 +1995,33 @@ NS_IMPL_ISUPPORTS1(WorkerPrivate::MemoryReporter, nsIMemoryReporter) template WorkerPrivateParent::WorkerPrivateParent( - JSContext* aCx, - JS::Handle aObject, - WorkerPrivate* aParent, - JSContext* aParentJSContext, - const nsAString& aScriptURL, - bool aIsChromeWorker, - const nsACString& aDomain, - nsCOMPtr& aWindow, - nsCOMPtr& aScriptContext, - nsCOMPtr& aBaseURI, - nsCOMPtr& aPrincipal, - nsCOMPtr& aChannel, - nsCOMPtr& aCSP, - bool aEvalAllowed, - bool aReportCSPViolations) -: EventTarget(aParent ? aCx : NULL), mMutex("WorkerPrivateParent Mutex"), + JSContext* aCx, + JS::HandleObject aObject, + WorkerPrivate* aParent, + const nsAString& aScriptURL, + bool aIsChromeWorker, + bool aIsSharedWorker, + const nsAString& aSharedWorkerName, + LoadInfo& aLoadInfo) +: EventTarget(aParent ? aCx : nullptr), mMutex("WorkerPrivateParent Mutex"), mCondVar(mMutex, "WorkerPrivateParent CondVar"), mMemoryReportCondVar(mMutex, "WorkerPrivateParent Memory Report CondVar"), - mJSObject(aObject), mParent(aParent), mParentJSContext(aParentJSContext), - mScriptURL(aScriptURL), mDomain(aDomain), mBusyCount(0), + mJSObject(aObject), mParent(aParent), mScriptURL(aScriptURL), + mSharedWorkerName(aSharedWorkerName), mBusyCount(0), mMessagePortSerial(0), mParentStatus(Pending), mJSObjectRooted(false), mParentSuspended(false), - mIsChromeWorker(aIsChromeWorker), mPrincipalIsSystem(false), - mMainThreadObjectsForgotten(false), mEvalAllowed(aEvalAllowed), - mReportCSPViolations(aReportCSPViolations) + mIsChromeWorker(aIsChromeWorker), mMainThreadObjectsForgotten(false), + mIsSharedWorker(aIsSharedWorker) { MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivateParent); + MOZ_ASSERT_IF(aIsSharedWorker, !aObject && !aSharedWorkerName.IsVoid()); + MOZ_ASSERT_IF(!aIsSharedWorker, aObject && aSharedWorkerName.IsEmpty()); - if (aWindow) { - NS_ASSERTION(aWindow->IsInnerWindow(), "Should have inner window here!"); + if (aLoadInfo.mWindow) { + NS_ASSERTION(aLoadInfo.mWindow->IsInnerWindow(), + "Should have inner window here!"); } - mWindow.swap(aWindow); - mScriptContext.swap(aScriptContext); - mBaseURI.swap(aBaseURI); - mPrincipal.swap(aPrincipal); - mChannel.swap(aChannel); - mCSP.swap(aCSP); + mLoadInfo.StealFrom(aLoadInfo); if (aParent) { aParent->AssertIsOnWorkerThread(); @@ -1941,6 +2038,11 @@ WorkerPrivateParent::WorkerPrivateParent( RuntimeService::GetDefaultJSSettings(mJSSettings); } + + if (!aIsSharedWorker) { + SetIsDOMBinding(); + SetWrapper(aObject); + } } template @@ -2016,10 +2118,68 @@ WorkerPrivateParent::NotifyPrivate(JSContext* aCx, Status aStatus) template bool -WorkerPrivateParent::Suspend(JSContext* aCx) +WorkerPrivateParent::Suspend(JSContext* aCx, nsPIDOMWindow* aWindow) { AssertIsOnParentThread(); - NS_ASSERTION(!mParentSuspended, "Suspended more than once!"); + MOZ_ASSERT(aCx); + + // Shared workers are only suspended if all of their owning documents are + // suspended. + if (IsSharedWorker()) { + AssertIsOnMainThread(); + MOZ_ASSERT(mSharedWorkers.Count()); + + struct Closure + { + nsPIDOMWindow* mWindow; + bool mAllSuspended; + + Closure(nsPIDOMWindow* aWindow) + : mWindow(aWindow), mAllSuspended(true) + { + AssertIsOnMainThread(); + // aWindow may be null here. + } + + static PLDHashOperator + Suspend(const uint64_t& aKey, + SharedWorker* aSharedWorker, + void* aClosure) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aSharedWorker); + MOZ_ASSERT(aClosure); + + auto closure = static_cast(aClosure); + + if (closure->mWindow && aSharedWorker->GetOwner() == closure->mWindow) { + // Calling Suspend() may change the refcount, ensure that the worker + // outlives this call. + nsRefPtr kungFuDeathGrip = aSharedWorker; + + aSharedWorker->Suspend(); + } else { + MOZ_ASSERT_IF(aSharedWorker->GetOwner() && closure->mWindow, + !SameCOMIdentity(aSharedWorker->GetOwner(), + closure->mWindow)); + if (!aSharedWorker->IsSuspended()) { + closure->mAllSuspended = false; + } + } + return PL_DHASH_NEXT; + } + }; + + Closure closure(aWindow); + + mSharedWorkers.EnumerateRead(Closure::Suspend, &closure); + + if (!closure.mAllSuspended || mParentSuspended) { + return true; + } + } + + MOZ_ASSERT(!mParentSuspended, "Suspended more than once!"); mParentSuspended = true; @@ -2033,15 +2193,78 @@ WorkerPrivateParent::Suspend(JSContext* aCx) nsRefPtr runnable = new SuspendRunnable(ParentAsWorkerPrivate()); - return runnable->Dispatch(aCx); + if (!runnable->Dispatch(aCx)) { + return false; + } + + return true; } template -void -WorkerPrivateParent::Resume(JSContext* aCx) +bool +WorkerPrivateParent::Resume(JSContext* aCx, nsPIDOMWindow* aWindow) { AssertIsOnParentThread(); - NS_ASSERTION(mParentSuspended, "Not yet suspended!"); + MOZ_ASSERT(aCx); + MOZ_ASSERT_IF(!IsSharedWorker(), mParentSuspended); + + // Shared workers are resumed if any of their owning documents are resumed. + if (IsSharedWorker()) { + AssertIsOnMainThread(); + MOZ_ASSERT(mSharedWorkers.Count()); + + struct Closure + { + nsPIDOMWindow* mWindow; + bool mAnyRunning; + + Closure(nsPIDOMWindow* aWindow) + : mWindow(aWindow), mAnyRunning(false) + { + AssertIsOnMainThread(); + // aWindow may be null here. + } + + static PLDHashOperator + Resume(const uint64_t& aKey, + SharedWorker* aSharedWorker, + void* aClosure) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aSharedWorker); + MOZ_ASSERT(aClosure); + + auto closure = static_cast(aClosure); + + if (closure->mWindow && aSharedWorker->GetOwner() == closure->mWindow) { + // Calling Resume() may change the refcount, ensure that the worker + // outlives this call. + nsRefPtr kungFuDeathGrip = aSharedWorker; + + aSharedWorker->Resume(); + closure->mAnyRunning = true; + } else { + MOZ_ASSERT_IF(aSharedWorker->GetOwner() && closure->mWindow, + !SameCOMIdentity(aSharedWorker->GetOwner(), + closure->mWindow)); + if (!aSharedWorker->IsSuspended()) { + closure->mAnyRunning = true; + } + } + return PL_DHASH_NEXT; + } + }; + + Closure closure(aWindow); + + mSharedWorkers.EnumerateRead(Closure::Resume, &closure); + + if (!closure.mAnyRunning || !mParentSuspended) { + return true; + } + } + + MOZ_ASSERT(mParentSuspended); mParentSuspended = false; @@ -2049,7 +2272,7 @@ WorkerPrivateParent::Resume(JSContext* aCx) MutexAutoLock lock(mMutex); if (mParentStatus >= Terminating) { - return; + return true; } } @@ -2057,6 +2280,7 @@ WorkerPrivateParent::Resume(JSContext* aCx) // could post new messages before we run those that have been queued. if (!mQueuedRunnables.IsEmpty()) { AssertIsOnMainThread(); + MOZ_ASSERT(!IsSharedWorker()); nsTArray > runnables; mQueuedRunnables.SwapElements(runnables); @@ -2069,15 +2293,22 @@ WorkerPrivateParent::Resume(JSContext* aCx) nsRefPtr runnable = new ResumeRunnable(ParentAsWorkerPrivate()); - runnable->Dispatch(aCx); + if (!runnable->Dispatch(aCx)) { + return false; + } + + return true; } template bool -WorkerPrivateParent::SynchronizeAndResume(nsIScriptContext* aCx) +WorkerPrivateParent::SynchronizeAndResume( + JSContext* aCx, + nsPIDOMWindow* aWindow, + nsIScriptContext* aScriptContext) { - AssertIsOnParentThread(); - NS_ASSERTION(mParentSuspended, "Not yet suspended!"); + AssertIsOnMainThread(); + MOZ_ASSERT_IF(!IsSharedWorker(), mParentSuspended); // 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 @@ -2086,8 +2317,14 @@ WorkerPrivateParent::SynchronizeAndResume(nsIScriptContext* aCx) // the messages. nsRefPtr runnable = - new SynchronizeAndResumeRunnable(ParentAsWorkerPrivate(), aCx); - return NS_SUCCEEDED(NS_DispatchToCurrentThread(runnable)); + new SynchronizeAndResumeRunnable(ParentAsWorkerPrivate(), aWindow, + aScriptContext); + if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { + JS_ReportError(aCx, "Failed to dispatch to current thread!"); + return false; + } + + return true; } template @@ -2225,24 +2462,32 @@ WorkerPrivateParent::ForgetMainThreadObjects( AssertIsOnParentThread(); MOZ_ASSERT(!mMainThreadObjectsForgotten); - aDoomed.SetCapacity(7); + static const uint32_t kDoomedCount = 7; - SwapToISupportsArray(mWindow, aDoomed); - SwapToISupportsArray(mScriptContext, aDoomed); - SwapToISupportsArray(mBaseURI, aDoomed); - SwapToISupportsArray(mScriptURI, aDoomed); - SwapToISupportsArray(mPrincipal, aDoomed); - SwapToISupportsArray(mChannel, aDoomed); - SwapToISupportsArray(mCSP, aDoomed); + aDoomed.SetCapacity(kDoomedCount); + + SwapToISupportsArray(mLoadInfo.mWindow, aDoomed); + SwapToISupportsArray(mLoadInfo.mScriptContext, aDoomed); + SwapToISupportsArray(mLoadInfo.mBaseURI, aDoomed); + SwapToISupportsArray(mLoadInfo.mResolvedScriptURI, aDoomed); + SwapToISupportsArray(mLoadInfo.mPrincipal, aDoomed); + SwapToISupportsArray(mLoadInfo.mChannel, aDoomed); + SwapToISupportsArray(mLoadInfo.mCSP, aDoomed); + // Before adding anything here update kDoomedCount above! + + MOZ_ASSERT(aDoomed.Length() == kDoomedCount); mMainThreadObjectsForgotten = true; } template bool -WorkerPrivateParent::PostMessage(JSContext* aCx, - JS::Handle aMessage, - JS::Handle aTransferable) +WorkerPrivateParent::PostMessageInternal( + JSContext* aCx, + JS::Handle aMessage, + JS::Handle aTransferable, + bool aToMessagePort, + uint64_t aMessagePortSerial) { AssertIsOnParentThread(); @@ -2283,17 +2528,121 @@ WorkerPrivateParent::PostMessage(JSContext* aCx, nsRefPtr runnable = new MessageEventRunnable(ParentAsWorkerPrivate(), WorkerRunnable::WorkerThread, buffer, - clonedObjects); + clonedObjects, aToMessagePort, aMessagePortSerial); return runnable->Dispatch(aCx); } +template +void +WorkerPrivateParent::PostMessageToMessagePort( + JSContext* aCx, + uint64_t aMessagePortSerial, + JS::Handle aMessage, + const Optional >& aTransferable, + ErrorResult& aRv) +{ + AssertIsOnMainThread(); + + JS::Rooted transferable(aCx, JS::UndefinedValue()); + if (aTransferable.WasPassed()) { + const Sequence& realTransferable = aTransferable.Value(); + JSObject* array = + JS_NewArrayObject(aCx, realTransferable.Length(), + const_cast(realTransferable.Elements())); + if (!array) { + aRv = NS_ERROR_OUT_OF_MEMORY; + return; + } + transferable.setObject(*array); + } + + if (!PostMessageInternal(aCx, aMessage, transferable, true, + aMessagePortSerial)) { + aRv = NS_ERROR_FAILURE; + } +} + +template +bool +WorkerPrivateParent::DispatchMessageEventToMessagePort( + JSContext* aCx, uint64_t aMessagePortSerial, + JSAutoStructuredCloneBuffer& aBuffer, + nsTArray >& aClonedObjects) +{ + AssertIsOnMainThread(); + + JSAutoStructuredCloneBuffer buffer; + buffer.swap(aBuffer); + + nsTArray > clonedObjects; + clonedObjects.SwapElements(aClonedObjects); + + SharedWorker* sharedWorker; + if (!mSharedWorkers.Get(aMessagePortSerial, &sharedWorker)) { + // SharedWorker has already been unregistered? + return true; + } + + nsRefPtr port = sharedWorker->Port(); + NS_ASSERTION(port, "SharedWorkers always have a port!"); + + if (port->IsClosed()) { + return true; + } + + nsCOMPtr sgo; + port->GetParentObject(getter_AddRefs(sgo)); + MOZ_ASSERT(sgo, "Should never happen if IsClosed() returned false!"); + MOZ_ASSERT(sgo->GetGlobalJSObject()); + + nsCOMPtr scx = sgo->GetContext(); + MOZ_ASSERT_IF(scx, scx->GetNativeContext()); + + AutoPushJSContext cx(scx ? scx->GetNativeContext() : aCx); + JSAutoCompartment(cx, sgo->GetGlobalJSObject()); + + JS::Rooted data(cx); + if (!buffer.read(cx, data.address(), WorkerStructuredCloneCallbacks(true))) { + return false; + } + + buffer.clear(); + + nsRefPtr event = + new nsDOMMessageEvent(port, nullptr, nullptr); + + nsresult rv = + event->InitMessageEvent(NS_LITERAL_STRING("message"), false, false, data, + EmptyString(), EmptyString(), nullptr); + if (NS_FAILED(rv)) { + xpc::Throw(cx, rv); + return false; + } + + event->SetTrusted(true); + + nsCOMPtr domEvent; + CallQueryInterface(event.get(), getter_AddRefs(domEvent)); + NS_ASSERTION(domEvent, "This should never fail!"); + + bool ignored; + rv = port->DispatchEvent(domEvent, &ignored); + if (NS_FAILED(rv)) { + xpc::Throw(cx, rv); + return false; + } + + return true; +} + template uint64_t WorkerPrivateParent::GetInnerWindowId() { AssertIsOnMainThread(); - NS_ASSERTION(!mWindow || mWindow->IsInnerWindow(), "Outer window?"); - return mWindow ? mWindow->WindowID() : 0; + NS_ASSERTION(!mLoadInfo.mWindow || mLoadInfo.mWindow->IsInnerWindow(), + "Outer window?"); + return mLoadInfo.mWindow ? mLoadInfo.mWindow->WindowID() : 0; } template @@ -2392,6 +2741,8 @@ template void WorkerPrivateParent::GarbageCollect(JSContext* aCx, bool aShrinking) { + AssertIsOnParentThread(); + nsRefPtr runnable = new GarbageCollectRunnable(ParentAsWorkerPrivate(), aShrinking, true); if (!runnable->Dispatch(aCx)) { @@ -2400,13 +2751,325 @@ WorkerPrivateParent::GarbageCollect(JSContext* aCx, bool aShrinking) } } +template +bool +WorkerPrivateParent::RegisterSharedWorker(JSContext* aCx, + SharedWorker* aSharedWorker) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aSharedWorker); + MOZ_ASSERT(IsSharedWorker()); + MOZ_ASSERT(!mSharedWorkers.Get(aSharedWorker->Serial())); + + nsRefPtr runnable = + new MessagePortRunnable(ParentAsWorkerPrivate(), aSharedWorker->Serial(), + true); + if (!runnable->Dispatch(aCx)) { + return false; + } + + 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)) { + return false; + } + + return true; +} + +template +void +WorkerPrivateParent::UnregisterSharedWorker( + JSContext* aCx, + SharedWorker* aSharedWorker) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aSharedWorker); + MOZ_ASSERT(IsSharedWorker()); + MOZ_ASSERT(mSharedWorkers.Get(aSharedWorker->Serial())); + + nsRefPtr runnable = + new MessagePortRunnable(ParentAsWorkerPrivate(), aSharedWorker->Serial(), + false); + if (!runnable->Dispatch(aCx)) { + JS_ReportPendingException(aCx); + } + + mSharedWorkers.Remove(aSharedWorker->Serial()); + + if (mSharedWorkers.Count()) { + // If there are still SharedWorker objects attached to this worker then they + // may all be suspended and this worker would need to be suspended. + if (!Suspend(aCx, nullptr)) { + JS_ReportPendingException(aCx); + } + } else { + // If that was the last SharedWorker then it's time to cancel this worker. + if (!Cancel(aCx)) { + JS_ReportPendingException(aCx); + } + } +} + +template +void +WorkerPrivateParent::BroadcastErrorToSharedWorkers( + JSContext* aCx, + const nsAString& aMessage, + const nsAString& aFilename, + const nsAString& aLine, + uint32_t aLineNumber, + uint32_t aColumnNumber, + uint32_t aFlags) +{ + AssertIsOnMainThread(); + + nsAutoTArray, 10> sharedWorkers; + GetAllSharedWorkers(sharedWorkers); + + if (sharedWorkers.IsEmpty()) { + return; + } + + nsAutoTArray windowActions; + nsresult rv; + + // First fire the error event at all SharedWorker objects. This may include + // multiple objects in a single window as well as objects in different + // windows. + for (uint32_t index = 0; index < sharedWorkers.Length(); index++) { + nsRefPtr& sharedWorker = sharedWorkers[index]; + + // May be null. + nsPIDOMWindow* window = sharedWorker->GetOwner(); + + uint32_t actionsIndex = windowActions.LastIndexOf(WindowAction(window)); + + // Get the context for this window so that we can report errors correctly. + JSContext* cx; + rv = NS_OK; + + if (actionsIndex == windowActions.NoIndex) { + nsIScriptContext* scx = sharedWorker->GetContextForEventHandlers(&rv); + cx = (NS_SUCCEEDED(rv) && scx) ? + scx->GetNativeContext() : + nsContentUtils::GetSafeJSContext(); + } else { + cx = windowActions[actionsIndex].mJSContext; + } + + MOZ_ASSERT(cx); + + AutoPushJSContext autoPush(cx); + + if (NS_FAILED(rv)) { + Throw(cx, rv); + JS_ReportPendingException(cx); + continue; + } + + ErrorEventInit errorInit; + errorInit.mBubbles = false; + errorInit.mCancelable = true; + errorInit.mMessage = aMessage; + errorInit.mFilename = aFilename; + errorInit.mLineno = aLineNumber; + errorInit.mColumn = aColumnNumber; + + nsRefPtr errorEvent = + ErrorEvent::Constructor(sharedWorker, NS_LITERAL_STRING("error"), + errorInit); + if (!errorEvent) { + Throw(cx, NS_ERROR_UNEXPECTED); + JS_ReportPendingException(cx); + continue; + } + + errorEvent->SetTrusted(true); + + bool defaultActionEnabled; + rv = sharedWorker->DispatchEvent(errorEvent, &defaultActionEnabled); + if (NS_FAILED(rv)) { + Throw(cx, rv); + JS_ReportPendingException(cx); + continue; + } + + if (defaultActionEnabled) { + // Add the owning window to our list so that we will fire an error event + // at it later. + if (!windowActions.Contains(window)) { + windowActions.AppendElement(WindowAction(window, cx)); + } + } else if (actionsIndex != windowActions.NoIndex) { + // Any listener that calls preventDefault() will prevent the window from + // receiving the error event. + windowActions[actionsIndex].mDefaultAction = false; + } + } + + // If there are no windows to consider further then we're done. + if (windowActions.IsEmpty()) { + return; + } + + bool shouldLogErrorToConsole = true; + + // Now fire error events at all the windows remaining. + for (uint32_t index = 0; index < windowActions.Length(); index++) { + WindowAction& windowAction = windowActions[index]; + + // If there is no window or the script already called preventDefault then + // skip this window. + if (!windowAction.mWindow || !windowAction.mDefaultAction) { + continue; + } + + AutoPushJSContext cx(windowAction.mJSContext); + MOZ_ASSERT(cx); + + nsCOMPtr sgo = + do_QueryInterface(windowAction.mWindow); + MOZ_ASSERT(sgo); + + InternalScriptErrorEvent event(true, NS_LOAD_ERROR); + event.lineNr = aLineNumber; + event.errorMsg = aMessage.BeginReading(); + event.fileName = aFilename.BeginReading(); + + nsEventStatus status = nsEventStatus_eIgnore; + rv = sgo->HandleScriptError(&event, &status); + if (NS_FAILED(rv)) { + Throw(cx, rv); + JS_ReportPendingException(cx); + continue; + } + + if (status == nsEventStatus_eConsumeNoDefault) { + shouldLogErrorToConsole = false; + } + } + + // Finally log a warning in the console if no window tried to prevent it. + if (shouldLogErrorToConsole) { + LogErrorToConsole(aMessage, aFilename, aLine, aLineNumber, aColumnNumber, + aFlags, 0); + } +} + +template +void +WorkerPrivateParent::GetAllSharedWorkers( + nsTArray >& aSharedWorkers) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(IsSharedWorker()); + + struct Helper + { + static PLDHashOperator + Collect(const uint64_t& aKey, + SharedWorker* aSharedWorker, + void* aClosure) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aSharedWorker); + MOZ_ASSERT(aClosure); + + auto array = static_cast >*>(aClosure); + array->AppendElement(aSharedWorker); + + return PL_DHASH_NEXT; + } + }; + + if (!aSharedWorkers.IsEmpty()) { + aSharedWorkers.Clear(); + } + + mSharedWorkers.EnumerateRead(Helper::Collect, &aSharedWorkers); +} + +template +void +WorkerPrivateParent::CloseSharedWorkersForWindow( + nsPIDOMWindow* aWindow) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(IsSharedWorker()); + MOZ_ASSERT(aWindow); + + struct Closure + { + nsPIDOMWindow* mWindow; + nsAutoTArray, 10> mSharedWorkers; + + Closure(nsPIDOMWindow* aWindow) + : mWindow(aWindow) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aWindow); + } + + static PLDHashOperator + Collect(const uint64_t& aKey, + SharedWorker* aSharedWorker, + void* aClosure) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aSharedWorker); + MOZ_ASSERT(aClosure); + + auto closure = static_cast(aClosure); + MOZ_ASSERT(closure->mWindow); + + if (aSharedWorker->GetOwner() == closure->mWindow) { + closure->mSharedWorkers.AppendElement(aSharedWorker); + } else { + MOZ_ASSERT(!SameCOMIdentity(aSharedWorker->GetOwner(), + closure->mWindow)); + } + + return PL_DHASH_NEXT; + } + }; + + Closure closure(aWindow); + + mSharedWorkers.EnumerateRead(Closure::Collect, &closure); + + for (uint32_t index = 0; index < closure.mSharedWorkers.Length(); index++) { + closure.mSharedWorkers[index]->Close(); + } +} + +template +void +WorkerPrivateParent::WorkerScriptLoaded() +{ + AssertIsOnMainThread(); + + if (IsSharedWorker()) { + // No longer need to hold references to the window or document we came from. + mLoadInfo.mWindow = nullptr; + mLoadInfo.mScriptContext = nullptr; + } +} + template void WorkerPrivateParent::SetBaseURI(nsIURI* aBaseURI) { AssertIsOnMainThread(); - mBaseURI = aBaseURI; + if (!mLoadInfo.mBaseURI) { + NS_ASSERTION(GetParent(), "Shouldn't happen without a parent!"); + mLoadInfo.mResolvedScriptURI = aBaseURI; + } + + mLoadInfo.mBaseURI = aBaseURI; if (NS_FAILED(aBaseURI->GetSpec(mLocationInfo.mHref))) { mLocationInfo.mHref.Truncate(); @@ -2476,8 +3139,8 @@ WorkerPrivateParent::SetPrincipal(nsIPrincipal* aPrincipal) { AssertIsOnMainThread(); - mPrincipal = aPrincipal; - mPrincipalIsSystem = nsContentUtils::IsSystemPrincipal(aPrincipal); + mLoadInfo.mPrincipal = aPrincipal; + mLoadInfo.mPrincipalIsSystem = nsContentUtils::IsSystemPrincipal(aPrincipal); } template @@ -2486,47 +3149,35 @@ WorkerPrivateParent::ParentJSContext() const { AssertIsOnParentThread(); - if (!mParent) { - AssertIsOnMainThread(); - - if (!mScriptContext) { - NS_ASSERTION(!mParentJSContext, "Shouldn't have a parent context!"); - return nsContentUtils::GetSafeJSContext(); - } - - NS_ASSERTION(mParentJSContext == mScriptContext->GetNativeContext(), - "Native context has changed!"); + if (mParent) { + return mParent->GetJSContext(); } - return mParentJSContext; + AssertIsOnMainThread(); + + return mLoadInfo.mScriptContext ? + mLoadInfo.mScriptContext->GetNativeContext() : + nsContentUtils::GetSafeJSContext(); } -WorkerPrivate::WorkerPrivate(JSContext* aCx, JS::Handle aObject, +WorkerPrivate::WorkerPrivate(JSContext* aCx, JS::HandleObject aObject, WorkerPrivate* aParent, - JSContext* aParentJSContext, - const nsAString& aScriptURL, bool aIsChromeWorker, - const nsACString& aDomain, - nsCOMPtr& aWindow, - nsCOMPtr& aParentScriptContext, - nsCOMPtr& aBaseURI, - nsCOMPtr& aPrincipal, - nsCOMPtr& aChannel, - nsCOMPtr& aCSP, - bool aEvalAllowed, - bool aReportCSPViolations, - bool aXHRParamsAllowed) -: WorkerPrivateParent(aCx, aObject, aParent, aParentJSContext, - aScriptURL, aIsChromeWorker, aDomain, - aWindow, aParentScriptContext, aBaseURI, - aPrincipal, aChannel, aCSP, aEvalAllowed, - aReportCSPViolations), + const nsAString& aScriptURL, + bool aIsChromeWorker, bool aIsSharedWorker, + const nsAString& aSharedWorkerName, + LoadInfo& aLoadInfo) +: WorkerPrivateParent(aCx, aObject, aParent, aScriptURL, + aIsChromeWorker, aIsSharedWorker, + aSharedWorkerName, aLoadInfo), mJSContext(nullptr), mErrorHandlerRecursionCount(0), mNextTimeoutId(1), mStatus(Pending), mSuspended(false), mTimerRunning(false), mRunningExpiredTimeouts(false), mCloseHandlerStarted(false), mCloseHandlerFinished(false), mMemoryReporterRunning(false), - mBlockedForMemoryReporter(false), mXHRParamsAllowed(aXHRParamsAllowed) + mBlockedForMemoryReporter(false) { MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivate); + MOZ_ASSERT_IF(aIsSharedWorker, !aObject && !aSharedWorkerName.IsVoid()); + MOZ_ASSERT_IF(!aIsSharedWorker, aObject && aSharedWorkerName.IsEmpty()); } WorkerPrivate::~WorkerPrivate() @@ -2536,114 +3187,166 @@ WorkerPrivate::~WorkerPrivate() // static already_AddRefed -WorkerPrivate::Create(JSContext* aCx, JS::Handle aObj, WorkerPrivate* aParent, - JS::Handle aScriptURL, bool aIsChromeWorker) +WorkerPrivate::Create(JSContext* aCx, JS::HandleObject aObject, + WorkerPrivate* aParent, const nsAString& aScriptURL, + bool aIsChromeWorker, bool aIsSharedWorker, + const nsAString& aSharedWorkerName, LoadInfo* aLoadInfo) { - nsCString domain; - nsCOMPtr baseURI; - nsCOMPtr principal; - nsCOMPtr scriptContext; - nsCOMPtr document; - nsCOMPtr window; - nsCOMPtr csp; + if (aParent) { + aParent->AssertIsOnWorkerThread(); + } else { + AssertIsOnMainThread(); + } - bool evalAllowed = true; - bool reportEvalViolations = false; + MOZ_ASSERT_IF(aIsSharedWorker, !aObject && !aSharedWorkerName.IsVoid()); + MOZ_ASSERT_IF(!aIsSharedWorker, aObject && aSharedWorkerName.IsEmpty()); - JSContext* parentContext; + mozilla::Maybe stackLoadInfo; + if (!aLoadInfo) { + stackLoadInfo.construct(); - bool xhrParamsAllowed = false; + nsresult rv = GetLoadInfo(aCx, nullptr, aParent, aScriptURL, + aIsChromeWorker, stackLoadInfo.addr()); + if (NS_FAILED(rv)) { + scriptloader::ReportLoadError(aCx, aScriptURL, rv, !aParent); + return nullptr; + } + + aLoadInfo = stackLoadInfo.addr(); + } + + nsRefPtr worker = + new WorkerPrivate(aCx, aObject, aParent, aScriptURL, aIsChromeWorker, + aIsSharedWorker, aSharedWorkerName, *aLoadInfo); + + nsRefPtr compiler = new CompileScriptRunnable(worker); + if (!compiler->Dispatch(aCx)) { + return nullptr; + } + + return worker.forget(); +} + +// static +nsresult +WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindow* aWindow, + WorkerPrivate* aParent, const nsAString& aScriptURL, + bool aIsChromeWorker, LoadInfo* aLoadInfo) +{ + using namespace mozilla::dom::workers::scriptloader; + + MOZ_ASSERT(aCx); + + if (aWindow) { + AssertIsOnMainThread(); + } + + LoadInfo loadInfo; + nsresult rv; if (aParent) { aParent->AssertIsOnWorkerThread(); // If the parent is going away give up now. - Status currentStatus; + Status parentStatus; { MutexAutoLock lock(aParent->mMutex); - currentStatus = aParent->mStatus; + parentStatus = aParent->mStatus; } - if (currentStatus > Running) { - JS_ReportError(aCx, "Cannot create child workers from the close handler!"); - return nullptr; + if (parentStatus > Running) { + NS_WARNING("Cannot create child workers from the close handler!"); + return NS_ERROR_FAILURE; } - parentContext = aCx; + rv = ChannelFromScriptURLWorkerThread(aCx, aParent, aScriptURL, + getter_AddRefs(loadInfo.mChannel)); + NS_ENSURE_SUCCESS(rv, rv); - // Domain is the only thing we can touch here. The rest will be handled by - // the ScriptLoader. - domain = aParent->Domain(); - } - else { + // Now that we've spun the loop there's no guarantee that our parent is + // still alive. We may have received control messages initiating shutdown. + { + MutexAutoLock lock(aParent->mMutex); + parentStatus = aParent->mStatus; + } + + if (parentStatus > Running) { + nsCOMPtr mainThread; + if (NS_FAILED(NS_GetMainThread(getter_AddRefs(mainThread))) || + NS_FAILED(NS_ProxyRelease(mainThread, loadInfo.mChannel))) { + NS_WARNING("Failed to proxy release of channel, leaking instead!"); + } + return NS_ERROR_FAILURE; + } + + loadInfo.mDomain = aParent->Domain(); + } else { AssertIsOnMainThread(); nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); - NS_ASSERTION(ssm, "This should never be null!"); + MOZ_ASSERT(ssm); bool isChrome = nsContentUtils::IsCallerChrome(); - // First check to make sure the caller has permission to make a - // ChromeWorker if they called the ChromeWorker constructor. + // First check to make sure the caller has permission to make a privileged + // worker if they called the ChromeWorker/ChromeSharedWorker constructor. if (aIsChromeWorker && !isChrome) { - xpc::Throw(aCx, NS_ERROR_DOM_SECURITY_ERR); - return nullptr; + return NS_ERROR_DOM_SECURITY_ERR; } // Chrome callers (whether ChromeWorker of Worker) always get the system // principal here as they're allowed to load anything. The script loader may // change the principal later depending on the script uri. - if (isChrome && - NS_FAILED(ssm->GetSystemPrincipal(getter_AddRefs(principal)))) { - JS_ReportError(aCx, "Could not get system principal!"); - return nullptr; + if (isChrome) { + rv = ssm->GetSystemPrincipal(getter_AddRefs(loadInfo.mPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); } - // See if we're being called from a window or from somewhere else. - nsCOMPtr scriptGlobal = - nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(aCx)); - if (scriptGlobal) { - // Window! - nsCOMPtr globalWindow = do_QueryInterface(scriptGlobal); + // See if we're being called from a window. + nsCOMPtr globalWindow = aWindow; + if (!globalWindow) { + nsCOMPtr scriptGlobal = + nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(aCx)); + if (scriptGlobal) { + globalWindow = do_QueryInterface(scriptGlobal); + MOZ_ASSERT(globalWindow); + } + } + nsCOMPtr document; + + if (globalWindow) { // Only use the current inner window, and only use it if the caller can // access it. - nsPIDOMWindow* outerWindow = globalWindow ? - globalWindow->GetOuterWindow() : - nullptr; - window = outerWindow ? outerWindow->GetCurrentInnerWindow() : nullptr; - if (!window || - (globalWindow != window && - !nsContentUtils::CanCallerAccess(window))) { - xpc::Throw(aCx, NS_ERROR_DOM_SECURITY_ERR); - return nullptr; + nsPIDOMWindow* outerWindow = globalWindow->GetOuterWindow(); + if (outerWindow) { + loadInfo.mWindow = outerWindow->GetCurrentInnerWindow(); } - scriptContext = scriptGlobal->GetContext(); - if (!scriptContext) { - JS_ReportError(aCx, "Couldn't get script context for this worker!"); - return nullptr; + if (!loadInfo.mWindow || + (globalWindow != loadInfo.mWindow && + !nsContentUtils::CanCallerAccess(loadInfo.mWindow))) { + return NS_ERROR_DOM_SECURITY_ERR; } - parentContext = scriptContext->GetNativeContext(); + nsCOMPtr sgo = do_QueryInterface(loadInfo.mWindow); + MOZ_ASSERT(sgo); + + loadInfo.mScriptContext = sgo->GetContext(); + NS_ENSURE_TRUE(loadInfo.mScriptContext, NS_ERROR_FAILURE); // If we're called from a window then we can dig out the principal and URI // from the document. - document = window->GetExtantDoc(); - if (!document) { - JS_ReportError(aCx, "No document in this window!"); - return nullptr; - } + document = loadInfo.mWindow->GetExtantDoc(); + NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); - baseURI = document->GetDocBaseURI(); + loadInfo.mBaseURI = document->GetDocBaseURI(); // Use the document's NodePrincipal as our principal if we're not being // called from chrome. - if (!principal) { - if (!(principal = document->NodePrincipal())) { - JS_ReportError(aCx, "Could not get document principal!"); - return nullptr; - } + if (!loadInfo.mPrincipal) { + loadInfo.mPrincipal = document->NodePrincipal(); + NS_ENSURE_TRUE(loadInfo.mPrincipal, NS_ERROR_FAILURE); // We use the document's base domain to limit the number of workers // each domain can create. For sandboxed documents, we use the domain @@ -2659,131 +3362,80 @@ WorkerPrivate::Create(JSContext* aCx, JS::Handle aObj, WorkerPrivate* if (tmpDoc) { // There was an unsandboxed ancestor, yay! nsCOMPtr tmpPrincipal = tmpDoc->NodePrincipal(); - - if (NS_FAILED(tmpPrincipal->GetBaseDomain(domain))) { - JS_ReportError(aCx, "Could not determine base domain!"); - return nullptr; - } + rv = tmpPrincipal->GetBaseDomain(loadInfo.mDomain); + NS_ENSURE_SUCCESS(rv, rv); } else { // No unsandboxed ancestor, use our GUID. - if (NS_FAILED(principal->GetBaseDomain(domain))) { - JS_ReportError(aCx, "Could not determine base domain!"); - return nullptr; - } + rv = loadInfo.mPrincipal->GetBaseDomain(loadInfo.mDomain); + NS_ENSURE_SUCCESS(rv, rv); } } else { // Document creating the worker is not sandboxed. - if (NS_FAILED(principal->GetBaseDomain(domain))) { - JS_ReportError(aCx, "Could not determine base domain!"); - return nullptr; - } + rv = loadInfo.mPrincipal->GetBaseDomain(loadInfo.mDomain); + NS_ENSURE_SUCCESS(rv, rv); } } - xhrParamsAllowed = CheckXHRParamsAllowed(window); - } - else { - // Not a window - NS_ASSERTION(isChrome, "Should be chrome only!"); + nsCOMPtr permMgr = + do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); - parentContext = nullptr; + uint32_t perm; + rv = permMgr->TestPermissionFromPrincipal(loadInfo.mPrincipal, "systemXHR", + &perm); + NS_ENSURE_SUCCESS(rv, rv); + + loadInfo.mXHRParamsAllowed = perm == nsIPermissionManager::ALLOW_ACTION; + } else { + // Not a window + MOZ_ASSERT(isChrome); // We're being created outside of a window. Need to figure out the script // that is creating us in order for us to use relative URIs later on. JS::RootedScript script(aCx); if (JS_DescribeScriptedCaller(aCx, &script, nullptr)) { - if (NS_FAILED(NS_NewURI(getter_AddRefs(baseURI), - JS_GetScriptFilename(aCx, script)))) { - JS_ReportError(aCx, "Failed to construct base URI!"); - return nullptr; + rv = NS_NewURI(getter_AddRefs(loadInfo.mBaseURI), + JS_GetScriptFilename(aCx, script)); + NS_ENSURE_SUCCESS(rv, rv); } - } - xhrParamsAllowed = true; + loadInfo.mXHRParamsAllowed = true; } - NS_ASSERTION(principal, "Must have a principal now!"); + MOZ_ASSERT(loadInfo.mPrincipal); + MOZ_ASSERT(isChrome || !loadInfo.mDomain.IsEmpty()); - if (!isChrome && domain.IsEmpty()) { - NS_ERROR("Must be chrome or have an domain!"); - return nullptr; + // XXXbent Use subject principal here instead of the one we already have? + nsCOMPtr subjectPrincipal = ssm->GetCxSubjectPrincipal(aCx); + MOZ_ASSERT(subjectPrincipal); + + if (!nsContentUtils::GetContentSecurityPolicy(aCx, + getter_AddRefs(loadInfo.mCSP))) { + NS_WARNING("Failed to get CSP!"); + return NS_ERROR_FAILURE; } - if (!nsContentUtils::GetContentSecurityPolicy(aCx, getter_AddRefs(csp))) { - return nullptr; + if (loadInfo.mCSP) { + rv = loadInfo.mCSP->GetAllowsEval(&loadInfo.mReportCSPViolations, + &loadInfo.mEvalAllowed); + NS_ENSURE_SUCCESS(rv, rv); + } else { + loadInfo.mEvalAllowed = true; + loadInfo.mReportCSPViolations = false; } - if (csp && NS_FAILED(csp->GetAllowsEval(&reportEvalViolations, &evalAllowed))) { - NS_ERROR("CSP: failed to get allowsEval"); - return nullptr; - } + rv = ChannelFromScriptURLMainThread(loadInfo.mPrincipal, loadInfo.mBaseURI, + document, aScriptURL, + getter_AddRefs(loadInfo.mChannel)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetFinalChannelURI(loadInfo.mChannel, + getter_AddRefs(loadInfo.mResolvedScriptURI)); + NS_ENSURE_SUCCESS(rv, rv); } - size_t urlLength; - const jschar* urlChars = JS_GetStringCharsZAndLength(aCx, aScriptURL, - &urlLength); - if (!urlChars) { - return nullptr; - } - - nsDependentString scriptURL(urlChars, urlLength); - nsCOMPtr channel; - nsresult rv; - if (aParent) { - rv = - scriptloader::ChannelFromScriptURLWorkerThread(aCx, aParent, scriptURL, - getter_AddRefs(channel)); - - // Now that we've spun the loop there's no guarantee that our parent is - // still alive. We may have received control messages initiating shutdown. - - Status currentStatus; - { - MutexAutoLock lock(aParent->mMutex); - currentStatus = aParent->mStatus; - } - - if (currentStatus > Running) { - nsCOMPtr mainThread; - NS_GetMainThread(getter_AddRefs(mainThread)); - if (!mainThread) { - MOZ_CRASH(); - } - - nsIChannel* rawChannel; - channel.forget(&rawChannel); - // If this fails we accept the leak. - NS_ProxyRelease(mainThread, rawChannel); - - return nullptr; - } - } - else { - rv = - scriptloader::ChannelFromScriptURLMainThread(principal, baseURI, - document, scriptURL, - getter_AddRefs(channel)); - } - if (NS_FAILED(rv)) { - scriptloader::ReportLoadError(aCx, scriptURL, rv, !aParent); - return nullptr; - } - - nsRefPtr worker = - new WorkerPrivate(aCx, aObj, aParent, parentContext, scriptURL, - aIsChromeWorker, domain, window, scriptContext, baseURI, - principal, channel, csp, evalAllowed, reportEvalViolations, - xhrParamsAllowed); - - worker->SetIsDOMBinding(); - worker->SetWrapper(aObj); - - nsRefPtr compiler = new CompileScriptRunnable(worker); - if (!compiler->Dispatch(aCx)) { - return nullptr; - } - - return worker.forget(); + aLoadInfo->StealFrom(loadInfo); + return NS_OK; } void @@ -2973,7 +3625,13 @@ WorkerPrivate::OperationCallback(JSContext* aCx) // Run all control events now. mayContinue = ProcessAllControlRunnables(); - if (!mayContinue || !mSuspended) { + bool maySuspend = mSuspended; + if (maySuspend) { + MutexAutoLock lock(mMutex); + maySuspend = mStatus <= Running; + } + + if (!mayContinue || !maySuspend) { break; } @@ -3237,36 +3895,6 @@ WorkerPrivate::ProcessAllControlRunnables() return result; } -bool -WorkerPrivate::CheckXHRParamsAllowed(nsPIDOMWindow* aWindow) -{ - AssertIsOnMainThread(); - NS_ASSERTION(aWindow, "Wrong cannot be null"); - - if (!aWindow->GetDocShell()) { - return false; - } - - nsCOMPtr doc = aWindow->GetExtantDoc(); - if (!doc) { - return false; - } - - nsCOMPtr permMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); - if (!permMgr) { - return false; - } - - uint32_t permission; - nsresult rv = permMgr->TestPermissionFromPrincipal(doc->NodePrincipal(), - "systemXHR", &permission); - if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) { - return false; - } - - return true; -} - bool WorkerPrivate::Dispatch(WorkerRunnable* aEvent, EventQueue* aQueue) { @@ -3395,6 +4023,20 @@ WorkerPrivate::TraceInternal(JSTracer* aTrc) "WorkerPrivate timeout extra argument value"); } } + + // MessagePort objects are basically held alive as long as the global. + mWorkerPorts.EnumerateRead(TraceMessagePorts, aTrc); +} + +// static +PLDHashOperator +WorkerPrivate::TraceMessagePorts(const uint64_t& aKey, + WorkerMessagePort* aData, + void* aUserArg) +{ + JSTracer* trc = static_cast(aUserArg); + aData->TraceJSObject(trc, "mWorkerPorts"); + return PL_DHASH_NEXT; } bool @@ -3641,9 +4283,11 @@ WorkerPrivate::DestroySyncLoop(uint32_t aSyncLoopKey) } bool -WorkerPrivate::PostMessageToParent(JSContext* aCx, +WorkerPrivate::PostMessageToParentInternal(JSContext* aCx, JS::Handle aMessage, - JS::Handle aTransferable) + JS::Handle aTransferable, + bool aToMessagePort, + uint64_t aMessagePortSerial) { AssertIsOnWorkerThread(); @@ -3661,10 +4305,45 @@ WorkerPrivate::PostMessageToParent(JSContext* aCx, nsRefPtr runnable = new MessageEventRunnable(this, WorkerRunnable::ParentThread, buffer, - clonedObjects); + clonedObjects, aToMessagePort, aMessagePortSerial); return runnable->Dispatch(aCx); } +void +WorkerPrivate::PostMessageToParentMessagePort( + JSContext* aCx, + uint64_t aMessagePortSerial, + JS::Handle aMessage, + const Optional >& aTransferable, + ErrorResult& aRv) +{ + AssertIsOnWorkerThread(); + + if (!mWorkerPorts.Get(aMessagePortSerial)) { + // This port has been closed from the main thread. There's no point in + // sending this message so just bail. + return; + } + + JS::Rooted transferable(aCx, JS::UndefinedValue()); + if (aTransferable.WasPassed()) { + const Sequence& realTransferable = aTransferable.Value(); + JSObject* array = + JS_NewArrayObject(aCx, realTransferable.Length(), + const_cast(realTransferable.Elements())); + if (!array) { + aRv = NS_ERROR_OUT_OF_MEMORY; + return; + } + transferable.setObject(*array); + } + + if (!PostMessageToParentInternal(aCx, aMessage, transferable, true, + aMessagePortSerial)) { + aRv = NS_ERROR_FAILURE; + } +} + bool WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus) { @@ -4275,52 +4954,6 @@ WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking, } } -#ifdef DEBUG -template -void -WorkerPrivateParent::AssertIsOnParentThread() const -{ - if (GetParent()) { - GetParent()->AssertIsOnWorkerThread(); - } - else { - AssertIsOnMainThread(); - } -} - -template -void -WorkerPrivateParent::AssertInnerWindowIsCorrect() const -{ - AssertIsOnParentThread(); - - // Only care about top level workers from windows. - if (mParent || !mWindow) { - return; - } - - AssertIsOnMainThread(); - - nsPIDOMWindow* outer = mWindow->GetOuterWindow(); - NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mWindow, - "Inner window no longer correct!"); -} - -void -WorkerPrivate::AssertIsOnWorkerThread() const -{ - if (mThread) { - bool current; - if (NS_FAILED(mThread->IsOnCurrentThread(¤t)) || !current) { - NS_ERROR("Wrong thread!"); - } - } - else { - NS_ERROR("Trying to assert thread identity after thread has been " - "shutdown!"); - } -} -#endif template void @@ -4345,13 +4978,24 @@ WorkerPrivateParent::StealHostObjectURIs(nsTArray& aArray) aArray.SwapElements(mHostObjectURIs); } +template +JSObject* +WorkerPrivateParent::WrapObject(JSContext* aCx, + JS::HandleObject aScope) +{ + MOZ_CRASH("This should never be called!"); + return nullptr; +} + WorkerCrossThreadDispatcher* WorkerPrivate::GetCrossThreadDispatcher() { - mozilla::MutexAutoLock lock(mMutex); + MutexAutoLock lock(mMutex); + if (!mCrossThreadDispatcher && mStatus <= Running) { mCrossThreadDispatcher = new WorkerCrossThreadDispatcher(this); } + return mCrossThreadDispatcher; } @@ -4395,6 +5039,107 @@ WorkerPrivate::EndCTypesCall() mBlockedForMemoryReporter = false; } +bool +WorkerPrivate::ConnectMessagePort(JSContext* aCx, uint64_t aMessagePortSerial) +{ + AssertIsOnWorkerThread(); + + NS_ASSERTION(!mWorkerPorts.Get(aMessagePortSerial), + "Already have this port registered!"); + + nsRefPtr port = + new WorkerMessagePort(aCx, aMessagePortSerial); + + JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); + + JS::Rooted portObj(aCx, Wrap(aCx, global, port)); + if (!portObj) { + return false; + } + + JS::Rooted event(aCx, CreateConnectEvent(aCx, portObj)); + if (!event) { + return false; + } + + mWorkerPorts.Put(aMessagePortSerial, port); + + bool dummy; + if (!DispatchEventToTarget(aCx, global, event, &dummy)) { + mWorkerPorts.Remove(aMessagePortSerial); + return false; + } + + return true; +} + +void +WorkerPrivate::DisconnectMessagePort(uint64_t aMessagePortSerial) +{ + AssertIsOnWorkerThread(); + + // The port may have already been removed from this list since either the main + // thread or the worker thread can remove it. + mWorkerPorts.Remove(aMessagePortSerial); +} + +WorkerMessagePort* +WorkerPrivate::GetMessagePort(uint64_t aMessagePortSerial) +{ + AssertIsOnWorkerThread(); + + WorkerMessagePort* port; + if (mWorkerPorts.Get(aMessagePortSerial, &port)) { + return port; + } + + return nullptr; +} + +#ifdef DEBUG +template +void +WorkerPrivateParent::AssertIsOnParentThread() const +{ + if (GetParent()) { + GetParent()->AssertIsOnWorkerThread(); + } + else { + AssertIsOnMainThread(); + } +} + +template +void +WorkerPrivateParent::AssertInnerWindowIsCorrect() const +{ + AssertIsOnParentThread(); + + // Only care about top level workers from windows. + if (mParent || !mLoadInfo.mWindow) { + return; + } + + AssertIsOnMainThread(); + + nsPIDOMWindow* outer = mLoadInfo.mWindow->GetOuterWindow(); + NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow, + "Inner window no longer correct!"); +} + +void +WorkerPrivate::AssertIsOnWorkerThread() const +{ + MOZ_ASSERT(mThread, + "Trying to assert thread identity after thread has been " + "shutdown!"); + + bool current; + MOZ_ASSERT(NS_SUCCEEDED(mThread->IsOnCurrentThread(¤t))); + MOZ_ASSERT(current, "Wrong thread!"); +} +#endif // DEBUG + BEGIN_WORKERS_NAMESPACE // Force instantiation. diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index c3f9294837fc..a8ca9c2b277b 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -19,9 +19,11 @@ #include "mozilla/CondVar.h" #include "mozilla/Mutex.h" #include "mozilla/TimeStamp.h" -#include "nsAutoPtr.h" -#include "nsCOMPtr.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDataHashtable.h" #include "nsEventQueue.h" +#include "nsHashKeys.h" #include "nsString.h" #include "nsTArray.h" #include "nsTPriorityQueue.h" @@ -33,6 +35,7 @@ class JSAutoStructuredCloneBuffer; class nsIChannel; +class nsIContentSecurityPolicy; class nsIDocument; class nsIPrincipal; class nsIScriptContext; @@ -46,6 +49,9 @@ class RuntimeStats; BEGIN_WORKERS_NAMESPACE +class MessagePort; +class SharedWorker; +class WorkerMessagePort; class WorkerPrivate; class WorkerRunnable : public nsIRunnable @@ -246,7 +252,50 @@ public: nsCString mHash; }; + struct LoadInfo + { + // All of these should be released in ForgetMainThreadObjects. + nsCOMPtr mBaseURI; + nsCOMPtr mResolvedScriptURI; + nsCOMPtr mPrincipal; + nsCOMPtr mScriptContext; + nsCOMPtr mWindow; + nsCOMPtr mCSP; + nsCOMPtr mChannel; + + nsCString mDomain; + + bool mEvalAllowed; + bool mReportCSPViolations; + bool mXHRParamsAllowed; + bool mPrincipalIsSystem; + + LoadInfo() + : mEvalAllowed(false), mReportCSPViolations(false), + mXHRParamsAllowed(false), mPrincipalIsSystem(false) + { } + + void + StealFrom(LoadInfo& aOther) + { + mBaseURI = aOther.mBaseURI.forget(); + mResolvedScriptURI = aOther.mResolvedScriptURI.forget(); + mPrincipal = aOther.mPrincipal.forget(); + mScriptContext = aOther.mScriptContext.forget(); + mWindow = aOther.mWindow.forget(); + mCSP = aOther.mCSP.forget(); + mChannel = aOther.mChannel.forget(); + mDomain = aOther.mDomain; + mEvalAllowed = aOther.mEvalAllowed; + mReportCSPViolations = aOther.mReportCSPViolations; + mXHRParamsAllowed = aOther.mXHRParamsAllowed; + mPrincipalIsSystem = aOther.mPrincipalIsSystem; + } + }; + protected: + typedef mozilla::ErrorResult ErrorResult; + SharedMutex mMutex; mozilla::CondVar mCondVar; mozilla::CondVar mMemoryReportCondVar; @@ -254,19 +303,10 @@ protected: private: JSObject* mJSObject; WorkerPrivate* mParent; - JSContext* mParentJSContext; nsString mScriptURL; - nsCString mDomain; + nsString mSharedWorkerName; LocationInfo mLocationInfo; - - // Main-thread things. - nsCOMPtr mWindow; - nsCOMPtr mScriptContext; - nsCOMPtr mBaseURI; - nsCOMPtr mScriptURI; - nsCOMPtr mPrincipal; - nsCOMPtr mChannel; - nsCOMPtr mCSP; + LoadInfo mLoadInfo; // Only used for top level workers. nsTArray > mQueuedRunnables; @@ -277,28 +317,24 @@ private: // Protected by mMutex. JSSettings mJSSettings; + // Only touched on the parent thread (currently this is always the main + // thread as SharedWorkers are always top-level). + nsDataHashtable mSharedWorkers; + uint64_t mBusyCount; + uint64_t mMessagePortSerial; Status mParentStatus; bool mJSObjectRooted; bool mParentSuspended; bool mIsChromeWorker; - bool mPrincipalIsSystem; bool mMainThreadObjectsForgotten; - bool mEvalAllowed; - bool mReportCSPViolations; + bool mIsSharedWorker; protected: - WorkerPrivateParent(JSContext* aCx, JS::Handle aObject, WorkerPrivate* aParent, - JSContext* aParentJSContext, const nsAString& aScriptURL, - bool aIsChromeWorker, const nsACString& aDomain, - nsCOMPtr& aWindow, - nsCOMPtr& aScriptContext, - nsCOMPtr& aBaseURI, - nsCOMPtr& aPrincipal, - nsCOMPtr& aChannel, - nsCOMPtr& aCSP, - bool aEvalAllowed, - bool aReportCSPViolations); + WorkerPrivateParent(JSContext* aCx, JS::HandleObject aObject, + WorkerPrivate* aParent, const nsAString& aScriptURL, + bool aIsChromeWorker, bool aIsSharedWorker, + const nsAString& aSharedWorkerName, LoadInfo& aLoadInfo); ~WorkerPrivateParent(); @@ -320,6 +356,11 @@ private: return NotifyPrivate(aCx, Terminating); } + bool + PostMessageInternal(JSContext* aCx, JS::Handle aMessage, + JS::Handle aTransferable, + bool aToMessagePort, uint64_t aMessagePortSerial); + public: // May be called on any thread... bool @@ -345,13 +386,14 @@ public: } bool - Suspend(JSContext* aCx); - - void - Resume(JSContext* aCx); + Suspend(JSContext* aCx, nsPIDOMWindow* aWindow); bool - SynchronizeAndResume(nsIScriptContext* aCx); + Resume(JSContext* aCx, nsPIDOMWindow* aWindow); + + bool + SynchronizeAndResume(JSContext* aCx, nsPIDOMWindow* aWindow, + nsIScriptContext* aScriptContext); virtual void _trace(JSTracer* aTrc) MOZ_OVERRIDE; @@ -387,7 +429,24 @@ public: bool PostMessage(JSContext* aCx, JS::Handle aMessage, - JS::Handle aTransferable); + JS::Handle aTransferable) + { + return PostMessageInternal(aCx, aMessage, aTransferable, false, 0); + } + + void + PostMessageToMessagePort(JSContext* aCx, + uint64_t aMessagePortSerial, + JS::Handle aMessage, + const Optional >& aTransferable, + ErrorResult& aRv); + + bool + DispatchMessageEventToMessagePort( + JSContext* aCx, + uint64_t aMessagePortSerial, + JSAutoStructuredCloneBuffer& aBuffer, + nsTArray >& aClonedObjects); uint64_t GetInnerWindowId(); @@ -411,6 +470,24 @@ public: void GarbageCollect(JSContext* aCx, bool aShrinking); + bool + RegisterSharedWorker(JSContext* aCx, SharedWorker* aSharedWorker); + + void + UnregisterSharedWorker(JSContext* aCx, SharedWorker* aSharedWorker); + + void + BroadcastErrorToSharedWorkers(JSContext* aCx, + const nsAString& aMessage, + const nsAString& aFilename, + const nsAString& aLine, + uint32_t aLineNumber, + uint32_t aColumnNumber, + uint32_t aFlags); + + void + WorkerScriptLoaded(); + void QueueRunnable(WorkerRunnable* aRunnable) { @@ -457,7 +534,7 @@ public: GetScriptContext() const { AssertIsOnMainThread(); - return mScriptContext; + return mLoadInfo.mScriptContext; } JSObject* @@ -475,38 +552,31 @@ public: const nsCString& Domain() const { - return mDomain; + return mLoadInfo.mDomain; } nsIURI* GetBaseURI() const { AssertIsOnMainThread(); - return mBaseURI; + return mLoadInfo.mBaseURI; } void SetBaseURI(nsIURI* aBaseURI); nsIURI* - GetScriptURI() const + GetResolvedScriptURI() const { AssertIsOnMainThread(); - return mScriptURI; - } - - void - SetScriptURI(nsIURI* aScriptURI) - { - AssertIsOnMainThread(); - mScriptURI = aScriptURI; + return mLoadInfo.mResolvedScriptURI; } nsIPrincipal* GetPrincipal() const { AssertIsOnMainThread(); - return mPrincipal; + return mLoadInfo.mPrincipal; } void @@ -515,60 +585,72 @@ public: bool UsesSystemPrincipal() const { - return mPrincipalIsSystem; + return mLoadInfo.mPrincipalIsSystem; } - nsIChannel* - GetChannel() const + already_AddRefed + ForgetWorkerChannel() { AssertIsOnMainThread(); - return mChannel; + return mLoadInfo.mChannel.forget(); } nsIDocument* GetDocument() const { AssertIsOnMainThread(); - return mWindow ? mWindow->GetExtantDoc() : nullptr; + return mLoadInfo.mWindow ? mLoadInfo.mWindow->GetExtantDoc() : nullptr; } nsPIDOMWindow* GetWindow() { AssertIsOnMainThread(); - return mWindow; + return mLoadInfo.mWindow; } nsIContentSecurityPolicy* GetCSP() const { AssertIsOnMainThread(); - return mCSP; + return mLoadInfo.mCSP; } void SetCSP(nsIContentSecurityPolicy* aCSP) { AssertIsOnMainThread(); - mCSP = aCSP; + mLoadInfo.mCSP = aCSP; } bool IsEvalAllowed() const { - return mEvalAllowed; + return mLoadInfo.mEvalAllowed; } void SetEvalAllowed(bool aEvalAllowed) { - mEvalAllowed = aEvalAllowed; + mLoadInfo.mEvalAllowed = aEvalAllowed; } bool GetReportCSPViolations() const { - return mReportCSPViolations; + return mLoadInfo.mReportCSPViolations; + } + + bool + XHRParamsAllowed() const + { + return mLoadInfo.mXHRParamsAllowed; + } + + void + SetXHRParamsAllowed(bool aAllowed) + { + mLoadInfo.mXHRParamsAllowed = aAllowed; } LocationInfo& @@ -590,6 +672,43 @@ public: return mIsChromeWorker; } + bool + IsSharedWorker() const + { + return mIsSharedWorker; + } + + const nsString& + SharedWorkerName() const + { + return mSharedWorkerName; + } + + uint64_t + NextMessagePortSerial() + { + AssertIsOnMainThread(); + return mMessagePortSerial++; + } + + void + GetAllSharedWorkers(nsTArray >& aSharedWorkers); + + void + CloseSharedWorkersForWindow(nsPIDOMWindow* aWindow); + + void + RegisterHostObjectURI(const nsACString& aURI); + + void + UnregisterHostObjectURI(const nsACString& aURI); + + void + StealHostObjectURIs(nsTArray& aArray); + + virtual JSObject* + WrapObject(JSContext* aCx, JS::HandleObject aScope) MOZ_OVERRIDE; + #ifdef DEBUG void AssertIsOnParentThread() const; @@ -605,10 +724,6 @@ public: AssertInnerWindowIsCorrect() const { } #endif - - void RegisterHostObjectURI(const nsACString& aURI); - void UnregisterHostObjectURI(const nsACString& aURI); - void StealHostObjectURIs(nsTArray& aArray); }; class WorkerPrivate : public WorkerPrivateParent @@ -658,6 +773,8 @@ class WorkerPrivate : public WorkerPrivateParent nsCOMPtr mTimer; nsRefPtr mMemoryReporter; + nsDataHashtable mWorkerPorts; + mozilla::TimeStamp mKillTime; uint32_t mErrorHandlerRecursionCount; uint32_t mNextTimeoutId; @@ -669,7 +786,6 @@ class WorkerPrivate : public WorkerPrivateParent bool mCloseHandlerFinished; bool mMemoryReporterRunning; bool mBlockedForMemoryReporter; - bool mXHRParamsAllowed; #ifdef DEBUG nsCOMPtr mThread; @@ -679,8 +795,15 @@ public: ~WorkerPrivate(); static already_AddRefed - Create(JSContext* aCx, JS::Handle aObj, WorkerPrivate* aParent, - JS::Handle aScriptURL, bool aIsChromeWorker); + Create(JSContext* aCx, JS::HandleObject aObject, WorkerPrivate* aParent, + const nsAString& aScriptURL, bool aIsChromeWorker, + bool aIsSharedWorker, const nsAString& aSharedWorkerName, + LoadInfo* aLoadInfo = nullptr); + + static nsresult + GetLoadInfo(JSContext* aCx, nsPIDOMWindow* aWindow, WorkerPrivate* aParent, + const nsAString& aScriptURL, bool aIsChromeWorker, + LoadInfo* aLoadInfo); void DoRunLoop(JSContext* aCx); @@ -764,8 +887,20 @@ public: DestroySyncLoop(uint32_t aSyncLoopKey); bool - PostMessageToParent(JSContext* aCx, JS::Handle aMessage, - JS::Handle transferable); + PostMessageToParent(JSContext* aCx, + JS::Handle aMessage, + JS::Handle aTransferable) + { + return PostMessageToParentInternal(aCx, aMessage, aTransferable, false, 0); + } + + void + PostMessageToParentMessagePort( + JSContext* aCx, + uint64_t aMessagePortSerial, + JS::Handle aMessage, + const Optional >& aTransferable, + ErrorResult& aRv); bool NotifyInternal(JSContext* aCx, Status aStatus); @@ -812,18 +947,6 @@ public: bool BlockAndCollectRuntimeStats(JS::RuntimeStats* aRtStats); - bool - XHRParamsAllowed() const - { - return mXHRParamsAllowed; - } - - void - SetXHRParamsAllowed(bool aAllowed) - { - mXHRParamsAllowed = aAllowed; - } - #ifdef JS_GC_ZEAL void UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal, uint32_t aFrequency); @@ -885,16 +1008,20 @@ public: BeginCTypesCall(); } + bool + ConnectMessagePort(JSContext* aCx, uint64_t aMessagePortSerial); + + void + DisconnectMessagePort(uint64_t aMessagePortSerial); + + WorkerMessagePort* + GetMessagePort(uint64_t aMessagePortSerial); + private: - WorkerPrivate(JSContext* aCx, JS::Handle aObject, WorkerPrivate* aParent, - JSContext* aParentJSContext, const nsAString& aScriptURL, - bool aIsChromeWorker, const nsACString& aDomain, - nsCOMPtr& aWindow, - nsCOMPtr& aScriptContext, - nsCOMPtr& aBaseURI, nsCOMPtr& aPrincipal, - nsCOMPtr& aChannel, - nsCOMPtr& aCSP, bool aEvalAllowed, - bool aReportCSPViolations, bool aXHRParamsAllowed); + WorkerPrivate(JSContext* aCx, JS::HandleObject aObject, + WorkerPrivate* aParent, const nsAString& aScriptURL, + bool aIsChromeWorker, bool aIsSharedWorker, + const nsAString& aSharedWorkerName, LoadInfo& aLoadInfo); bool Dispatch(WorkerRunnable* aEvent, EventQueue* aQueue); @@ -960,8 +1087,17 @@ private: void WaitForWorkerEvents(PRIntervalTime interval = PR_INTERVAL_NO_TIMEOUT); - static bool - CheckXHRParamsAllowed(nsPIDOMWindow* aWindow); + static PLDHashOperator + TraceMessagePorts(const uint64_t& aKey, + WorkerMessagePort* aData, + void* aUserArg); + + bool + PostMessageToParentInternal(JSContext* aCx, + JS::Handle aMessage, + JS::Handle aTransferable, + bool aToMessagePort, + uint64_t aMessagePortSerial); }; WorkerPrivate* diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index 617f69670dab..3169f9d87b63 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -141,7 +141,8 @@ protected: _trace(JSTracer* aTrc) MOZ_OVERRIDE { for (int32_t i = 0; i < SLOT_COUNT; i++) { - JS_CallHeapValueTracer(aTrc, &mSlots[i], "WorkerGlobalScope instance slot"); + JS_CallHeapValueTracer(aTrc, &mSlots[i], + "WorkerGlobalScope instance slot"); } mWorker->TraceInternal(aTrc); EventTarget::_trace(aTrc); @@ -296,8 +297,10 @@ private: JSObject* wrapper = &JS_CALLEE(aCx, aVp).toObject(); JS_ASSERT(JS_ObjectIsFunction(aCx, wrapper)); - JS::Rooted scope(aCx, js::GetFunctionNativeReserved(wrapper, SLOT_wrappedScope)); - JS::Rooted listener(aCx, js::GetFunctionNativeReserved(wrapper, SLOT_wrappedFunction)); + JS::Rooted scope(aCx, + js::GetFunctionNativeReserved(wrapper, SLOT_wrappedScope)); + JS::Rooted listener(aCx, + js::GetFunctionNativeReserved(wrapper, SLOT_wrappedFunction)); JS_ASSERT(scope.isObject()); @@ -319,7 +322,8 @@ private: } if (JSVAL_IS_BOOLEAN(rval) && JSVAL_TO_BOOLEAN(rval) && - !JS_CallFunctionName(aCx, event, "preventDefault", 0, NULL, rval.address())) { + !JS_CallFunctionName(aCx, event, "preventDefault", 0, NULL, + rval.address())) { return false; } @@ -334,9 +338,9 @@ private: MOZ_ASSERT(scope); ErrorResult rv; + nsRefPtr adaptor = scope->GetEventListener(NS_ConvertASCIItoUTF16(name + 2), rv); - if (rv.Failed()) { JS_ReportError(aCx, "Failed to get event listener!"); return false; @@ -445,7 +449,7 @@ private: static bool Close(JSContext* aCx, unsigned aArgc, jsval* aVp) { - JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + JS::Rooted obj(aCx, JS_THIS_OBJECT(aCx, aVp)); if (!obj) { return false; } @@ -466,7 +470,7 @@ private: static bool ImportScripts(JSContext* aCx, unsigned aArgc, jsval* aVp) { - JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + JS::Rooted obj(aCx, JS_THIS_OBJECT(aCx, aVp)); if (!obj) { return false; } @@ -487,7 +491,7 @@ private: static bool SetTimeout(JSContext* aCx, unsigned aArgc, jsval* aVp) { - JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + JS::Rooted obj(aCx, JS_THIS_OBJECT(aCx, aVp)); if (!obj) { return false; } @@ -498,7 +502,8 @@ private: } JS::Rooted dummy(aCx); - if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", dummy.address())) { + if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", + dummy.address())) { return false; } @@ -508,7 +513,7 @@ private: static bool ClearTimeout(JSContext* aCx, unsigned aArgc, jsval* aVp) { - JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + JS::Rooted obj(aCx, JS_THIS_OBJECT(aCx, aVp)); if (!obj) { return false; } @@ -534,7 +539,7 @@ private: static bool SetInterval(JSContext* aCx, unsigned aArgc, jsval* aVp) { - JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + JS::Rooted obj(aCx, JS_THIS_OBJECT(aCx, aVp)); if (!obj) { return false; } @@ -545,7 +550,8 @@ private: } JS::Rooted dummy(aCx); - if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", dummy.address())) { + if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", + dummy.address())) { return false; } @@ -555,7 +561,7 @@ private: static bool ClearInterval(JSContext* aCx, unsigned aArgc, jsval* aVp) { - JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + JS::Rooted obj(aCx, JS_THIS_OBJECT(aCx, aVp)); if (!obj) { return false; } @@ -581,7 +587,7 @@ private: static bool Dump(JSContext* aCx, unsigned aArgc, jsval* aVp) { - JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + JS::Rooted obj(aCx, JS_THIS_OBJECT(aCx, aVp)); if (!obj) { return false; } @@ -615,7 +621,7 @@ private: static bool AtoB(JSContext* aCx, unsigned aArgc, jsval* aVp) { - JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + JS::Rooted obj(aCx, JS_THIS_OBJECT(aCx, aVp)); if (!obj) { return false; } @@ -625,7 +631,8 @@ private: } JS::Rooted string(aCx); - if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", string.address())) { + if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", + string.address())) { return false; } @@ -641,7 +648,7 @@ private: static bool BtoA(JSContext* aCx, unsigned aArgc, jsval* aVp) { - JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + JS::Rooted obj(aCx, JS_THIS_OBJECT(aCx, aVp)); if (!obj) { return false; } @@ -651,7 +658,8 @@ private: } JS::Rooted binary(aCx); - if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", binary.address())) { + if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", + binary.address())) { return false; } @@ -676,8 +684,8 @@ NS_INTERFACE_MAP_END const JSClass WorkerGlobalScope::sClass = { "WorkerGlobalScope", 0, - JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, - JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub + JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, + JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub }; const JSPropertySpec WorkerGlobalScope::sProperties[] = { @@ -747,12 +755,13 @@ public: static JSObject* InitClass(JSContext* aCx, JSObject* aObj, JSObject* aParentProto) { - JSObject* proto = + JS::Rooted proto(aCx, JS_InitClass(aCx, aObj, aParentProto, ProtoClass(), Construct, 0, - sProperties, sFunctions, NULL, NULL); + sProperties, sFunctions, NULL, NULL)); if (proto) { + void* domClass = const_cast(DOMClassStruct()); js::SetReservedSlot(proto, DOM_PROTO_INSTANCE_CLASS_SLOT, - JS::PrivateValue(const_cast(DOMClassStruct()))); + JS::PrivateValue(domClass)); } return proto; } @@ -807,9 +816,9 @@ private: MOZ_ASSERT(scope); ErrorResult rv; + nsRefPtr handler = scope->GetEventListener(NS_ConvertASCIItoUTF16(name + 2), rv); - if (rv.Failed()) { JS_ReportError(aCx, "Failed to get event listener!"); return false; @@ -852,8 +861,7 @@ private: } else { handler = nullptr; } - scope->SetEventListener(NS_ConvertASCIItoUTF16(name + 2), - handler, rv); + scope->SetEventListener(NS_ConvertASCIItoUTF16(name + 2), handler, rv); if (rv.Failed()) { JS_ReportError(aCx, "Failed to set event listener!"); @@ -894,8 +902,8 @@ private: } static bool - Resolve(JSContext* aCx, JS::Handle aObj, JS::Handle aId, unsigned aFlags, - JS::MutableHandle aObjp) + Resolve(JSContext* aCx, JS::Handle aObj, JS::Handle aId, + unsigned aFlags, JS::MutableHandle aObjp) { bool resolved; if (!JS_ResolveStandardClass(aCx, aObj, aId, &resolved)) { @@ -933,7 +941,7 @@ private: static bool PostMessage(JSContext* aCx, unsigned aArgc, jsval* aVp) { - JSObject* obj = JS_THIS_OBJECT(aCx, aVp); + JS::Rooted obj(aCx, JS_THIS_OBJECT(aCx, aVp)); if (!obj) { return false; } @@ -967,9 +975,10 @@ const DOMJSClass DedicatedWorkerGlobalScope::sClass = { "DedicatedWorkerGlobalScope", JSCLASS_DOM_GLOBAL | JSCLASS_IS_DOMJSCLASS | JSCLASS_IMPLEMENTS_BARRIERS | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS) | JSCLASS_NEW_RESOLVE, - JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, - JS_EnumerateStub, reinterpret_cast(Resolve), JS_ConvertStub, - Finalize, NULL, NULL, NULL, NULL, Trace + JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, + JS_StrictPropertyStub, JS_EnumerateStub, + reinterpret_cast(Resolve), JS_ConvertStub, Finalize, nullptr, + nullptr, nullptr, nullptr, Trace }, { INTERFACE_CHAIN_1(prototypes::id::EventTarget_workers), @@ -1023,29 +1032,354 @@ const char* const DedicatedWorkerGlobalScope::sEventStrings[STRING_COUNT] = { "onmessage", }; +class SharedWorkerGlobalScope : public WorkerGlobalScope +{ + static DOMJSClass sClass; + static DOMIfaceAndProtoJSClass sProtoClass; + static const JSPropertySpec sProperties[]; + + enum + { + STRING_onconnect = 0, + + STRING_COUNT + }; + + static const char* const sEventStrings[STRING_COUNT]; + +public: + static const JSClass* + Class() + { + return sClass.ToJSClass(); + } + + static const JSClass* + ProtoClass() + { + return sProtoClass.ToJSClass(); + } + + static const DOMClass* + DOMClassStruct() + { + return &sClass.mClass; + } + + static JSObject* + InitClass(JSContext* aCx, JSObject* aObj, JSObject* aParentProto) + { + JS::Rooted proto(aCx, + JS_InitClass(aCx, aObj, aParentProto, ProtoClass(), Construct, 0, + sProperties, nullptr, nullptr, nullptr)); + if (proto) { + void* domClass = const_cast(DOMClassStruct()); + js::SetReservedSlot(proto, DOM_PROTO_INSTANCE_CLASS_SLOT, + JS::PrivateValue(domClass)); + } + return proto; + } + + static bool + InitPrivate(JSContext* aCx, JSObject* aObj, WorkerPrivate* aWorkerPrivate) + { + MOZ_ASSERT(JS_GetClass(aObj) == Class()); + + dom::AllocateProtoAndIfaceCache(aObj); + + nsRefPtr scope = + new SharedWorkerGlobalScope(aCx, aWorkerPrivate); + + js::SetReservedSlot(aObj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL(scope)); + + scope->SetIsDOMBinding(); + scope->SetWrapper(aObj); + + scope.forget(); + return true; + } + +protected: + SharedWorkerGlobalScope(JSContext* aCx, WorkerPrivate* aWorker) + : WorkerGlobalScope(aCx, aWorker) + { + MOZ_COUNT_CTOR(mozilla::dom::workers::SharedWorkerGlobalScope); + } + + ~SharedWorkerGlobalScope() + { + MOZ_COUNT_DTOR(mozilla::dom::workers::SharedWorkerGlobalScope); + } + +private: + using EventTarget::GetEventListener; + using EventTarget::SetEventListener; + + static bool + IsSharedWorkerGlobalScope(JS::Handle aVal) + { + return aVal.isObject() && JS_GetClass(&aVal.toObject()) == Class(); + } + + static bool + GetOnconnectImpl(JSContext* aCx, JS::CallArgs aArgs) + { + auto name = sEventStrings[STRING_onconnect]; + + auto scope = GetInstancePrivate(aCx, &aArgs.thisv().toObject(), name); + MOZ_ASSERT(scope); + + ErrorResult rv; + + nsRefPtr handler = + scope->GetEventListener(NS_ConvertASCIItoUTF16(name + 2), rv); + if (rv.Failed()) { + JS_ReportError(aCx, "Failed to get event listener!"); + return false; + } + + if (!handler) { + aArgs.rval().setNull(); + } else { + aArgs.rval().setObject(*handler->Callable()); + } + + return true; + } + + static bool + GetOnconnect(JSContext* aCx, unsigned aArgc, JS::Value* aVp) + { + auto args = JS::CallArgsFromVp(aArgc, aVp); + return JS::CallNonGenericMethod(aCx, args); + } + + static bool + SetOnconnectImpl(JSContext* aCx, JS::CallArgs aArgs) + { + auto name = sEventStrings[STRING_onconnect]; + + auto scope = GetInstancePrivate(aCx, &aArgs.thisv().toObject(), name); + MOZ_ASSERT(scope); + + if (aArgs.length() == 0 || !aArgs[0].isObject()) { + JS_ReportError(aCx, "Not an event listener!"); + return false; + } + + + ErrorResult rv; + + JS::Rooted listenerObj(aCx, aArgs[0].toObjectOrNull()); + nsRefPtr handler; + if (listenerObj && JS_ObjectIsCallable(aCx, listenerObj)) { + handler = new EventHandlerNonNull(listenerObj); + } else { + handler = nullptr; + } + scope->SetEventListener(NS_ConvertASCIItoUTF16(name + 2), handler, rv); + + if (rv.Failed()) { + JS_ReportError(aCx, "Failed to set event listener!"); + return false; + } + + aArgs.rval().setUndefined(); + return true; + } + + static bool + SetOnconnect(JSContext* aCx, unsigned aArgc, JS::Value* aVp) + { + auto args = JS::CallArgsFromVp(aArgc, aVp); + return JS::CallNonGenericMethod(aCx, args); + } + + static bool + GetNameImpl(JSContext* aCx, JS::CallArgs aArgs) + { + auto scope = GetInstancePrivate(aCx, &aArgs.thisv().toObject(), "name"); + MOZ_ASSERT(scope); + + auto name = scope->mWorker->SharedWorkerName(); + MOZ_ASSERT(!name.IsVoid()); + + JS::Rooted nameStr(aCx, + JS_InternUCStringN(aCx, name.get(), name.Length())); + if (!nameStr) { + return false; + } + + aArgs.rval().setString(nameStr); + return true; + } + + static bool + GetName(JSContext* aCx, unsigned aArgc, JS::Value* aVp) + { + auto args = JS::CallArgsFromVp(aArgc, aVp); + return JS::CallNonGenericMethod(aCx, args); + } + + + static SharedWorkerGlobalScope* + GetInstancePrivate(JSContext* aCx, JSObject* aObj, const char* aFunctionName) + { + const JSClass* classPtr = JS_GetClass(aObj); + if (classPtr == Class()) { + return UnwrapDOMObject(aObj); + } + + JS_ReportErrorNumber(aCx, js_GetErrorMessage, nullptr, + JSMSG_INCOMPATIBLE_PROTO, Class()->name, aFunctionName, + classPtr->name); + return nullptr; + } + + static bool + Construct(JSContext* aCx, unsigned aArgc, jsval* aVp) + { + JS_ReportErrorNumber(aCx, js_GetErrorMessage, nullptr, + JSMSG_WRONG_CONSTRUCTOR, Class()->name); + return false; + } + + static bool + Resolve(JSContext* aCx, JS::Handle aObj, JS::Handle aId, + unsigned aFlags, JS::MutableHandle aObjp) + { + bool resolved; + if (!JS_ResolveStandardClass(aCx, aObj, aId, &resolved)) { + return false; + } + + aObjp.set(resolved ? aObj.get() : nullptr); + return true; + } + + static void + Finalize(JSFreeOp* aFop, JSObject* aObj) + { + MOZ_ASSERT(JS_GetClass(aObj) == Class()); + SharedWorkerGlobalScope* scope = + UnwrapDOMObject(aObj); + if (scope) { + DestroyProtoAndIfaceCache(aObj); + scope->_finalize(aFop); + } + } + + static void + Trace(JSTracer* aTrc, JSObject* aObj) + { + MOZ_ASSERT(JS_GetClass(aObj) == Class()); + SharedWorkerGlobalScope* scope = + UnwrapDOMObject(aObj); + if (scope) { + TraceProtoAndIfaceCache(aTrc, aObj); + scope->_trace(aTrc); + } + } +}; + +DOMJSClass SharedWorkerGlobalScope::sClass = { + { + // We don't have to worry about Xray expando slots here because we'll never + // have an Xray wrapper to a worker global scope. + "SharedWorkerGlobalScope", + JSCLASS_DOM_GLOBAL | JSCLASS_IS_DOMJSCLASS | JSCLASS_IMPLEMENTS_BARRIERS | + JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS) | JSCLASS_NEW_RESOLVE, + JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, + JS_StrictPropertyStub, JS_EnumerateStub, + reinterpret_cast(Resolve), JS_ConvertStub, Finalize, nullptr, + nullptr, nullptr, nullptr, Trace + }, + { + INTERFACE_CHAIN_1(prototypes::id::EventTarget_workers), + false, + &sWorkerNativePropertyHooks + } +}; + +DOMIfaceAndProtoJSClass SharedWorkerGlobalScope::sProtoClass = { + { + // XXXbz we use "SharedWorkerGlobalScope" here to match sClass + // so that we can JS_InitClass this JSClass and then + // call JS_NewObject with our sClass and have it find the right + // prototype. + "SharedWorkerGlobalScope", + JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(2), + JS_PropertyStub, /* addProperty */ + JS_DeletePropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + nullptr, /* finalize */ + nullptr, /* checkAccess */ + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + nullptr, /* trace */ + JSCLASS_NO_INTERNAL_MEMBERS + }, + eInterfacePrototype, + &sWorkerNativePropertyHooks, + "[object SharedWorkerGlobalScope]", + prototypes::id::_ID_Count, + 0 +}; + +const JSPropertySpec SharedWorkerGlobalScope::sProperties[] = { + JS_PSGS(sEventStrings[STRING_onconnect], GetOnconnect, SetOnconnect, + JSPROP_ENUMERATE), + JS_PSGS("name", GetName, GetterOnlyJSNative, JSPROP_ENUMERATE), + JS_PS_END +}; + +const char* const SharedWorkerGlobalScope::sEventStrings[STRING_COUNT] = { + "onconnect", +}; + WorkerGlobalScope* WorkerGlobalScope::GetInstancePrivate(JSContext* aCx, JSObject* aObj, const char* aFunctionName) { const JSClass* classPtr = JS_GetClass(aObj); - // We can only make DedicatedWorkerGlobalScope, not WorkerGlobalScope, so this - // should never happen. - JS_ASSERT(classPtr != Class()); + // We can only make [Dedicated|Shared]WorkerGlobalScope, not + // WorkerGlobalScope, so this should never happen. + MOZ_ASSERT(classPtr != Class()); if (classPtr == DedicatedWorkerGlobalScope::Class()) { return UnwrapDOMObject(aObj); } - JS_ReportErrorNumber(aCx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO, - sClass.name, aFunctionName, classPtr->name); - return NULL; + if (classPtr == SharedWorkerGlobalScope::Class()) { + return UnwrapDOMObject(aObj); + } + + JS_ReportErrorNumber(aCx, js_GetErrorMessage, nullptr, + JSMSG_INCOMPATIBLE_PROTO, sClass.name, aFunctionName, + classPtr->name); + return nullptr; } bool -WorkerGlobalScope::IsWorkerGlobalScope(JS::Handle v) +WorkerGlobalScope::IsWorkerGlobalScope(JS::Handle aVal) { - return v.isObject() && JS_GetClass(&v.toObject()) == DedicatedWorkerGlobalScope::Class(); + if (!aVal.isObject()) { + return false; + } + + auto classPtr = JS_GetClass(&aVal.toObject()); + + return classPtr == DedicatedWorkerGlobalScope::Class() || + classPtr == SharedWorkerGlobalScope::Class(); } } /* anonymous namespace */ @@ -1053,32 +1387,42 @@ WorkerGlobalScope::IsWorkerGlobalScope(JS::Handle v) BEGIN_WORKERS_NAMESPACE JSObject* -CreateDedicatedWorkerGlobalScope(JSContext* aCx) +CreateGlobalScope(JSContext* aCx) { using namespace mozilla::dom; WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); - JS_ASSERT(worker); + MOZ_ASSERT(worker); + + const JSClass* classPtr = worker->IsSharedWorker() ? + SharedWorkerGlobalScope::Class() : + DedicatedWorkerGlobalScope::Class(); JS::CompartmentOptions options; - if (worker->IsChromeWorker()) + if (worker->IsChromeWorker()) { options.setVersion(JSVERSION_LATEST); + } + JS::Rooted global(aCx, - JS_NewGlobalObject(aCx, DedicatedWorkerGlobalScope::Class(), - GetWorkerPrincipal(), JS::DontFireOnNewGlobalHook, options)); + JS_NewGlobalObject(aCx, classPtr, GetWorkerPrincipal(), + JS::DontFireOnNewGlobalHook, options)); if (!global) { - return NULL; + return nullptr; } JSAutoCompartment ac(aCx, global); // Make the private slots now so that all our instance checks succeed. - if (!DedicatedWorkerGlobalScope::InitPrivate(aCx, global, worker)) { - return NULL; + if (worker->IsSharedWorker()) { + if (!SharedWorkerGlobalScope::InitPrivate(aCx, global, worker)) { + return nullptr; + } + } else if (!DedicatedWorkerGlobalScope::InitPrivate(aCx, global, worker)) { + return nullptr; } // Proto chain should be: - // global -> DedicatedWorkerGlobalScope + // global -> [Dedicated|Shared]WorkerGlobalScope // -> WorkerGlobalScope // -> EventTarget // -> Object @@ -1086,43 +1430,45 @@ CreateDedicatedWorkerGlobalScope(JSContext* aCx) JS::Rooted eventTargetProto(aCx, EventTargetBinding_workers::GetProtoObject(aCx, global)); if (!eventTargetProto) { - return NULL; + return nullptr; } JS::Rooted scopeProto(aCx, WorkerGlobalScope::InitClass(aCx, global, eventTargetProto)); if (!scopeProto) { - return NULL; + return nullptr; } - JS::Rooted dedicatedScopeProto(aCx, + JS::Rooted finalScopeProto(aCx, + worker->IsSharedWorker() ? + SharedWorkerGlobalScope::InitClass(aCx, global, scopeProto) : DedicatedWorkerGlobalScope::InitClass(aCx, global, scopeProto)); - if (!dedicatedScopeProto) { - return NULL; + if (!finalScopeProto) { + return nullptr; } - if (!JS_SetPrototype(aCx, global, dedicatedScopeProto)) { - return NULL; + if (!JS_SetPrototype(aCx, global, finalScopeProto)) { + return nullptr; } - JSObject* workerProto = worker::InitClass(aCx, global, eventTargetProto, - false); + JS::Rooted workerProto(aCx, + worker::InitClass(aCx, global, eventTargetProto, false)); if (!workerProto) { - return NULL; + return nullptr; } if (worker->IsChromeWorker()) { if (!chromeworker::InitClass(aCx, global, workerProto, false) || !DefineChromeWorkerFunctions(aCx, global) || !DefineOSFileConstants(aCx, global)) { - return NULL; + return nullptr; } } // Init other classes we care about. if (!events::InitClasses(aCx, global, false) || !file::InitClasses(aCx, global)) { - return NULL; + return nullptr; } // Init other paris-bindings. @@ -1137,11 +1483,11 @@ CreateDedicatedWorkerGlobalScope(JSContext* aCx) !URLBinding_workers::GetConstructorObject(aCx, global) || !WorkerLocationBinding_workers::GetConstructorObject(aCx, global) || !WorkerNavigatorBinding_workers::GetConstructorObject(aCx, global)) { - return NULL; + return nullptr; } if (!JS_DefineProfilingFunctions(aCx, global)) { - return NULL; + return nullptr; } JS_FireOnNewGlobalObject(aCx, global); @@ -1149,11 +1495,4 @@ CreateDedicatedWorkerGlobalScope(JSContext* aCx) return global; } -bool -ClassIsWorkerGlobalScope(const JSClass* aClass) -{ - return WorkerGlobalScope::Class() == aClass || - DedicatedWorkerGlobalScope::Class() == aClass; -} - END_WORKERS_NAMESPACE diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h index 9adf0ef55ce8..ba2e49958056 100644 --- a/dom/workers/WorkerScope.h +++ b/dom/workers/WorkerScope.h @@ -11,11 +11,8 @@ BEGIN_WORKERS_NAMESPACE JSObject* -CreateDedicatedWorkerGlobalScope(JSContext* aCx); - -bool -ClassIsWorkerGlobalScope(const JSClass* aClass); +CreateGlobalScope(JSContext* aCx); END_WORKERS_NAMESPACE -#endif /* mozilla_dom_workers_workerscope_h__ */ +#endif /* mozilla_dom_workers_workerscope_h__ */ \ No newline at end of file diff --git a/dom/workers/Workers.h b/dom/workers/Workers.h index b64e4cef1700..6f4bab937e45 100644 --- a/dom/workers/Workers.h +++ b/dom/workers/Workers.h @@ -168,13 +168,13 @@ ResolveWorkerClasses(JSContext* aCx, JS::Handle aObj, JS::Handle aObjp); void -CancelWorkersForWindow(JSContext* aCx, nsPIDOMWindow* aWindow); +CancelWorkersForWindow(nsPIDOMWindow* aWindow); void -SuspendWorkersForWindow(JSContext* aCx, nsPIDOMWindow* aWindow); +SuspendWorkersForWindow(nsPIDOMWindow* aWindow); void -ResumeWorkersForWindow(nsIScriptContext* aCx, nsPIDOMWindow* aWindow); +ResumeWorkersForWindow(nsPIDOMWindow* aWindow); class WorkerTask { public: diff --git a/dom/workers/moz.build b/dom/workers/moz.build index fb451be25fec..d796293d1dc7 100644 --- a/dom/workers/moz.build +++ b/dom/workers/moz.build @@ -20,9 +20,12 @@ EXPORTS.mozilla.dom.workers.bindings += [ 'EventTarget.h', 'FileReaderSync.h', 'Location.h', + 'MessagePort.h', 'Navigator.h', + 'SharedWorker.h', 'URL.h', 'WorkerFeature.h', + 'WorkerMessagePort.h', 'XMLHttpRequest.h', 'XMLHttpRequestEventTarget.h', 'XMLHttpRequestUpload.h', @@ -37,12 +40,15 @@ CPP_SOURCES += [ 'File.cpp', 'FileReaderSync.cpp', 'Location.cpp', + 'MessagePort.cpp', 'Navigator.cpp', 'Principal.cpp', 'RuntimeService.cpp', 'ScriptLoader.cpp', + 'SharedWorker.cpp', 'URL.cpp', 'Worker.cpp', + 'WorkerMessagePort.cpp', 'WorkerPrivate.cpp', 'WorkerScope.cpp', 'XMLHttpRequest.cpp', @@ -64,4 +70,4 @@ LOCAL_INCLUDES += [ '/content/base/src', '/content/events/src', '/xpcom/build', -] +] \ No newline at end of file diff --git a/dom/workers/test/Makefile.in b/dom/workers/test/Makefile.in index cb4b35cd20d0..64cd5f12d810 100644 --- a/dom/workers/test/Makefile.in +++ b/dom/workers/test/Makefile.in @@ -8,4 +8,3 @@ MOCHITEST_FILES += \ test_xhr_timeout.html \ $(NULL) endif - diff --git a/dom/workers/test/mochitest.ini b/dom/workers/test/mochitest.ini index a69d0c362a8f..444a8d011186 100644 --- a/dom/workers/test/mochitest.ini +++ b/dom/workers/test/mochitest.ini @@ -22,6 +22,8 @@ support-files = json_worker.js location_worker.js longThread_worker.js + multi_sharedWorker_frame.html + multi_sharedWorker_sharedWorker.js navigator_worker.js newError_worker.js recursion_worker.js @@ -30,6 +32,7 @@ support-files = relativeLoad_worker.js relativeLoad_worker2.js rvals_worker.js + sharedWorker_sharedWorker.js simpleThread_worker.js suspend_iframe.html suspend_worker.js @@ -73,6 +76,8 @@ support-files = [test_loadError.html] [test_location.html] [test_longThread.html] +[test_multi_sharedWorker.html] +[test_multi_sharedWorker_lifetimes.html] [test_navigator.html] [test_newError.html] [test_recursion.html] @@ -81,6 +86,7 @@ support-files = [test_resolveWorker-assignment.html] [test_resolveWorker.html] [test_rvals.html] +[test_sharedWorker.html] [test_simpleThread.html] [test_suspend.html] [test_terminate.html] diff --git a/dom/workers/test/multi_sharedWorker_frame.html b/dom/workers/test/multi_sharedWorker_frame.html new file mode 100644 index 000000000000..c09c3706e3de --- /dev/null +++ b/dom/workers/test/multi_sharedWorker_frame.html @@ -0,0 +1,52 @@ + + + + + Test for SharedWorker + + + + + diff --git a/dom/workers/test/multi_sharedWorker_sharedWorker.js b/dom/workers/test/multi_sharedWorker_sharedWorker.js new file mode 100644 index 000000000000..47a7ae04f1d1 --- /dev/null +++ b/dom/workers/test/multi_sharedWorker_sharedWorker.js @@ -0,0 +1,72 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +if (self.name != "FrameWorker") { + throw new Error("Bad worker name: " + self.name); +} + +var registeredPorts = []; +var errorCount = 0; +var storedData; + +self.onconnect = function(event) { + var port = event.ports[0]; + + if (registeredPorts.length) { + var data = { + type: "connect" + }; + + registeredPorts.forEach(function(registeredPort) { + registeredPort.postMessage(data); + }); + } + + port.onmessage = function(event) { + switch (event.data.command) { + case "start": + break; + + case "error": + throw new Error("Expected"); + + case "store": + storedData = event.data.data; + break; + + case "retrieve": + var data = { + type: "result", + data: storedData + }; + port.postMessage(data); + break; + + default: + throw new Error("Unknown command '" + error.data.command + "'"); + } + }; + + registeredPorts.push(port); +}; + +self.onerror = function(message, filename, lineno) { + if (!errorCount++) { + var data = { + type: "worker-error", + message: message, + filename: filename, + lineno: lineno + }; + + registeredPorts.forEach(function (registeredPort) { + registeredPort.postMessage(data); + }); + + // Prevent the error from propagating the first time only. + return true; + } +}; diff --git a/dom/workers/test/sharedWorker_sharedWorker.js b/dom/workers/test/sharedWorker_sharedWorker.js new file mode 100644 index 000000000000..67b2ddcdc09a --- /dev/null +++ b/dom/workers/test/sharedWorker_sharedWorker.js @@ -0,0 +1,82 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +if (!("self" in this)) { + throw new Error("No 'self' exists on SharedWorkerGlobalScope!"); +} +if (this !== self) { + throw new Error("'self' not equal to global object!"); +} +if (!(self instanceof SharedWorkerGlobalScope)) { + throw new Error("self not a SharedWorkerGlobalScope instance!"); +} + +var propsToCheck = [ + "location", + "navigator", + "close", + "importScripts", + "setTimeout", + "clearTimeout", + "setInterval", + "clearInterval", + "dump", + "atob", + "btoa" +]; + +for (var index = 0; index < propsToCheck.length; index++) { + var prop = propsToCheck[index]; + if (!(prop in self)) { + throw new Error("SharedWorkerGlobalScope has no '" + prop + "' property!"); + } +} + +onconnect = function(event) { + if (!(event instanceof WorkerMessageEvent)) { + throw new Error("'connect' event is not a WorkerMessageEvent!"); + } + if (!("ports" in event)) { + throw new Error("'connect' event doesn't have a 'ports' property!"); + } + if (!Array.isArray(event.ports)) { + throw new Error("'connect' event has 'ports' property that isn't an " + + "Array!"); + } + if (event.ports.length != 1) { + throw new Error("'connect' event has a 'ports' property with length '" + + event.ports.length + "'!"); + } + if (!event.ports[0]) { + throw new Error("'connect' event has a null 'ports[0]' property!"); + } + if (!(event.ports[0] instanceof WorkerMessagePort)) { + throw new Error("'connect' event has a 'ports[0]' property that isn't a " + + "MessagePort!"); + } + if (event.data) { + throw new Error("'connect' event has data: " + event.data); + } + + event.ports[0].onmessage = function(event) { + if (!(event instanceof WorkerMessageEvent)) { + throw new Error("'message' event is not a WorkerMessageEvent!"); + } + if (!("ports" in event)) { + throw new Error("'message' event doesn't have a 'ports' property!"); + } + if (!Array.isArray(event.ports)) { + throw new Error("'message' event has 'ports' property that isn't an " + + "Array!"); + } + if (event.ports.length) { + throw new Error("'message' event has a 'ports' property with length '" + + event.ports.length + "'!"); + } + event.target.postMessage(event.data); + throw new Error(event.data); + }; +}; diff --git a/dom/workers/test/test_multi_sharedWorker.html b/dom/workers/test/test_multi_sharedWorker.html new file mode 100644 index 000000000000..6c5f4df3fa97 --- /dev/null +++ b/dom/workers/test/test_multi_sharedWorker.html @@ -0,0 +1,251 @@ + + + + + Test for SharedWorker + + + + + + + + + diff --git a/dom/workers/test/test_multi_sharedWorker_lifetimes.html b/dom/workers/test/test_multi_sharedWorker_lifetimes.html new file mode 100644 index 000000000000..26ac222c0ce7 --- /dev/null +++ b/dom/workers/test/test_multi_sharedWorker_lifetimes.html @@ -0,0 +1,151 @@ + + + + + Test for SharedWorker + + + + + + + + diff --git a/dom/workers/test/test_sharedWorker.html b/dom/workers/test/test_sharedWorker.html new file mode 100644 index 000000000000..c5a980759c61 --- /dev/null +++ b/dom/workers/test/test_sharedWorker.html @@ -0,0 +1,76 @@ + + + + + Test for SharedWorker + + + + +

+ +
+      
+    
+ + diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 23cdf49aac2b..e739b1009c40 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -103,6 +103,9 @@ pref("dom.workers.enabled", true); // The number of workers per domain allowed to run concurrently. pref("dom.workers.maxPerDomain", 20); +// Whether or not Shared Web Workers are enabled. +pref("dom.workers.sharedWorkers.enabled", false); + // Whether nonzero values can be returned from performance.timing.* pref("dom.enable_performance", true);