diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp index b8d7edc930..354e92193b 100644 --- a/dom/base/FragmentOrElement.cpp +++ b/dom/base/FragmentOrElement.cpp @@ -824,6 +824,10 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor) aVisitor.mCanHandle = true; aVisitor.mMayHaveListenerManager = HasListenerManager(); + if (IsInShadowTree()) { + aVisitor.mItemInShadowTree = true; + } + // Don't propagate mouseover and mouseout events when mouse is moving // inside chrome access only content. bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree(); diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index de3beb2938..50cc781efc 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -47,6 +47,7 @@ #include "mozilla/dom/HTMLTemplateElement.h" #include "mozilla/dom/ipc/BlobChild.h" #include "mozilla/dom/ipc/BlobParent.h" +#include "mozilla/dom/MessagePort.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/TabParent.h" @@ -9993,3 +9994,71 @@ nsContentUtils::GetClosestNonNativeAnonymousAncestor(Element* aElement) } return e; } + +/* static */ nsresult +nsContentUtils::CreateJSValueFromSequenceOfObject(JSContext* aCx, + const Sequence& aTransfer, + JS::MutableHandle aValue) +{ + if (aTransfer.IsEmpty()) { + return NS_OK; + } + + JS::Rooted array(aCx, JS_NewArrayObject(aCx, aTransfer.Length())); + if (!array) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < aTransfer.Length(); ++i) { + JS::Rooted object(aCx, aTransfer[i]); + if (!object) { + continue; + } + + if (NS_WARN_IF(!JS_DefineElement(aCx, array, i, object, + JSPROP_ENUMERATE))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + aValue.setObject(*array); + return NS_OK; +} + +/* static */ +void nsContentUtils::StructuredClone(JSContext* aCx, + nsIGlobalObject* aGlobal, + JS::Handle aValue, + const StructuredSerializeOptions& aOptions, + JS::MutableHandle aRv, + ErrorResult& aError) { + JS::Rooted transferArray(aCx, JS::UndefinedValue()); + aError = nsContentUtils::CreateJSValueFromSequenceOfObject( + aCx, aOptions.mTransfer, &transferArray); + if (NS_WARN_IF(aError.Failed())) { + return; + } + + JS::CloneDataPolicy clonePolicy; + // FIXME: Uncomment once bug 1609990 and bug 1611855 lands. + //clonePolicy.allowIntraClusterClonableSharedObjects(); + //clonePolicy.allowSharedMemoryObjects(); + + StructuredCloneHolder holder(StructuredCloneHolder::CloningSupported, + StructuredCloneHolder::TransferringSupported, + JS::StructuredCloneScope::SameProcessDifferentThread); + holder.Write(aCx, aValue, transferArray, clonePolicy, aError); + if (NS_WARN_IF(aError.Failed())) { + return; + } + + // TODO: Pass clonePolicy. + //holder.Read(this, aCx, aRv, clonePolicy, aError); + holder.Read(aGlobal, aCx, aRv, aError); + if (NS_WARN_IF(aError.Failed())) { + return; + } + + nsTArray> ports = holder.TakeTransferredPorts(); + Unused << ports; +} diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index f2ba5cb490..c0e44b3be4 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -132,6 +132,7 @@ class nsIContentParent; class TabChild; class Selection; class TabParent; +struct StructuredSerializeOptions; } // namespace dom namespace ipc { @@ -2812,6 +2813,23 @@ public: static bool IsCustomElementsEnabled() { return sIsCustomElementsEnabled; } + static nsresult + CreateJSValueFromSequenceOfObject(JSContext* aCx, + const mozilla::dom::Sequence& aTransfer, + JS::MutableHandle aValue); + + /** + * This implements the structured cloning algorithm as described by + * https://html.spec.whatwg.org/#structured-cloning. + */ + static void + StructuredClone(JSContext* aCx, + nsIGlobalObject* aGlobal, + JS::Handle aValue, + const mozilla::dom::StructuredSerializeOptions& aOptions, + JS::MutableHandle aRv, + mozilla::ErrorResult& aError); + private: static bool InitializeEventTable(); diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index b9bb459eba..c5ed38dc27 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -909,7 +909,8 @@ nsPIDOMWindow::nsPIDOMWindow(nsPIDOMWindowOuter *aOuterWindow) mOuterWindow(aOuterWindow), // Make sure no actual window ends up with mWindowID == 0 mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false), - mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false) + mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false), + mEvent(nullptr) {} template @@ -5245,6 +5246,16 @@ nsGlobalWindow::SetOpener(JSContext* aCx, JS::Handle aOpener, SetOpenerWindow(outer, false); } +void +nsGlobalWindow::GetEvent(JSContext* aCx, JS::MutableHandle aRetval) +{ + if (mEvent) { + Unused << nsContentUtils::WrapNative(aCx, mEvent, aRetval); + } else { + aRetval.setUndefined(); + } +} + void nsGlobalWindow::GetStatusOuter(nsAString& aStatus) { @@ -8886,30 +8897,33 @@ nsGlobalWindow::PostMessageMoz(JSContext* aCx, JS::Handle aMessage, } void -nsGlobalWindow::PostMessageMoz(JSContext* aCx, JS::Handle aMessage, +nsGlobalWindow::PostMessageMoz(JSContext* aCx, + JS::Handle aMessage, const nsAString& aTargetOrigin, - const Optional>& aTransfer, + const Sequence& aTransfer, nsIPrincipal& aSubjectPrincipal, - ErrorResult& aError) + ErrorResult& aRv) { JS::Rooted transferArray(aCx, JS::UndefinedValue()); - if (aTransfer.WasPassed()) { - const Sequence& values = aTransfer.Value(); - - // The input sequence only comes from the generated bindings code, which - // ensures it is rooted. - JS::HandleValueArray elements = - JS::HandleValueArray::fromMarkedLocation(values.Length(), values.Elements()); - - transferArray = JS::ObjectOrNullValue(JS_NewArrayObject(aCx, elements)); - if (transferArray.isNull()) { - aError.Throw(NS_ERROR_OUT_OF_MEMORY); - return; - } + aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransfer, + &transferArray); + if (NS_WARN_IF(aRv.Failed())) { + return; } PostMessageMoz(aCx, aMessage, aTargetOrigin, transferArray, - aSubjectPrincipal, aError); + aSubjectPrincipal, aRv); +} + +void +nsGlobalWindow::PostMessageMoz(JSContext* aCx, + JS::Handle aMessage, + const WindowPostMessageOptions& aOptions, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + PostMessageMoz(aCx, aMessage, aOptions.mTargetOrigin, aOptions.mTransfer, + aSubjectPrincipal, aRv); } class nsCloseEvent : public Runnable { @@ -14641,6 +14655,17 @@ nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage, } } +// https://html.spec.whatwg.org/#structured-cloning +void +nsGlobalWindow::StructuredClone(JSContext* aCx, + JS::Handle aValue, + const StructuredSerializeOptions& aOptions, + JS::MutableHandle aRv, + ErrorResult& aError) +{ + nsContentUtils::StructuredClone(aCx, this, aValue, aOptions, aRv, aError); +} + // Helper called by methods that move/resize the window, // to ensure the presContext (if any) is aware of resolution // change that may happen in multi-monitor configuration. diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index e42835a0b2..63bb574dd4 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -135,6 +135,7 @@ class TabGroup; class Timeout; class U2F; class WakeLock; +struct WindowPostMessageOptions; class Worklet; namespace cache { class CacheStorage; @@ -845,6 +846,7 @@ public: already_AddRefed GetOpener() override; void SetOpener(JSContext* aCx, JS::Handle aOpener, mozilla::ErrorResult& aError); + void GetEvent(JSContext* aCx, JS::MutableHandle aRetval); already_AddRefed GetParentOuter(); already_AddRefed GetParent(mozilla::ErrorResult& aError); already_AddRefed GetParent() override; @@ -933,7 +935,12 @@ public: void Print(mozilla::ErrorResult& aError); void PostMessageMoz(JSContext* aCx, JS::Handle aMessage, const nsAString& aTargetOrigin, - const mozilla::dom::Optional >& aTransfer, + const mozilla::dom::Sequence& aTransfer, + nsIPrincipal& aSubjectPrincipal, + mozilla::ErrorResult& aRv); + void PostMessageMoz(JSContext* aCx, + JS::Handle aMessage, + const mozilla::dom::WindowPostMessageOptions& aOptions, nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& aError); int32_t SetTimeout(JSContext* aCx, mozilla::dom::Function& aFunction, @@ -1177,6 +1184,10 @@ public: const mozilla::dom::Sequence& aLayout, mozilla::ErrorResult& aRv); + void StructuredClone(JSContext* aCx, JS::Handle aValue, + const mozilla::dom::StructuredSerializeOptions& aOptions, + JS::MutableHandle aRv, + mozilla::ErrorResult& aError); // ChromeWindow bits. Do NOT call these unless your window is in // fact an nsGlobalChromeWindow. diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h index 21eb4cff7f..ffebf6570d 100644 --- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -194,6 +194,10 @@ protected: // we have what it takes to do so. void MaybeCreateDoc(); + // The event dispatch code sets and unsets this while keeping + // the event object alive. + nsIDOMEvent* mEvent; + public: inline bool IsLoadingOrRunningTimeout() const; @@ -789,6 +793,17 @@ public: return mInnerObjectsFreed; } + // Sets the event for window.event. Does NOT take ownership, so + // the caller is responsible for clearing the event before the + // event gets deallocated. Pass nullptr to set window.event to + // undefined. Returns the previous value. + nsIDOMEvent* SetEvent(nsIDOMEvent* aEvent) + { + nsIDOMEvent* old = mEvent; + mEvent = aEvent; + return old; + } + /** * Check whether this window is a secure context. */ diff --git a/dom/events/EventDispatcher.cpp b/dom/events/EventDispatcher.cpp index e2b5699241..001b458cf3 100644 --- a/dom/events/EventDispatcher.cpp +++ b/dom/events/EventDispatcher.cpp @@ -264,6 +264,16 @@ public: return mFlags.mRootOfClosedTree; } + void SetItemInShadowTree(bool aSet) + { + mFlags.mItemInShadowTree = aSet; + } + + bool IsItemInShadowTree() + { + return mFlags.mItemInShadowTree; + } + void SetIsSlotInClosedTree(bool aSet) { mFlags.mIsSlotInClosedTree = aSet; @@ -351,7 +361,8 @@ public: mManager->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent, &aVisitor.mDOMEvent, CurrentTarget(), - &aVisitor.mEventStatus); + &aVisitor.mEventStatus, + IsItemInShadowTree()); NS_ASSERTION(aVisitor.mEvent->mCurrentTarget == nullptr, "CurrentTarget should be null!"); } @@ -384,6 +395,7 @@ private: bool mWantsPreHandleEvent : 1; bool mPreHandleEventOnly : 1; bool mRootOfClosedTree : 1; + bool mItemInShadowTree : 1; bool mIsSlotInClosedTree : 1; bool mIsChromeHandler : 1; private: @@ -433,6 +445,7 @@ EventTargetChainItem::GetEventTargetParent(EventChainPreVisitor& aVisitor) SetWantsPreHandleEvent(aVisitor.mWantsPreHandleEvent); SetPreHandleEventOnly(aVisitor.mWantsPreHandleEvent && !aVisitor.mCanHandle); SetRootOfClosedTree(aVisitor.mRootOfClosedTree); + SetItemInShadowTree(aVisitor.mItemInShadowTree); SetRetargetedRelatedTarget(aVisitor.mRetargetedRelatedTarget); mItemFlags = aVisitor.mItemFlags; mItemData = aVisitor.mItemData; diff --git a/dom/events/EventDispatcher.h b/dom/events/EventDispatcher.h index 403c472c5c..9eaa124137 100644 --- a/dom/events/EventDispatcher.h +++ b/dom/events/EventDispatcher.h @@ -125,6 +125,7 @@ public: , mMayHaveListenerManager(true) , mWantsPreHandleEvent(false) , mRootOfClosedTree(false) + , mItemInShadowTree(false) , mParentIsSlotInClosedTree(false) , mParentIsChromeHandler(false) , mRelatedTargetRetargetedInCurrentScope(false) @@ -147,6 +148,7 @@ public: mMayHaveListenerManager = true; mWantsPreHandleEvent = false; mRootOfClosedTree = false; + mItemInShadowTree = false; mParentIsSlotInClosedTree = false; mParentIsChromeHandler = false; // Note, we don't clear mRelatedTargetRetargetedInCurrentScope explicitly, @@ -236,6 +238,12 @@ public: */ bool mRootOfClosedTree; + /** + * If target is node and its root is a shadow root. + * https://dom.spec.whatwg.org/#event-path-item-in-shadow-tree + */ + bool mItemInShadowTree; + /** * True if mParentTarget is HTMLSlotElement in a closed shadow tree and the * current target is assigned to that slot. diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index 5187bf8f30..4ba5ed3386 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -1121,6 +1121,38 @@ EventListenerManager::GetLegacyEventMessage(EventMessage aEventMessage) const } } +already_AddRefed +EventListenerManager::WindowFromListener(Listener* aListener, + bool aItemInShadowTree) +{ + nsCOMPtr innerWindow; + if (!aItemInShadowTree) { + if (aListener->mListener.HasWebIDLCallback()) { + CallbackObject* callback = aListener->mListener.GetWebIDLCallback(); + nsGlobalWindow* win; + if (callback) { + // Find the real underlying callback. + JSObject* realCallback = + js::UncheckedUnwrap(callback->CallbackPreserveColor()); + // Get the global for this callback. + win = mIsMainThreadELM ? + xpc::WindowGlobalOrNull(realCallback) : + nullptr; + } + if (win && win->IsInnerWindow()) { + innerWindow = win->AsInner(); // Can be nullptr + } + } else { + // Can't get the global from + // listener->mListener.GetXPCOMCallback(). + // In most cases, it would be the same as for + // the target, so let's do that. + innerWindow = GetInnerWindowForTarget(); // Can be nullptr + } + } + return innerWindow.forget(); +} + /** * Causes a check for event listeners and processing by them if they exist. * @param an event listener @@ -1131,7 +1163,8 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, WidgetEvent* aEvent, nsIDOMEvent** aDOMEvent, EventTarget* aCurrentTarget, - nsEventStatus* aEventStatus) + nsEventStatus* aEventStatus, + bool aItemInShadowTree) { //Set the value of the internal PreventDefault flag properly based on aEventStatus if (!aEvent->DefaultPrevented() && @@ -1223,9 +1256,18 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, listener = listenerHolder.ptr(); hasRemovedListener = true; } + nsCOMPtr innerWindow = + WindowFromListener(listener, aItemInShadowTree); + nsIDOMEvent* oldWindowEvent = nullptr; + if (innerWindow) { + oldWindowEvent = innerWindow->SetEvent(*aDOMEvent); + } if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent, aCurrentTarget))) { aEvent->mFlags.mExceptionWasRaised = true; } + if (innerWindow) { + Unused << innerWindow->SetEvent(oldWindowEvent); + } aEvent->mFlags.mInPassiveListener = false; if (needsEndEventMarker) { diff --git a/dom/events/EventListenerManager.h b/dom/events/EventListenerManager.h index 51373317d4..decd305f5e 100644 --- a/dom/events/EventListenerManager.h +++ b/dom/events/EventListenerManager.h @@ -350,7 +350,8 @@ public: WidgetEvent* aEvent, nsIDOMEvent** aDOMEvent, dom::EventTarget* aCurrentTarget, - nsEventStatus* aEventStatus) + nsEventStatus* aEventStatus, + bool aItemInShadowTree) { if (mListeners.IsEmpty() || aEvent->PropagationStopped()) { return; @@ -371,7 +372,7 @@ public: return; } HandleEventInternal(aPresContext, aEvent, aDOMEvent, aCurrentTarget, - aEventStatus); + aEventStatus, aItemInShadowTree); } /** @@ -482,7 +483,8 @@ protected: WidgetEvent* aEvent, nsIDOMEvent** aDOMEvent, dom::EventTarget* aCurrentTarget, - nsEventStatus* aEventStatus); + nsEventStatus* aEventStatus, + bool aItemInShadowTree); nsresult HandleEventSubType(Listener* aListener, nsIDOMEvent* aDOMEvent, @@ -573,6 +575,10 @@ public: return typedHandler ? typedHandler->OnBeforeUnloadEventHandler() : nullptr; } +private: + already_AddRefed WindowFromListener(Listener* aListener, + bool aItemInShadowTree); + protected: /** * Helper method for implementing the various Get*EventHandler above. Will diff --git a/dom/messagechannel/MessagePort.cpp b/dom/messagechannel/MessagePort.cpp index 6f7a1abc19..5fbd1e9bb9 100644 --- a/dom/messagechannel/MessagePort.cpp +++ b/dom/messagechannel/MessagePort.cpp @@ -394,52 +394,39 @@ MessagePort::WrapObject(JSContext* aCx, JS::Handle aGivenProto) } void -MessagePort::PostMessage(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, +MessagePort::PostMessage(JSContext* aCx, + JS::Handle aMessage, + const Sequence& aTransferable, ErrorResult& aRv) { // We *must* clone the data here, or the JS::Value could be modified // by script - JS::Rooted transferable(aCx, JS::UndefinedValue()); - if (aTransferable.WasPassed()) { - const Sequence& realTransferable = aTransferable.Value(); - - // Here we want to check if the transerable object list contains - // this port. No other checks are done. - for (const JS::Value& value : realTransferable) { - if (!value.isObject()) { - continue; - } - - JS::Rooted object(aCx, &value.toObject()); - - MessagePort* port = nullptr; - nsresult rv = UNWRAP_OBJECT(MessagePort, &object, port); - if (NS_FAILED(rv)) { - continue; - } - - if (port == this) { - aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); - return; - } + // Here we want to check if the transferable object list contains + // this port. + for (uint32_t i = 0; i < aTransferable.Length(); ++i) { + JSObject* rawObject = aTransferable[i]; + if (!rawObject) { + continue; } - // The input sequence only comes from the generated bindings code, which - // ensures it is rooted. - JS::HandleValueArray elements = - JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(), - realTransferable.Elements()); + JS::Rooted object(aCx, rawObject); - JSObject* array = - JS_NewArrayObject(aCx, elements); - if (!array) { - aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + MessagePort* port = nullptr; + nsresult rv = UNWRAP_OBJECT(MessagePort, &object, port); + if (NS_SUCCEEDED(rv) && port == this) { + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); return; } + } - transferable.setObject(*array); + JS::Rooted transferable(aCx, JS::UndefinedValue()); + + aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, + aTransferable, + &transferable); + if (NS_WARN_IF(aRv.Failed())) { + return; } RefPtr data = new SharedMessagePortMessage(); @@ -508,6 +495,14 @@ MessagePort::PostMessage(JSContext* aCx, JS::Handle aMessage, mActor->SendPostMessages(messages); } +void +MessagePort::PostMessage(JSContext* aCx, JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, + ErrorResult& aRv) +{ + PostMessage(aCx, aMessage, aOptions.mTransfer, aRv); +} + void MessagePort::Start() { diff --git a/dom/messagechannel/MessagePort.h b/dom/messagechannel/MessagePort.h index 0ed2af11c1..b832872e1f 100644 --- a/dom/messagechannel/MessagePort.h +++ b/dom/messagechannel/MessagePort.h @@ -26,6 +26,7 @@ class MessagePortIdentifier; class MessagePortMessage; class PostMessageRunnable; class SharedMessagePortMessage; +struct StructuredSerializeOptions; namespace workers { class WorkerHolder; @@ -62,7 +63,12 @@ public: void PostMessage(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, + ErrorResult& aRv); + + void + PostMessage(JSContext* aCx, JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, ErrorResult& aRv); void Start(); diff --git a/dom/tests/mochitest/whatwg/test_postMessage_origin.xhtml b/dom/tests/mochitest/whatwg/test_postMessage_origin.xhtml index f6a9198968..e48bee136d 100644 --- a/dom/tests/mochitest/whatwg/test_postMessage_origin.xhtml +++ b/dom/tests/mochitest/whatwg/test_postMessage_origin.xhtml @@ -383,13 +383,6 @@ var tests = hasWrongReturnOriginBug: true }, - // 55 - { - args: ["NOT-RECEIVED", undefined], - source: "sameDomain", - name: "SyntaxError", - code: DOMException.SYNTAX_ERR - }, ]; function allTests(callback) diff --git a/dom/webidl/Client.webidl b/dom/webidl/Client.webidl index 3e79c6a635..c961c2b31f 100644 --- a/dom/webidl/Client.webidl +++ b/dom/webidl/Client.webidl @@ -15,7 +15,9 @@ interface Client { readonly attribute DOMString id; [Throws] - void postMessage(any message, optional sequence transfer); + void postMessage(any message, sequence transferable); + [Throws] + void postMessage(any message, optional StructuredSerializeOptions options); }; [Exposed=ServiceWorker] diff --git a/dom/webidl/DedicatedWorkerGlobalScope.webidl b/dom/webidl/DedicatedWorkerGlobalScope.webidl index 26dca58dab..80c7c8decb 100644 --- a/dom/webidl/DedicatedWorkerGlobalScope.webidl +++ b/dom/webidl/DedicatedWorkerGlobalScope.webidl @@ -16,7 +16,9 @@ Exposed=DedicatedWorker] interface DedicatedWorkerGlobalScope : WorkerGlobalScope { [Throws] - void postMessage(any message, optional sequence transfer); + void postMessage(any message, sequence transfer); + [Throws] + void postMessage(any message, optional StructuredSerializeOptions options); attribute EventHandler onmessage; }; diff --git a/dom/webidl/MessagePort.webidl b/dom/webidl/MessagePort.webidl index 59c61a7149..0b36c47d94 100644 --- a/dom/webidl/MessagePort.webidl +++ b/dom/webidl/MessagePort.webidl @@ -10,7 +10,9 @@ [Exposed=(Window,Worker,System)] interface MessagePort : EventTarget { [Throws] - void postMessage(any message, optional sequence transferable); + void postMessage(any message, sequence transferable); + [Throws] + void postMessage(any message, optional StructuredSerializeOptions options); void start(); void close(); @@ -19,3 +21,8 @@ interface MessagePort : EventTarget { attribute EventHandler onmessage; }; // MessagePort implements Transferable; + +// Used to declare which objects should be transferred. +dictionary StructuredSerializeOptions { + sequence transfer = []; +}; \ No newline at end of file diff --git a/dom/webidl/ServiceWorker.webidl b/dom/webidl/ServiceWorker.webidl index 8c3749e940..ff80fafc21 100644 --- a/dom/webidl/ServiceWorker.webidl +++ b/dom/webidl/ServiceWorker.webidl @@ -19,9 +19,10 @@ interface ServiceWorker : EventTarget { attribute EventHandler onstatechange; - // FIXME(catalinb): Should inherit this from Worker. [Throws] - void postMessage(any message, optional sequence transferable); + void postMessage(any message, sequence transferable); + [Throws] + void postMessage(any message, optional StructuredSerializeOptions options); }; ServiceWorker implements AbstractWorker; diff --git a/dom/webidl/Window.webidl b/dom/webidl/Window.webidl index 1b98042473..2ba9f4f124 100644 --- a/dom/webidl/Window.webidl +++ b/dom/webidl/Window.webidl @@ -22,7 +22,6 @@ interface IID; interface nsIBrowserDOMWindow; interface nsIMessageBroadcaster; interface nsIDOMCrypto; -typedef any Transferable; // http://www.whatwg.org/specs/web-apps/current-work/ [PrimaryGlobal, LegacyUnenumerableNamedProperties, NeedResolve] @@ -51,6 +50,7 @@ typedef any Transferable; [Throws] void stop(); [Throws, CrossOriginCallable, UnsafeInPrerendering] void focus(); [Throws, CrossOriginCallable] void blur(); + [Replaceable, Pref="dom.window.event.enabled"] readonly attribute any event; // other browsing contexts [Replaceable, Throws, CrossOriginReadable] readonly attribute WindowProxy frames; @@ -80,7 +80,9 @@ typedef any Transferable; [Throws, UnsafeInPrerendering] void print(); [Throws, CrossOriginCallable, NeedsSubjectPrincipal] - void postMessage(any message, DOMString targetOrigin, optional sequence transfer); + void postMessage(any message, DOMString targetOrigin, optional sequence transfer = []); + [Throws, CrossOriginCallable, NeedsSubjectPrincipal] + void postMessage(any message, optional WindowPostMessageOptions options); // also has obsolete members }; @@ -486,3 +488,7 @@ callback IdleRequestCallback = void (IdleDeadline deadline); partial interface Window { [ChromeOnly] readonly attribute boolean isSecureContextIfOpenerIgnored; }; + +dictionary WindowPostMessageOptions : StructuredSerializeOptions { + USVString targetOrigin = "/"; +}; diff --git a/dom/webidl/WindowOrWorkerGlobalScope.webidl b/dom/webidl/WindowOrWorkerGlobalScope.webidl index 652a46ffcb..9e639db5f4 100644 --- a/dom/webidl/WindowOrWorkerGlobalScope.webidl +++ b/dom/webidl/WindowOrWorkerGlobalScope.webidl @@ -44,6 +44,10 @@ interface WindowOrWorkerGlobalScope { Promise createImageBitmap(ImageBitmapSource aImage); [Throws] Promise createImageBitmap(ImageBitmapSource aImage, long aSx, long aSy, long aSw, long aSh); + + // structured cloning + [Throws] + any structuredClone(any value, optional StructuredSerializeOptions options); }; // https://fetch.spec.whatwg.org/#fetch-method diff --git a/dom/webidl/Worker.webidl b/dom/webidl/Worker.webidl index 158a502d60..c13357cd7e 100644 --- a/dom/webidl/Worker.webidl +++ b/dom/webidl/Worker.webidl @@ -19,7 +19,9 @@ interface Worker : EventTarget { void terminate(); [Throws] - void postMessage(any message, optional sequence transfer); + void postMessage(any message, sequence transfer); + [Throws] + void postMessage(any message, optional StructuredSerializeOptions options); attribute EventHandler onmessage; }; diff --git a/dom/workers/ServiceWorker.cpp b/dom/workers/ServiceWorker.cpp index 16dd28b8ad..f2a39ea9c2 100644 --- a/dom/workers/ServiceWorker.cpp +++ b/dom/workers/ServiceWorker.cpp @@ -14,6 +14,7 @@ #include "mozilla/Preferences.h" #include "mozilla/dom/Promise.h" +#include "mozilla/dom/MessagePortBinding.h" #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" #ifdef XP_WIN @@ -77,7 +78,7 @@ ServiceWorker::GetScriptURL(nsString& aURL) const void ServiceWorker::PostMessage(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, ErrorResult& aRv) { if (State() == ServiceWorkerState::Redundant) { @@ -97,6 +98,15 @@ ServiceWorker::PostMessage(JSContext* aCx, JS::Handle aMessage, aRv = workerPrivate->SendMessageEvent(aCx, aMessage, aTransferable, Move(clientInfo)); } +void +ServiceWorker::PostMessage(JSContext* aCx, + JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, + ErrorResult& aRv) +{ + PostMessage(aCx, aMessage, aOptions.mTransfer, aRv); +} + } // namespace workers } // namespace dom } // namespace mozilla diff --git a/dom/workers/ServiceWorker.h b/dom/workers/ServiceWorker.h index 3a3cce8260..eb14fc4293 100644 --- a/dom/workers/ServiceWorker.h +++ b/dom/workers/ServiceWorker.h @@ -15,6 +15,8 @@ class nsPIDOMWindowInner; namespace mozilla { namespace dom { +struct StructuredSerializeOptions; + namespace workers { class ServiceWorkerInfo; @@ -63,7 +65,13 @@ public: void PostMessage(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, + ErrorResult& aRv); + + void + PostMessage(JSContext* aCx, + JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, ErrorResult& aRv); private: diff --git a/dom/workers/ServiceWorkerClient.cpp b/dom/workers/ServiceWorkerClient.cpp index 1f845a947b..e8b8341e21 100644 --- a/dom/workers/ServiceWorkerClient.cpp +++ b/dom/workers/ServiceWorkerClient.cpp @@ -11,6 +11,7 @@ #include "mozilla/dom/Navigator.h" #include "mozilla/dom/ServiceWorkerMessageEvent.h" #include "mozilla/dom/ServiceWorkerMessageEventBinding.h" +#include "mozilla/dom/MessagePortBinding.h" #include "nsGlobalWindow.h" #include "nsIBrowserDOMWindow.h" #include "nsIDocument.h" @@ -190,7 +191,7 @@ private: void ServiceWorkerClient::PostMessage(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, ErrorResult& aRv) { WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); @@ -198,20 +199,11 @@ ServiceWorkerClient::PostMessage(JSContext* aCx, JS::Handle aMessage, workerPrivate->AssertIsOnWorkerThread(); JS::Rooted transferable(aCx, JS::UndefinedValue()); - if (aTransferable.WasPassed()) { - const Sequence& realTransferable = aTransferable.Value(); - - JS::HandleValueArray elements = - JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(), - realTransferable.Elements()); - - JSObject* array = JS_NewArrayObject(aCx, elements); - if (!array) { - aRv.Throw(NS_ERROR_OUT_OF_MEMORY); - return; - } - - transferable.setObject(*array); + aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, + aTransferable, + &transferable); + if (NS_WARN_IF(aRv.Failed())) { + return; } RefPtr runnable = @@ -229,3 +221,11 @@ ServiceWorkerClient::PostMessage(JSContext* aCx, JS::Handle aMessage, } } +void +ServiceWorkerClient::PostMessage(JSContext* aCx, + JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, + ErrorResult& aRv) +{ + PostMessage(aCx, aMessage, aOptions.mTransfer, aRv); +} diff --git a/dom/workers/ServiceWorkerClient.h b/dom/workers/ServiceWorkerClient.h index 46bcdbc4c4..7f89ee611a 100644 --- a/dom/workers/ServiceWorkerClient.h +++ b/dom/workers/ServiceWorkerClient.h @@ -91,7 +91,13 @@ public: void PostMessage(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, + ErrorResult& aRv); + + void + PostMessage(JSContext* aCx, + JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, ErrorResult& aRv); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; diff --git a/dom/workers/ServiceWorkerPrivate.cpp b/dom/workers/ServiceWorkerPrivate.cpp index 2f5b8f7500..fe6ec138b7 100644 --- a/dom/workers/ServiceWorkerPrivate.cpp +++ b/dom/workers/ServiceWorkerPrivate.cpp @@ -141,7 +141,7 @@ NS_IMPL_ISUPPORTS0(MessageWaitUntilHandler) nsresult ServiceWorkerPrivate::SendMessageEvent(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, UniquePtr&& aClientInfo) { ErrorResult rv(SpawnWorkerIfNeeded(MessageEvent, nullptr)); diff --git a/dom/workers/ServiceWorkerPrivate.h b/dom/workers/ServiceWorkerPrivate.h index 8ce021c8cf..c2d5abfd6c 100644 --- a/dom/workers/ServiceWorkerPrivate.h +++ b/dom/workers/ServiceWorkerPrivate.h @@ -74,7 +74,7 @@ public: nsresult SendMessageEvent(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, UniquePtr&& aClientInfo); // This is used to validate the worker script and continue the installation diff --git a/dom/workers/SharedWorker.cpp b/dom/workers/SharedWorker.cpp index 97df0f57fd..2fae19f291 100644 --- a/dom/workers/SharedWorker.cpp +++ b/dom/workers/SharedWorker.cpp @@ -22,6 +22,7 @@ using mozilla::dom::Optional; using mozilla::dom::Sequence; using mozilla::dom::MessagePort; +using mozilla::dom::StructuredSerializeOptions; using namespace mozilla; USING_WORKERS_NAMESPACE @@ -142,7 +143,7 @@ SharedWorker::Close() void SharedWorker::PostMessage(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, ErrorResult& aRv) { AssertIsOnMainThread(); @@ -152,6 +153,15 @@ SharedWorker::PostMessage(JSContext* aCx, JS::Handle aMessage, mMessagePort->PostMessage(aCx, aMessage, aTransferable, aRv); } +void +SharedWorker::PostMessage(JSContext* aCx, + JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, + ErrorResult& aRv) +{ + PostMessage(aCx, aMessage, aOptions.mTransfer, aRv); +} + NS_IMPL_ADDREF_INHERITED(SharedWorker, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(SharedWorker, DOMEventTargetHelper) diff --git a/dom/workers/SharedWorker.h b/dom/workers/SharedWorker.h index 4820b4e7c1..e85fc2609a 100644 --- a/dom/workers/SharedWorker.h +++ b/dom/workers/SharedWorker.h @@ -94,8 +94,15 @@ private: // Only called by MessagePort. void - PostMessage(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + PostMessage(JSContext* aCx, + JS::Handle aMessage, + const Sequence& aTransferable, + ErrorResult& aRv); + + void + PostMessage(JSContext* aCx, + JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, ErrorResult& aRv); }; diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index cf270703e0..a7a4929f3b 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -3006,13 +3006,12 @@ WorkerPrivateParent::ForgetMainThreadObjects( template void -WorkerPrivateParent::PostMessageInternal( - JSContext* aCx, - JS::Handle aMessage, - const Optional>& aTransferable, - UniquePtr&& aClientInfo, - PromiseNativeHandler* aHandler, - ErrorResult& aRv) +WorkerPrivateParent::PostMessageInternal(JSContext* aCx, + JS::Handle aMessage, + const Sequence& aTransferable, + UniquePtr&& aClientInfo, + PromiseNativeHandler* aHandler, + ErrorResult& aRv) { AssertIsOnParentThread(); @@ -3024,22 +3023,11 @@ WorkerPrivateParent::PostMessageInternal( } JS::Rooted transferable(aCx, JS::UndefinedValue()); - if (aTransferable.WasPassed()) { - const Sequence& realTransferable = aTransferable.Value(); - - // The input sequence only comes from the generated bindings code, which - // ensures it is rooted. - JS::HandleValueArray elements = - JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(), - realTransferable.Elements()); - - JSObject* array = - JS_NewArrayObject(aCx, elements); - if (!array) { - aRv.Throw(NS_ERROR_OUT_OF_MEMORY); - return; - } - transferable.setObject(*array); + aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, + aTransferable, + &transferable); + if (NS_WARN_IF(aRv.Failed())) { + return; } RefPtr runnable = @@ -3083,18 +3071,31 @@ WorkerPrivateParent::PostMessageInternal( template void WorkerPrivateParent::PostMessage( - JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + JSContext* aCx, + JS::Handle aMessage, + const Sequence& aTransferable, ErrorResult& aRv) { PostMessageInternal(aCx, aMessage, aTransferable, nullptr, nullptr, aRv); } +template +void +WorkerPrivateParent::PostMessage( + JSContext* aCx, + JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, + ErrorResult& aRv) +{ + PostMessageInternal(aCx, aMessage, aOptions.mTransfer, nullptr, nullptr, aRv); +} + template void WorkerPrivateParent::PostMessageToServiceWorker( - JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + JSContext* aCx, + JS::Handle aMessage, + const Sequence& aTransferable, UniquePtr&& aClientInfo, PromiseNativeHandler* aHandler, ErrorResult& aRv) @@ -5677,27 +5678,17 @@ void WorkerPrivate::PostMessageToParentInternal( JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, ErrorResult& aRv) { AssertIsOnWorkerThread(); JS::Rooted transferable(aCx, JS::UndefinedValue()); - if (aTransferable.WasPassed()) { - const Sequence& realTransferable = aTransferable.Value(); - - // The input sequence only comes from the generated bindings code, which - // ensures it is rooted. - JS::HandleValueArray elements = - JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(), - realTransferable.Elements()); - - JSObject* array = JS_NewArrayObject(aCx, elements); - if (!array) { - aRv = NS_ERROR_OUT_OF_MEMORY; - return; - } - transferable.setObject(*array); + aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, + aTransferable, + &transferable); + if (NS_WARN_IF(aRv.Failed())) { + return; } RefPtr runnable = diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index b638c1ef11..0a71420047 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -68,6 +68,7 @@ class PromiseNativeHandler; class StructuredCloneHolder; class WorkerDebuggerGlobalScope; class WorkerGlobalScope; +struct StructuredSerializeOptions; } // namespace dom namespace ipc { class PrincipalInfo; @@ -287,7 +288,7 @@ private: void PostMessageInternal(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, UniquePtr&& aClientInfo, PromiseNativeHandler* aHandler, ErrorResult& aRv); @@ -400,12 +401,18 @@ public: void PostMessage(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, + ErrorResult& aRv); + + void + PostMessage(JSContext* aCx, + JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, ErrorResult& aRv); void PostMessageToServiceWorker(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, UniquePtr&& aClientInfo, PromiseNativeHandler* aHandler, ErrorResult& aRv); @@ -1161,18 +1168,17 @@ public: void PostMessageToParent(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, ErrorResult& aRv) { PostMessageToParentInternal(aCx, aMessage, aTransferable, aRv); } void - PostMessageToParentMessagePort( - JSContext* aCx, - JS::Handle aMessage, - const Optional>& aTransferable, - ErrorResult& aRv); + PostMessageToParentMessagePort(JSContext* aCx, + JS::Handle aMessage, + const Sequence& aTransferable, + ErrorResult& aRv); void EnterDebuggerEventLoop(); @@ -1471,7 +1477,7 @@ private: void PostMessageToParentInternal(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, ErrorResult& aRv); void diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index ec42364b52..637e441732 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -7,6 +7,7 @@ #include "jsapi.h" #include "mozilla/EventListenerManager.h" +#include "mozilla/Unused.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/Console.h" #include "mozilla/dom/DedicatedWorkerGlobalScopeBinding.h" @@ -492,6 +493,15 @@ WorkerGlobalScope::CreateImageBitmap(const ImageBitmapSource& aImage, } } +// https://html.spec.whatwg.org/#structured-cloning +void WorkerGlobalScope::StructuredClone(JSContext* aCx, + JS::Handle aValue, + const StructuredSerializeOptions& aOptions, + JS::MutableHandle aRv, + ErrorResult& aError) { + nsContentUtils::StructuredClone(aCx, this, aValue, aOptions, aRv, aError); +} + DedicatedWorkerGlobalScope::DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate) : WorkerGlobalScope(aWorkerPrivate) { @@ -535,13 +545,22 @@ DedicatedWorkerGlobalScope::WrapGlobalObject(JSContext* aCx, void DedicatedWorkerGlobalScope::PostMessage(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, ErrorResult& aRv) { mWorkerPrivate->AssertIsOnWorkerThread(); mWorkerPrivate->PostMessageToParent(aCx, aMessage, aTransferable, aRv); } +void +DedicatedWorkerGlobalScope::PostMessage(JSContext* aCx, + JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, + ErrorResult& aRv) +{ + PostMessage(aCx, aMessage, aOptions.mTransfer, aRv); +} + SharedWorkerGlobalScope::SharedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate, const nsCString& aName) : WorkerGlobalScope(aWorkerPrivate), mName(aName) diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h index 8d06152da2..01bae5cc11 100644 --- a/dom/workers/WorkerScope.h +++ b/dom/workers/WorkerScope.h @@ -24,6 +24,7 @@ class Function; class IDBFactory; enum class ImageBitmapFormat : uint32_t; class Performance; +struct StructuredSerializeOptions; class Promise; class RequestOrUSVString; class ServiceWorkerRegistration; @@ -182,6 +183,11 @@ public: const mozilla::dom::Sequence& aLayout, mozilla::ErrorResult& aRv); + void StructuredClone(JSContext* aCx, JS::Handle aValue, + const StructuredSerializeOptions& aOptions, + JS::MutableHandle aRv, + ErrorResult& aError); + bool WindowInteractionAllowed() const { @@ -215,7 +221,13 @@ public: void PostMessage(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, + const Sequence& aTransferable, + ErrorResult& aRv); + + void + PostMessage(JSContext* aCx, + JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, ErrorResult& aRv); IMPL_EVENT_HANDLER(message) diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp index b8a4859a46..9e46f2dae0 100644 --- a/js/xpconnect/src/Sandbox.cpp +++ b/js/xpconnect/src/Sandbox.cpp @@ -326,6 +326,43 @@ SandboxCreateFetch(JSContext* cx, HandleObject obj) dom::HeadersBinding::GetConstructorObject(cx); } +static bool SandboxStructuredClone(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "structuredClone", 1)) { + return false; + } + + RootedDictionary options(cx); + if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue, + "Argument 2", false)) { + return false; + } + + nsIGlobalObject* global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx)); + if (!global) { + JS_ReportErrorASCII(cx, "structuredClone: Missing global"); + return false; + } + + JS::Rooted result(cx); + ErrorResult rv; + nsContentUtils::StructuredClone(cx, global, args[0], options, &result, rv); + if (rv.MaybeSetPendingException(cx)) { + return false; + } + + args.rval().set(result); + return true; +} + +static bool SandboxCreateStructuredClone(JSContext* cx, HandleObject obj) { + MOZ_ASSERT(JS_IsGlobalObject(obj)); + + return JS_DefineFunction(cx, obj, "structuredClone", SandboxStructuredClone, + 1, 0); +} + static bool SandboxIsProxy(JSContext* cx, unsigned argc, Value* vp) { @@ -931,6 +968,8 @@ xpc::GlobalProperties::Parse(JSContext* cx, JS::HandleObject obj) #endif } else if (!strcmp(name.ptr(), "fetch")) { fetch = true; + } else if (!strcmp(name.ptr(), "structuredClone")) { + structuredClone = true; } else if (!strcmp(name.ptr(), "caches")) { caches = true; } else if (!strcmp(name.ptr(), "FileReader")) { @@ -1006,6 +1045,9 @@ xpc::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj) if (fetch && !SandboxCreateFetch(cx, obj)) return false; + if (structuredClone && !SandboxCreateStructuredClone(cx, obj)) + return false; + if (caches && !dom::cache::CacheStorage::DefineCaches(cx, obj)) return false; diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index 724c8db3a9..6c2711210a 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -2879,6 +2879,7 @@ struct GlobalProperties { bool crypto : 1; bool rtcIdentityProvider : 1; bool fetch : 1; + bool structuredClone : 1; bool caches : 1; bool fileReader: 1; private: diff --git a/js/xpconnect/tests/unit/test_structuredClone.js b/js/xpconnect/tests/unit/test_structuredClone.js new file mode 100644 index 0000000000..6b9b2d776a --- /dev/null +++ b/js/xpconnect/tests/unit/test_structuredClone.js @@ -0,0 +1,25 @@ +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["structuredClone"] }); + + sb.equal = equal; + + sb.testing = Components.utils.cloneInto({xyz: 123}, sb); + Cu.evalInSandbox(` + equal(structuredClone("abc"), "abc"); + + var obj = {a: 1}; + obj.self = obj; + var clone = structuredClone(obj); + equal(clone.a, 1); + equal(clone.self, clone); + + var ab = new ArrayBuffer(1); + clone = structuredClone(ab, {transfer: [ab]}); + equal(clone.byteLength, 1); + equal(ab.byteLength, 0); + + clone = structuredClone(testing); + equal(clone.xyz, 123); + `, sb); +} diff --git a/js/xpconnect/tests/unit/xpcshell.ini b/js/xpconnect/tests/unit/xpcshell.ini index 12648d3ecc..b13d8d455f 100644 --- a/js/xpconnect/tests/unit/xpcshell.ini +++ b/js/xpconnect/tests/unit/xpcshell.ini @@ -114,6 +114,7 @@ skip-if = os == "android" # native test components aren't available on Android [test_css.js] [test_rtcIdentityProvider.js] [test_sandbox_atob.js] +[test_structuredClone.js] [test_isProxy.js] [test_getObjectPrincipal.js] [test_sandbox_name.js] diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index b9f4094be4..d4c0dd553a 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -5252,6 +5252,9 @@ pref("prompts.content_handling_dialog_modal.enabled", false); // Whether module scripts (