author | Tiberius Oros <toros@mozilla.com> |
Fri, 09 Feb 2018 12:02:51 +0200 | |
changeset 403090 | d49553765a743ebbd4f08e92a93c9d811ee064c2 |
parent 403089 | dfd0afe71bb5c1430c3abca4ca31ac76d65cc1f1 (current diff) |
parent 403050 | 5bc50083d6fd713ccce6fdcb78fca9b7a1f3c625 (diff) |
child 403091 | 23171f69c8793d6e460aa0cd52019118f6bda926 |
child 403140 | f53d15feeb781c5fd26ca62e1a9b9ec9dd287bca |
push id | 99714 |
push user | toros@mozilla.com |
push date | Fri, 09 Feb 2018 10:15:09 +0000 |
treeherder | mozilla-inbound@23171f69c879 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 60.0a1 |
first release with | nightly linux32
d49553765a74
/
60.0a1
/
20180209102946
/
files
nightly linux64
d49553765a74
/
60.0a1
/
20180209102946
/
files
nightly mac
d49553765a74
/
60.0a1
/
20180209102946
/
files
nightly win32
d49553765a74
/
60.0a1
/
20180209102946
/
files
nightly win64
d49553765a74
/
60.0a1
/
20180209102946
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
60.0a1
/
20180209102946
/
pushlog to previous
nightly linux64
60.0a1
/
20180209102946
/
pushlog to previous
nightly mac
60.0a1
/
20180209102946
/
pushlog to previous
nightly win32
60.0a1
/
20180209102946
/
pushlog to previous
nightly win64
60.0a1
/
20180209102946
/
pushlog to previous
|
--- a/dom/base/contentAreaDropListener.js +++ b/dom/base/contentAreaDropListener.js @@ -109,55 +109,74 @@ ContentAreaDropListener.prototype = uri = ioService.newURI(uriString); } catch (ex) { } if (!uri) return uriString; // uriString is a valid URI, so do the security check. let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]. getService(Ci.nsIScriptSecurityManager); - let sourceNode = dataTransfer.mozSourceNode; let flags = secMan.STANDARD; if (disallowInherit) flags |= secMan.DISALLOW_INHERIT_PRINCIPAL; - let principal; - if (sourceNode) { - principal = this._getTriggeringPrincipalFromSourceNode(sourceNode); - } else { - // Use file:/// as the default uri so that drops of file URIs are always - // allowed. - principal = secMan.createCodebasePrincipal(ioService.newURI("file:///"), {}); - } + let principal = this._getTriggeringPrincipalFromDataTransfer(dataTransfer, false); secMan.checkLoadURIStrWithPrincipal(principal, uriString, flags); return uriString; }, - _getTriggeringPrincipalFromSourceNode: function(aSourceNode) + _getTriggeringPrincipalFromDataTransfer: function(aDataTransfer, + fallbackToSystemPrincipal) { - if (aSourceNode.localName == "browser" && - aSourceNode.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") { - return aSourceNode.contentPrincipal; + let sourceNode = aDataTransfer.mozSourceNode; + if (sourceNode && + (sourceNode.localName !== "browser" || + sourceNode.namespaceURI !== "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")) { + // Use sourceNode's principal only if the sourceNode is not browser. + // + // If sourceNode is browser, the actual triggering principal may be + // differ than sourceNode's principal, since sourceNode's principal is + // top level document's one and the drag may be triggered from a frame + // with different principal. + if (sourceNode.nodePrincipal) { + return sourceNode.nodePrincipal; + } } - return aSourceNode.nodePrincipal; + + // First, fallback to mozTriggeringPrincipalURISpec that is set when the + // drop comes from another content process. + let principalURISpec = aDataTransfer.mozTriggeringPrincipalURISpec; + if (!principalURISpec) { + // Fallback to either system principal or file principal, supposing + // the drop comes from outside of the browser, so that drops of file + // URIs are always allowed. + // + // TODO: Investigate and describe the difference between them, + // or use only one principal. (Bug 1367038) + if (fallbackToSystemPrincipal) { + let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]. + getService(Ci.nsIScriptSecurityManager); + return secMan.getSystemPrincipal(); + } else { + principalURISpec = "file:///"; + } + } + let ioService = Cc["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); + let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]. + getService(Ci.nsIScriptSecurityManager); + return secMan.createCodebasePrincipal(ioService.newURI(principalURISpec), {}); }, getTriggeringPrincipal: function(aEvent) { let dataTransfer = aEvent.dataTransfer; - let sourceNode = dataTransfer.mozSourceNode; - if (sourceNode) { - return this._getTriggeringPrincipalFromSourceNode(sourceNode, false); - } - // Bug 1367038: mozSourceNode is null if the drag event originated - // in an external application - needs better fallback! - let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]. - getService(Ci.nsIScriptSecurityManager); - return secMan.getSystemPrincipal(); + return this._getTriggeringPrincipalFromDataTransfer(dataTransfer, true); + }, canDropLink: function(aEvent, aAllowSameDocument) { if (this._eventTargetIsDisabled(aEvent)) return false; let dataTransfer = aEvent.dataTransfer;
--- a/dom/base/nsContentAreaDragDrop.cpp +++ b/dom/base/nsContentAreaDragDrop.cpp @@ -67,17 +67,18 @@ class MOZ_STACK_CLASS DragDataProducer public: DragDataProducer(nsPIDOMWindowOuter* aWindow, nsIContent* aTarget, nsIContent* aSelectionTargetNode, bool aIsAltKeyPressed); nsresult Produce(DataTransfer* aDataTransfer, bool* aCanDrag, nsISelection** aSelection, - nsIContent** aDragNode); + nsIContent** aDragNode, + nsACString& aPrincipalURISpec); private: void AddString(DataTransfer* aDataTransfer, const nsAString& aFlavor, const nsAString& aData, nsIPrincipal* aPrincipal); nsresult AddStringsToDataTransfer(nsIContent* aDragNode, DataTransfer* aDataTransfer); @@ -114,25 +115,27 @@ private: nsresult nsContentAreaDragDrop::GetDragData(nsPIDOMWindowOuter* aWindow, nsIContent* aTarget, nsIContent* aSelectionTargetNode, bool aIsAltKeyPressed, DataTransfer* aDataTransfer, bool* aCanDrag, nsISelection** aSelection, - nsIContent** aDragNode) + nsIContent** aDragNode, + nsACString& aPrincipalURISpec) { NS_ENSURE_TRUE(aSelectionTargetNode, NS_ERROR_INVALID_ARG); *aCanDrag = true; DragDataProducer provider(aWindow, aTarget, aSelectionTargetNode, aIsAltKeyPressed); - return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode); + return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode, + aPrincipalURISpec); } NS_IMPL_ISUPPORTS(nsContentAreaDragDropDataProvider, nsIFlavorDataProvider) // SaveURIToFile // used on platforms where it's possible to drag items (e.g. images) // into the file system @@ -360,17 +363,18 @@ DragDataProducer::GetNodeString(nsIConte range->ToString(outNodeString); } } nsresult DragDataProducer::Produce(DataTransfer* aDataTransfer, bool* aCanDrag, nsISelection** aSelection, - nsIContent** aDragNode) + nsIContent** aDragNode, + nsACString& aPrincipalURISpec) { NS_PRECONDITION(aCanDrag && aSelection && aDataTransfer && aDragNode, "null pointer passed to Produce"); NS_ASSERTION(mWindow, "window not set"); NS_ASSERTION(mSelectionTargetNode, "selection target node should have been set"); *aDragNode = nullptr; @@ -425,17 +429,17 @@ DragDataProducer::Produce(DataTransfer* nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(mTarget); if (flo) { RefPtr<nsFrameLoader> fl = flo->GetFrameLoader(); if (fl) { TabParent* tp = static_cast<TabParent*>(fl->GetRemoteBrowser()); if (tp) { // We have a TabParent, so it may have data for dnd in case the child // process started a dnd session. - tp->AddInitialDnDDataTo(aDataTransfer); + tp->AddInitialDnDDataTo(aDataTransfer, aPrincipalURISpec); } } } return NS_OK; } if (isChromeShell && textControl) { // Only use the selection if the target node is in the selection.
--- a/dom/base/nsContentAreaDragDrop.h +++ b/dom/base/nsContentAreaDragDrop.h @@ -45,25 +45,29 @@ public: * not drag the link. * aDataTransfer - the dataTransfer for the drag event. * aCanDrag - [out] set to true if the drag may proceed, false to stop the * drag entirely * aSelection - [out] set to the selection being dragged, or null if no * selection is being dragged. * aDragNode - [out] the link, image or area being dragged, or null if the * drag occurred on another element. + * aPrincipalURISpec - [out] set to the URI of the triggering principal of + * the drag, or empty string if it's from + * browser chrome or OS */ static nsresult GetDragData(nsPIDOMWindowOuter* aWindow, nsIContent* aTarget, nsIContent* aSelectionTargetNode, bool aIsAltKeyPressed, mozilla::dom::DataTransfer* aDataTransfer, bool* aCanDrag, nsISelection** aSelection, - nsIContent** aDragNode); + nsIContent** aDragNode, + nsACString& aPrincipalURISpec); }; // this is used to save images to disk lazily when the image data is asked for // during the drop instead of when it is added to the drag data transfer. This // ensures that the image data is only created when an image drop is allowed. class nsContentAreaDragDropDataProvider : public nsIFlavorDataProvider { virtual ~nsContentAreaDragDropDataProvider() {}
--- a/dom/events/DataTransfer.cpp +++ b/dom/events/DataTransfer.cpp @@ -302,16 +302,30 @@ DataTransfer::SetDropEffectInt(uint32_t NS_IMETHODIMP DataTransfer::GetEffectAllowedInt(uint32_t* aEffectAllowed) { *aEffectAllowed = mEffectAllowed; return NS_OK; } +void +DataTransfer::GetMozTriggeringPrincipalURISpec(nsAString& aPrincipalURISpec) +{ + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + if (!dragSession) { + aPrincipalURISpec.Truncate(0); + return; + } + + nsCString principalURISpec; + dragSession->GetTriggeringPrincipalURISpec(principalURISpec); + CopyUTF8toUTF16(principalURISpec, aPrincipalURISpec); +} + NS_IMETHODIMP DataTransfer::SetEffectAllowedInt(uint32_t aEffectAllowed) { mEffectAllowed = aEffectAllowed; return NS_OK; } NS_IMETHODIMP
--- a/dom/events/DataTransfer.h +++ b/dom/events/DataTransfer.h @@ -206,16 +206,18 @@ public: bool MozUserCancelled() const { return mUserCancelled; } already_AddRefed<nsINode> GetMozSourceNode(); + void GetMozTriggeringPrincipalURISpec(nsAString& aPrincipalURISpec); + mozilla::dom::Element* GetDragTarget() const { return mDragTarget; } nsresult GetDataAtNoSecurityCheck(const nsAString& aFormat, uint32_t aIndex, nsIVariant** aData);
--- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -1330,27 +1330,30 @@ EventStateManager::DispatchCrossProcessE RefPtr<TabParent> tabParent = remote; if (tabParent->Manager()->IsContentParent()) { tabParent->Manager()->AsContentParent()->MaybeInvokeDragSession(tabParent); } nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE; uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE; + nsCString principalURISpec; if (dragSession) { dragSession->DragEventDispatchedToChildProcess(); dragSession->GetDragAction(&action); + dragSession->GetTriggeringPrincipalURISpec(principalURISpec); nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer; dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer)); if (initialDataTransfer) { initialDataTransfer->GetDropEffectInt(&dropEffect); } } - tabParent->SendRealDragEvent(*aEvent->AsDragEvent(), action, dropEffect); + tabParent->SendRealDragEvent(*aEvent->AsDragEvent(), action, dropEffect, + principalURISpec); return; } case ePluginEventClass: { *aStatus = nsEventStatus_eConsumeNoDefault; remote->SendPluginEvent(*aEvent->AsPluginEvent()); return; } default: { @@ -1858,21 +1861,23 @@ EventStateManager::GenerateDragGesture(n auto protectDataTransfer = MakeScopeExit([&] { if (dataTransfer) { dataTransfer->Disconnect(); } }); nsCOMPtr<nsISelection> selection; nsCOMPtr<nsIContent> eventContent, targetContent; + nsCString principalURISpec; mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(eventContent)); if (eventContent) DetermineDragTargetAndDefaultData(window, eventContent, dataTransfer, getter_AddRefs(selection), - getter_AddRefs(targetContent)); + getter_AddRefs(targetContent), + principalURISpec); // Stop tracking the drag gesture now. This should stop us from // reentering GenerateDragGesture inside DOM event processing. StopTrackingDragGesture(); if (!targetContent) return; @@ -1924,17 +1929,18 @@ EventStateManager::GenerateDragGesture(n if (observerService) { observerService->NotifyObservers(dataTransfer, "on-datatransfer-available", nullptr); } if (status != nsEventStatus_eConsumeNoDefault) { bool dragStarted = DoDefaultDragStart(aPresContext, event, dataTransfer, - targetContent, selection); + targetContent, selection, + principalURISpec); if (dragStarted) { sActiveESM = nullptr; MaybeFirePointerCancel(aEvent); aEvent->StopPropagation(); } } // Reset mCurretTargetContent to what it was @@ -1947,32 +1953,34 @@ EventStateManager::GenerateDragGesture(n } } // GenerateDragGesture void EventStateManager::DetermineDragTargetAndDefaultData(nsPIDOMWindowOuter* aWindow, nsIContent* aSelectionTarget, DataTransfer* aDataTransfer, nsISelection** aSelection, - nsIContent** aTargetNode) + nsIContent** aTargetNode, + nsACString& aPrincipalURISpec) { *aTargetNode = nullptr; // GetDragData determines if a selection, link or image in the content // should be dragged, and places the data associated with the drag in the // data transfer. // mGestureDownContent is the node where the mousedown event for the drag // occurred, and aSelectionTarget is the node to use when a selection is used bool canDrag; nsCOMPtr<nsIContent> dragDataNode; bool wasAlt = (mGestureModifiers & MODIFIER_ALT) != 0; nsresult rv = nsContentAreaDragDrop::GetDragData(aWindow, mGestureDownContent, aSelectionTarget, wasAlt, aDataTransfer, &canDrag, aSelection, - getter_AddRefs(dragDataNode)); + getter_AddRefs(dragDataNode), + aPrincipalURISpec); if (NS_FAILED(rv) || !canDrag) return; // if GetDragData returned a node, use that as the node being dragged. // Otherwise, if a selection is being dragged, use the node within the // selection that was dragged. Otherwise, just use the mousedown target. nsIContent* dragContent = mGestureDownContent; if (dragDataNode) @@ -2026,17 +2034,18 @@ EventStateManager::DetermineDragTargetAn } } bool EventStateManager::DoDefaultDragStart(nsPresContext* aPresContext, WidgetDragEvent* aDragEvent, DataTransfer* aDataTransfer, nsIContent* aDragTarget, - nsISelection* aSelection) + nsISelection* aSelection, + const nsACString& aPrincipalURISpec) { nsCOMPtr<nsIDragService> dragService = do_GetService("@mozilla.org/widget/dragservice;1"); if (!dragService) return false; // Default handling for the dragstart event. // @@ -2105,17 +2114,19 @@ EventStateManager::DoDefaultDragStart(ns RefPtr<DragEvent> event = NS_NewDOMDragEvent(dragTarget, aPresContext, aDragEvent); // Use InvokeDragSessionWithSelection if a selection is being dragged, // such that the image can be generated from the selected text. However, // use InvokeDragSessionWithImage if a custom image was set or something // other than a selection is being dragged. if (!dragImage && aSelection) { - dragService->InvokeDragSessionWithSelection(aSelection, transArray, + dragService->InvokeDragSessionWithSelection(aSelection, + aPrincipalURISpec, + transArray, action, event, dataTransfer); } else { // if dragging within a XUL tree and no custom drag image was // set, the region argument to InvokeDragSessionWithImage needs // to be set to the area encompassing the selected rows of the // tree to ensure that the drag feedback gets clipped to those // rows. For other content, region should be null. @@ -2128,17 +2139,18 @@ EventStateManager::DoDefaultDragStart(ns do_QueryFrame(dragTarget->GetPrimaryFrame()); if (treeBody) { treeBody->GetSelectionRegion(getter_AddRefs(region)); } } } #endif - dragService->InvokeDragSessionWithImage(dragTarget->AsDOMNode(), transArray, + dragService->InvokeDragSessionWithImage(dragTarget->AsDOMNode(), + aPrincipalURISpec, transArray, region, action, dragImage ? dragImage->AsDOMNode() : nullptr, imageX, imageY, event, dataTransfer); } return true;
--- a/dom/events/EventStateManager.h +++ b/dom/events/EventStateManager.h @@ -920,38 +920,45 @@ protected: * Determine which node the drag should be targeted at. * This is either the node clicked when there is a selection, or, for HTML, * the element with a draggable property set to true. * * aSelectionTarget - target to check for selection * aDataTransfer - data transfer object that will contain the data to drag * aSelection - [out] set to the selection to be dragged * aTargetNode - [out] the draggable node, or null if there isn't one + * aPrincipalURISpec - [out] set to the URI of the triggering principal of + * the drag, or an empty string if it's from + * browser chrome or OS */ void DetermineDragTargetAndDefaultData(nsPIDOMWindowOuter* aWindow, nsIContent* aSelectionTarget, dom::DataTransfer* aDataTransfer, nsISelection** aSelection, - nsIContent** aTargetNode); + nsIContent** aTargetNode, + nsACString& aPrincipalURISpec); /* * Perform the default handling for the dragstart event and set up a * drag for aDataTransfer if it contains any data. Returns true if a drag has * started. * * aDragEvent - the dragstart event * aDataTransfer - the data transfer that holds the data to be dragged * aDragTarget - the target of the drag * aSelection - the selection to be dragged + * aPrincipalURISpec - the URI of the triggering principal of the drag, + * or an empty string if it's from browser chrome or OS */ bool DoDefaultDragStart(nsPresContext* aPresContext, WidgetDragEvent* aDragEvent, dom::DataTransfer* aDataTransfer, nsIContent* aDragTarget, - nsISelection* aSelection); + nsISelection* aSelection, + const nsACString& aPrincipalURISpec); bool IsTrackingDragGesture ( ) const { return mGestureDownContent != nullptr; } /** * Set the fields of aEvent to reflect the mouse position and modifier keys * that were set when the user first pressed the mouse button (stored by * BeginTrackingDragGesture). aEvent->mWidget must be * mCurrentTarget->GetNearestWidget(). */
--- a/dom/ipc/ContentPrefs.cpp +++ b/dom/ipc/ContentPrefs.cpp @@ -121,16 +121,17 @@ const char* mozilla::dom::ContentPrefs:: "javascript.options.ion.offthread_compilation", "javascript.options.ion.threshold", "javascript.options.ion.unsafe_eager_compilation", "javascript.options.jit.full_debug_checks", "javascript.options.native_regexp", "javascript.options.parallel_parsing", "javascript.options.shared_memory", "javascript.options.spectre.index_masking", + "javascript.options.spectre.string_mitigations", "javascript.options.streams", "javascript.options.strict", "javascript.options.strict.debug", "javascript.options.throw_on_asmjs_validation_failure", "javascript.options.throw_on_debuggee_would_run", "javascript.options.wasm", "javascript.options.wasm_baselinejit", "javascript.options.wasm_ionjit",
--- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -561,17 +561,18 @@ parent: nested(inside_sync) sync DispatchWheelEvent(WidgetWheelEvent event); nested(inside_sync) sync DispatchMouseEvent(WidgetMouseEvent event); nested(inside_sync) sync DispatchKeyboardEvent(WidgetKeyboardEvent event); async InvokeDragSession(IPCDataTransfer[] transfers, uint32_t action, OptionalShmem visualData, uint32_t stride, uint8_t format, - LayoutDeviceIntRect dragRect); + LayoutDeviceIntRect dragRect, + nsCString principalURISpec); // After a compositor reset, it is necessary to reconnect each layers ID to // the compositor of the widget that will render those layers. Note that // this is sync so we can ensure that messages to the window compositor // arrive before the TabChild attempts to use its cross-process compositor // bridge. sync EnsureLayersConnected() returns (CompositorOptions compositorOptions); @@ -700,17 +701,17 @@ child: uint64_t aInputBlockId, nsEventStatus aApzResponse); /* * We disable the input event queue when there is an active dnd session. We * don't need support RealDragEvent with input priority. */ async RealDragEvent(WidgetDragEvent aEvent, uint32_t aDragAction, - uint32_t aDropEffect); + uint32_t aDropEffect, nsCString aPrincipalURISpec); async PluginEvent(WidgetPluginEvent aEvent); /** * @see nsIDOMWindowUtils sendKeyEvent. */ async KeyEvent(nsString aType, int32_t aKeyCode,
--- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -1943,24 +1943,26 @@ TabChild::RecvNormalPriorityRealTouchMov const nsEventStatus& aApzResponse) { return RecvRealTouchMoveEvent(aEvent, aGuid, aInputBlockId, aApzResponse); } mozilla::ipc::IPCResult TabChild::RecvRealDragEvent(const WidgetDragEvent& aEvent, const uint32_t& aDragAction, - const uint32_t& aDropEffect) + const uint32_t& aDropEffect, + const nsCString& aPrincipalURISpec) { WidgetDragEvent localEvent(aEvent); localEvent.mWidget = mPuppetWidget; nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); if (dragSession) { dragSession->SetDragAction(aDragAction); + dragSession->SetTriggeringPrincipalURISpec(aPrincipalURISpec); nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer; dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer)); if (initialDataTransfer) { initialDataTransfer->SetDropEffectInt(aDropEffect); } } if (aEvent.mMessage == eDrop) {
--- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -386,17 +386,18 @@ public: const uint64_t& aInputBlockId) override; virtual mozilla::ipc::IPCResult RecvNormalPriorityRealMouseButtonEvent(const mozilla::WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId) override; virtual mozilla::ipc::IPCResult RecvRealDragEvent(const WidgetDragEvent& aEvent, const uint32_t& aDragAction, - const uint32_t& aDropEffect) override; + const uint32_t& aDropEffect, + const nsCString& aPrincipalURISpec) override; virtual mozilla::ipc::IPCResult RecvRealKeyEvent(const mozilla::WidgetKeyboardEvent& aEvent) override; virtual mozilla::ipc::IPCResult RecvNormalPriorityRealKeyEvent(const mozilla::WidgetKeyboardEvent& aEvent) override; virtual mozilla::ipc::IPCResult RecvMouseWheelEvent(const mozilla::WidgetWheelEvent& aEvent,
--- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -1276,30 +1276,32 @@ TabParent::QueryDropLinksForVerification mVerifyDropLinks.Clear(); return false; } return true; } void TabParent::SendRealDragEvent(WidgetDragEvent& aEvent, uint32_t aDragAction, - uint32_t aDropEffect) + uint32_t aDropEffect, + const nsCString& aPrincipalURISpec) { if (mIsDestroyed || !mIsReadyToHandleInputEvents) { return; } MOZ_ASSERT(!Manager()->AsContentParent()->IsInputPriorityEventEnabled()); aEvent.mRefPoint += GetChildProcessOffset(); if (aEvent.mMessage == eDrop) { if (!QueryDropLinksForVerification()) { return; } } DebugOnly<bool> ret = - PBrowserParent::SendRealDragEvent(aEvent, aDragAction, aDropEffect); + PBrowserParent::SendRealDragEvent(aEvent, aDragAction, aDropEffect, + aPrincipalURISpec); NS_WARNING_ASSERTION(ret, "PBrowserParent::SendRealDragEvent() failed"); MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess()); } LayoutDevicePoint TabParent::AdjustTapToChildWidget(const LayoutDevicePoint& aPoint) { return aPoint + LayoutDevicePoint(GetChildProcessOffset()); @@ -3340,17 +3342,18 @@ TabParent::RecvAsyncAuthPrompt(const nsC return IPC_OK(); } mozilla::ipc::IPCResult TabParent::RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers, const uint32_t& aAction, const OptionalShmem& aVisualDnDData, const uint32_t& aStride, const uint8_t& aFormat, - const LayoutDeviceIntRect& aDragRect) + const LayoutDeviceIntRect& aDragRect, + const nsCString& aPrincipalURISpec) { mInitialDataTransferItems.Clear(); nsIPresShell* shell = mFrameElement->OwnerDoc()->GetShell(); if (!shell) { if (Manager()->IsContentParent()) { Unused << Manager()->AsContentParent()->SendEndDragSession(true, true, LayoutDeviceIntPoint(), 0); @@ -3382,29 +3385,44 @@ TabParent::RecvInvokeDragSession(nsTArra gfx::CreateDataSourceSurfaceFromData(gfx::IntSize(aDragRect.width, aDragRect.height), static_cast<gfx::SurfaceFormat>(aFormat), aVisualDnDData.get_Shmem().get<uint8_t>(), aStride); } mDragValid = true; mDragRect = aDragRect; + mDragPrincipalURISpec = aPrincipalURISpec; esm->BeginTrackingRemoteDragGesture(mFrameElement); if (aVisualDnDData.type() == OptionalShmem::TShmem) { Unused << DeallocShmem(aVisualDnDData); } return IPC_OK(); } void -TabParent::AddInitialDnDDataTo(DataTransfer* aDataTransfer) +TabParent::AddInitialDnDDataTo(DataTransfer* aDataTransfer, + nsACString& aPrincipalURISpec) { + aPrincipalURISpec.Assign(mDragPrincipalURISpec); + + nsCOMPtr<nsIPrincipal> principal; + if (!mDragPrincipalURISpec.IsEmpty()) { + // If principal is given, try using it first. + principal = BasePrincipal::CreateCodebasePrincipal(mDragPrincipalURISpec); + } + if (!principal) { + // Fallback to system principal, to handle like the data is from browser + // chrome or OS. + principal = nsContentUtils::GetSystemPrincipal(); + } + for (uint32_t i = 0; i < mInitialDataTransferItems.Length(); ++i) { nsTArray<IPCDataTransferItem>& itemArray = mInitialDataTransferItems[i]; for (auto& item : itemArray) { RefPtr<nsVariantCC> variant = new nsVariantCC(); // Special case kFilePromiseMime so that we get the right // nsIFlavorDataProvider for it. if (item.flavor().EqualsLiteral(kFilePromiseMime)) { RefPtr<nsISupports> flavorDataProvider = @@ -3430,29 +3448,27 @@ TabParent::AddInitialDnDDataTo(DataTrans } else { Shmem data = item.data().get_Shmem(); variant->SetAsACString(nsDependentCString(data.get<char>(), data.Size<char>())); } mozilla::Unused << DeallocShmem(item.data().get_Shmem()); } - // Using system principal here, since once the data is on parent process - // side, it can be handled as being from browser chrome or OS. - // We set aHidden to false, as we don't need to worry about hiding data // from content in the parent process where there is no content. // XXX: Nested Content Processes may change this aDataTransfer->SetDataWithPrincipalFromOtherProcess(NS_ConvertUTF8toUTF16(item.flavor()), variant, i, - nsContentUtils::GetSystemPrincipal(), + principal, /* aHidden = */ false); } } mInitialDataTransferItems.Clear(); + mDragPrincipalURISpec.Truncate(0); } bool TabParent::TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface, LayoutDeviceIntRect* aDragRect) { if (!mDragValid) return false;
--- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -437,17 +437,18 @@ public: * The following Send*Event() marks aEvent as posted to remote process if * it succeeded. So, you can check the result with * aEvent.HasBeenPostedToRemoteProcess(). */ void SendRealMouseEvent(WidgetMouseEvent& aEvent); void SendRealDragEvent(WidgetDragEvent& aEvent, uint32_t aDragAction, - uint32_t aDropEffect); + uint32_t aDropEffect, + const nsCString& aPrincipalURISpec); void SendMouseWheelEvent(WidgetWheelEvent& aEvent); void SendRealKeyEvent(WidgetKeyboardEvent& aEvent); void SendRealTouchEvent(WidgetTouchEvent& aEvent); void SendPluginEvent(WidgetPluginEvent& aEvent); @@ -580,19 +581,21 @@ public: void LayerTreeUpdate(uint64_t aEpoch, bool aActive); virtual mozilla::ipc::IPCResult RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers, const uint32_t& aAction, const OptionalShmem& aVisualDnDData, const uint32_t& aStride, const uint8_t& aFormat, - const LayoutDeviceIntRect& aDragRect) override; + const LayoutDeviceIntRect& aDragRect, + const nsCString& aPrincipalURISpec) override; - void AddInitialDnDDataTo(DataTransfer* aDataTransfer); + void AddInitialDnDDataTo(DataTransfer* aDataTransfer, + nsACString& aPrincipalURISpec); bool TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface, LayoutDeviceIntRect* aDragRect); layout::RenderFrameParent* GetRenderFrame(); bool SetRenderFrame(PRenderFrameParent* aRFParent); bool GetRenderFrameInfo(TextureFactoryIdentifier* aTextureFactoryIdentifier, @@ -689,16 +692,17 @@ private: uint32_t mChromeFlags; nsTArray<nsTArray<IPCDataTransferItem>> mInitialDataTransferItems; RefPtr<gfx::DataSourceSurface> mDnDVisualization; bool mDragValid; LayoutDeviceIntRect mDragRect; + nsCString mDragPrincipalURISpec; // When true, the TabParent is initialized without child side's request. // When false, the TabParent is initialized by window.open() from child side. bool mInitedByParent; nsCOMPtr<nsILoadContext> mLoadContext; // We keep a strong reference to the frameloader after we've sent the
--- a/dom/webidl/DataTransfer.webidl +++ b/dom/webidl/DataTransfer.webidl @@ -152,16 +152,24 @@ partial interface DataTransfer { /** * The node that the mouse was pressed over to begin the drag. For external * drags, or if the caller cannot access this node, this will be null. */ [UseCounter] readonly attribute Node? mozSourceNode; /** + * The URI spec of the triggering principal. This may be different than + * sourceNode's principal when sourceNode is xul:browser and the drag is + * triggered in a browsing context inside it. + */ + [ChromeOnly] + readonly attribute DOMString mozTriggeringPrincipalURISpec; + + /** * Copy the given DataTransfer for the given event. Used by testing code for * creating emulated Drag and Drop events in the UI. * * NOTE: Don't expose a DataTransfer produced with this method to the web or * use this for non-testing purposes. It can easily be used to get the * DataTransfer into an invalid state, and is an unstable implementation * detail of EventUtils.synthesizeDrag. */
--- a/gfx/2d/ScaledFontFontconfig.cpp +++ b/gfx/2d/ScaledFontFontconfig.cpp @@ -308,16 +308,19 @@ ScaledFontFontconfig::GetWRFontInstanceO // Match cairo-ft's handling of embeddedbitmap: // If AA is explicitly disabled, leave bitmaps enabled. // Otherwise, disable embedded bitmaps unless explicitly enabled. FcBool bitmap; if (FcPatternGetBool(mPattern, FC_EMBEDDED_BITMAP, 0, &bitmap) == FcResultMatch && bitmap) { options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS; } + + // FIXME: Cairo-FT metrics are not compatible with subpixel positioning. + options.subpx_dir = wr::SubpixelDirection::None; } else { options.render_mode = wr::FontRenderMode::Mono; options.subpx_dir = wr::SubpixelDirection::None; platformOptions.hinting = wr::FontHinting::Mono; options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS; } FcBool hinting;
--- a/gfx/qcms/transform.c +++ b/gfx/qcms/transform.c @@ -990,16 +990,20 @@ void qcms_transform_release(qcms_transfo free(t->input_gamma_table_b); free(t->input_gamma_table_gray); free(t->output_gamma_lut_r); free(t->output_gamma_lut_g); free(t->output_gamma_lut_b); + /* r_clut points to beginning of buffer allocated in qcms_transform_precacheLUT_float */ + if (t->r_clut) + free(t->r_clut); + transform_free(t); } #ifdef X86 // Determine if we can build with SSE2 (this was partly copied from jmorecfg.h in // mozilla/jpeg) // ------------------------------------------------------------------------- #if defined(_M_IX86) && defined(_MSC_VER) @@ -1186,16 +1190,17 @@ qcms_transform* qcms_transform_precacheL } else { transform->transform_fn = qcms_transform_data_tetra_clut; } } } //XXX: qcms_modular_transform_data may return either the src or dest buffer. If so it must not be free-ed + // It will be stored in r_clut, which will be cleaned up in qcms_transform_release. if (src && lut != src) { free(src); } if (dest && lut != dest) { free(dest); } if (lut == NULL) {
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -1333,17 +1333,17 @@ PrepareAndExecuteRegExp(JSContext* cx, M // Check if |lastIndex > 0 && lastIndex < input->length()|. // lastIndex should already have no sign here. masm.branchTest32(Assembler::Zero, lastIndex, lastIndex, &done); masm.loadStringLength(input, temp2); masm.branch32(Assembler::AboveOrEqual, lastIndex, temp2, &done); // Check if input[lastIndex] is trail surrogate. - masm.loadStringChars(input, temp2); + masm.loadStringChars(input, temp2, CharEncoding::TwoByte); masm.computeEffectiveAddress(BaseIndex(temp2, lastIndex, TimesTwo), temp3); masm.load16ZeroExtend(Address(temp3, 0), temp3); masm.branch32(Assembler::Below, temp3, Imm32(unicode::TrailSurrogateMin), &done); masm.branch32(Assembler::Above, temp3, Imm32(unicode::TrailSurrogateMax), &done); // Check if input[lastIndex-1] is lead surrogate. masm.move32(lastIndex, temp3); @@ -1369,30 +1369,32 @@ PrepareAndExecuteRegExp(JSContext* cx, M masm.add32(Imm32(1), temp2); masm.store32(temp2, pairCountAddress); } // Load the code pointer for the type of input string we have, and compute // the input start/end pointers in the InputOutputData. Register codePointer = temp1; { - masm.loadStringChars(input, temp2); - masm.storePtr(temp2, inputStartAddress); masm.loadStringLength(input, temp3); Label isLatin1, done; masm.branchLatin1String(input, &isLatin1); { + masm.loadStringChars(input, temp2, CharEncoding::TwoByte); + masm.storePtr(temp2, inputStartAddress); masm.lshiftPtr(Imm32(1), temp3); masm.loadPtr(Address(temp1, RegExpShared::offsetOfTwoByteJitCode(mode)), codePointer); - } - masm.jump(&done); + masm.jump(&done); + } + masm.bind(&isLatin1); { - masm.bind(&isLatin1); + masm.loadStringChars(input, temp2, CharEncoding::Latin1); + masm.storePtr(temp2, inputStartAddress); masm.loadPtr(Address(temp1, RegExpShared::offsetOfLatin1JitCode(mode)), codePointer); } masm.bind(&done); masm.addPtr(temp3, temp2); masm.storePtr(temp2, inputEndAddress); } @@ -1575,25 +1577,26 @@ CreateDependentString::generate(MacroAss masm.push(base); // Adjust the start index address for the above pushes. MOZ_ASSERT(startIndexAddress.base == masm.getStackPointer()); BaseIndex newStartIndexAddress = startIndexAddress; newStartIndexAddress.offset += 2 * sizeof(void*); // Load chars pointer for the new string. - masm.addPtr(ImmWord(JSInlineString::offsetOfInlineStorage()), string); + masm.loadInlineStringCharsForStore(string, string); // Load the source characters pointer. - masm.loadStringChars(base, base); - masm.load32(newStartIndexAddress, temp2); + masm.loadStringChars(base, temp2, + latin1 ? CharEncoding::Latin1 : CharEncoding::TwoByte); + masm.load32(newStartIndexAddress, base); if (latin1) masm.addPtr(temp2, base); else - masm.computeEffectiveAddress(BaseIndex(base, temp2, TimesTwo), base); + masm.computeEffectiveAddress(BaseIndex(temp2, base, TimesTwo), base); CopyStringChars(masm, string, base, temp1, temp2, latin1 ? 1 : 2, latin1 ? 1 : 2); // Null-terminate. if (latin1) masm.store8(Imm32(0), Address(string, 0)); else masm.store16(Imm32(0), Address(string, 0)); @@ -1609,34 +1612,35 @@ CreateDependentString::generate(MacroAss // Make a dependent string. int32_t flags = (latin1 ? JSString::LATIN1_CHARS_BIT : 0) | JSString::DEPENDENT_FLAGS; masm.newGCString(string, temp2, &fallbacks_[FallbackKind::NotInlineString]); masm.bind(&joins_[FallbackKind::NotInlineString]); masm.store32(Imm32(flags), Address(string, JSString::offsetOfFlags())); masm.store32(temp1, Address(string, JSString::offsetOfLength())); - masm.loadPtr(Address(base, JSString::offsetOfNonInlineChars()), temp1); + masm.loadNonInlineStringChars(base, temp1, + latin1 ? CharEncoding::Latin1 : CharEncoding::TwoByte); masm.load32(startIndexAddress, temp2); if (latin1) masm.addPtr(temp2, temp1); else masm.computeEffectiveAddress(BaseIndex(temp1, temp2, TimesTwo), temp1); - masm.storePtr(temp1, Address(string, JSString::offsetOfNonInlineChars())); - masm.storePtr(base, Address(string, JSDependentString::offsetOfBase())); + masm.storeNonInlineStringChars(temp1, string); + masm.storeDependentStringBase(base, string); // Follow any base pointer if the input is itself a dependent string. // Watch for undepended strings, which have a base pointer but don't // actually share their characters with it. Label noBase; masm.load32(Address(base, JSString::offsetOfFlags()), temp1); masm.and32(Imm32(JSString::TYPE_FLAGS_MASK), temp1); masm.branch32(Assembler::NotEqual, temp1, Imm32(JSString::DEPENDENT_FLAGS), &noBase); - masm.loadPtr(Address(base, JSDependentString::offsetOfBase()), temp1); - masm.storePtr(temp1, Address(string, JSDependentString::offsetOfBase())); + masm.loadDependentStringBase(base, temp1); + masm.storeDependentStringBase(temp1, string); masm.bind(&noBase); } masm.bind(&done); } static void* AllocateString(JSContext* cx) @@ -2446,18 +2450,16 @@ CodeGenerator::visitOutOfLineRegExpInsta masm.jump(ool->rejoin()); } static void FindFirstDollarIndex(MacroAssembler& masm, Register str, Register len, Register chars, Register temp, Register output, bool isLatin1) { - masm.loadStringChars(str, chars); - masm.move32(Imm32(0), output); Label start, done; masm.bind(&start); if (isLatin1) masm.load8ZeroExtend(BaseIndex(chars, output, TimesOne), temp); else masm.load16ZeroExtend(BaseIndex(chars, output, TimesTwo), temp); @@ -2489,21 +2491,23 @@ CodeGenerator::visitGetFirstDollarIndex( StoreRegisterTo(output)); masm.branchIfRope(str, ool->entry()); masm.loadStringLength(str, len); Label isLatin1, done; masm.branchLatin1String(str, &isLatin1); { + masm.loadStringChars(str, temp0, CharEncoding::TwoByte); FindFirstDollarIndex(masm, str, len, temp0, temp1, output, /* isLatin1 = */ false); - } - masm.jump(&done); + masm.jump(&done); + } + masm.bind(&isLatin1); { - masm.bind(&isLatin1); + masm.loadStringChars(str, temp0, CharEncoding::Latin1); FindFirstDollarIndex(masm, str, len, temp0, temp1, output, /* isLatin1 = */ true); } masm.bind(&done); masm.bind(ool->rejoin()); } typedef JSString* (*StringReplaceFn)(JSContext*, HandleString, HandleString, HandleString); static const VMFunction StringFlatReplaceInfo = @@ -7769,23 +7773,25 @@ CopyStringCharsMaybeInflate(MacroAssembl { // destChars is TwoByte and input is a Latin1 or TwoByte string, so we may // have to inflate. Label isLatin1, done; masm.loadStringLength(input, temp1); masm.branchLatin1String(input, &isLatin1); { - masm.loadStringChars(input, input); + masm.loadStringChars(input, temp2, CharEncoding::TwoByte); + masm.movePtr(temp2, input); CopyStringChars(masm, destChars, input, temp1, temp2, sizeof(char16_t), sizeof(char16_t)); masm.jump(&done); } masm.bind(&isLatin1); { - masm.loadStringChars(input, input); + masm.loadStringChars(input, temp2, CharEncoding::Latin1); + masm.movePtr(temp2, input); CopyStringChars(masm, destChars, input, temp1, temp2, sizeof(char), sizeof(char16_t)); } masm.bind(&done); } static void ConcatInlineString(MacroAssembler& masm, Register lhs, Register rhs, Register output, Register temp1, Register temp2, Register temp3, @@ -7823,35 +7829,37 @@ ConcatInlineString(MacroAssembler& masm, masm.store32(Imm32(flags), Address(output, JSString::offsetOfFlags())); } masm.bind(&allocDone); // Store length. masm.store32(temp2, Address(output, JSString::offsetOfLength())); // Load chars pointer in temp2. - masm.computeEffectiveAddress(Address(output, JSInlineString::offsetOfInlineStorage()), temp2); + masm.loadInlineStringCharsForStore(output, temp2); { // Copy lhs chars. Note that this advances temp2 to point to the next // char. This also clobbers the lhs register. if (isTwoByte) { CopyStringCharsMaybeInflate(masm, lhs, temp2, temp1, temp3); } else { masm.loadStringLength(lhs, temp3); - masm.loadStringChars(lhs, lhs); + masm.loadStringChars(lhs, temp1, CharEncoding::Latin1); + masm.movePtr(temp1, lhs); CopyStringChars(masm, temp2, lhs, temp3, temp1, sizeof(char), sizeof(char)); } // Copy rhs chars. Clobbers the rhs register. if (isTwoByte) { CopyStringCharsMaybeInflate(masm, rhs, temp2, temp1, temp3); } else { masm.loadStringLength(rhs, temp3); - masm.loadStringChars(rhs, rhs); + masm.loadStringChars(rhs, temp1, CharEncoding::Latin1); + masm.movePtr(temp1, rhs); CopyStringChars(masm, temp2, rhs, temp3, temp1, sizeof(char), sizeof(char)); } // Null-terminate. if (isTwoByte) masm.store16(Imm32(0), Address(temp2, 0)); else masm.store8(Imm32(0), Address(temp2, 0)); @@ -7902,77 +7910,79 @@ CodeGenerator::visitSubstr(LSubstr* lir) // Use slow path for ropes. masm.bind(&nonZero); masm.branchIfRopeOrExternal(string, temp, slowPath); // Handle inlined strings by creating a FatInlineString. masm.branchTest32(Assembler::Zero, stringFlags, Imm32(JSString::INLINE_CHARS_BIT), ¬Inline); masm.newGCFatInlineString(output, temp, slowPath); masm.store32(length, Address(output, JSString::offsetOfLength())); - Address stringStorage(string, JSInlineString::offsetOfInlineStorage()); - Address outputStorage(output, JSInlineString::offsetOfInlineStorage()); masm.branchLatin1String(string, &isInlinedLatin1); { masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS), Address(output, JSString::offsetOfFlags())); - masm.computeEffectiveAddress(stringStorage, temp); + masm.loadInlineStringChars(string, temp, CharEncoding::TwoByte); if (temp2 == string) masm.push(string); BaseIndex chars(temp, begin, ScaleFromElemWidth(sizeof(char16_t))); masm.computeEffectiveAddress(chars, temp2); - masm.computeEffectiveAddress(outputStorage, temp); + masm.loadInlineStringCharsForStore(output, temp); CopyStringChars(masm, temp, temp2, length, temp3, sizeof(char16_t), sizeof(char16_t)); masm.load32(Address(output, JSString::offsetOfLength()), length); masm.store16(Imm32(0), Address(temp, 0)); if (temp2 == string) masm.pop(string); masm.jump(done); } masm.bind(&isInlinedLatin1); { masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS | JSString::LATIN1_CHARS_BIT), Address(output, JSString::offsetOfFlags())); - if (temp2 == string) + if (temp2 == string) { masm.push(string); - masm.computeEffectiveAddress(stringStorage, temp2); + masm.loadInlineStringChars(string, temp, CharEncoding::Latin1); + masm.movePtr(temp, temp2); + } else { + masm.loadInlineStringChars(string, temp2, CharEncoding::Latin1); + } static_assert(sizeof(char) == 1, "begin index shouldn't need scaling"); masm.addPtr(begin, temp2); - masm.computeEffectiveAddress(outputStorage, temp); + masm.loadInlineStringCharsForStore(output, temp); CopyStringChars(masm, temp, temp2, length, temp3, sizeof(char), sizeof(char)); masm.load32(Address(output, JSString::offsetOfLength()), length); masm.store8(Imm32(0), Address(temp, 0)); if (temp2 == string) masm.pop(string); masm.jump(done); } // Handle other cases with a DependentString. masm.bind(¬Inline); masm.newGCString(output, temp, slowPath); masm.store32(length, Address(output, JSString::offsetOfLength())); - masm.storePtr(string, Address(output, JSDependentString::offsetOfBase())); + masm.storeDependentStringBase(string, output); masm.branchLatin1String(string, &isLatin1); { masm.store32(Imm32(JSString::DEPENDENT_FLAGS), Address(output, JSString::offsetOfFlags())); - masm.loadPtr(Address(string, JSString::offsetOfNonInlineChars()), temp); + masm.loadNonInlineStringChars(string, temp, CharEncoding::TwoByte); BaseIndex chars(temp, begin, ScaleFromElemWidth(sizeof(char16_t))); masm.computeEffectiveAddress(chars, temp); - masm.storePtr(temp, Address(output, JSString::offsetOfNonInlineChars())); + masm.storeNonInlineStringChars(temp, output); masm.jump(done); } masm.bind(&isLatin1); { masm.store32(Imm32(JSString::DEPENDENT_FLAGS | JSString::LATIN1_CHARS_BIT), Address(output, JSString::offsetOfFlags())); - masm.loadPtr(Address(string, JSString::offsetOfNonInlineChars()), temp); + masm.loadNonInlineStringChars(string, temp, CharEncoding::Latin1); static_assert(sizeof(char) == 1, "begin index shouldn't need scaling"); masm.addPtr(begin, temp); - masm.storePtr(temp, Address(output, JSString::offsetOfNonInlineChars())); + masm.storeNonInlineStringChars(temp, output); masm.jump(done); } masm.bind(done); } JitCode* JitCompartment::generateStringConcatStub(JSContext* cx) @@ -8034,18 +8044,17 @@ JitCompartment::generateStringConcatStub // lhs and rhs flags, so we just have to clear the other flags to get our // rope flags (Latin1 if both lhs and rhs are Latin1). static_assert(JSString::INIT_ROPE_FLAGS == 0, "Rope type flags must be 0"); masm.and32(Imm32(JSString::LATIN1_CHARS_BIT), temp1); masm.store32(temp1, Address(output, JSString::offsetOfFlags())); masm.store32(temp2, Address(output, JSString::offsetOfLength())); // Store left and right nodes. - masm.storePtr(lhs, Address(output, JSRope::offsetOfLeft())); - masm.storePtr(rhs, Address(output, JSRope::offsetOfRight())); + masm.storeRopeChildren(lhs, rhs, output); masm.ret(); masm.bind(&leftEmpty); masm.mov(rhs, output); masm.ret(); masm.bind(&rightEmpty); masm.mov(lhs, output); @@ -8332,34 +8341,32 @@ CodeGenerator::visitFromCodePoint(LFromC Label isSupplementary; masm.branch32(Assembler::AboveOrEqual, codePoint, Imm32(unicode::NonBMPMin), &isSupplementary); { // Store length. masm.store32(Imm32(1), Address(output, JSString::offsetOfLength())); // Load chars pointer in temp1. - masm.computeEffectiveAddress(Address(output, JSInlineString::offsetOfInlineStorage()), - temp1); + masm.loadInlineStringCharsForStore(output, temp1); masm.store16(codePoint, Address(temp1, 0)); // Null-terminate. masm.store16(Imm32(0), Address(temp1, sizeof(char16_t))); masm.jump(done); } masm.bind(&isSupplementary); { // Store length. masm.store32(Imm32(2), Address(output, JSString::offsetOfLength())); // Load chars pointer in temp1. - masm.computeEffectiveAddress(Address(output, JSInlineString::offsetOfInlineStorage()), - temp1); + masm.loadInlineStringCharsForStore(output, temp1); // Inlined unicode::LeadSurrogate(uint32_t). masm.move32(codePoint, temp2); masm.rshift32(Imm32(10), temp2); masm.add32(Imm32(unicode::LeadSurrogateMin - (unicode::NonBMPMin >> 10)), temp2); masm.store16(temp2, Address(temp1, 0));
--- a/js/src/jit/JitOptions.cpp +++ b/js/src/jit/JitOptions.cpp @@ -228,16 +228,17 @@ DefaultJitOptions::DefaultJitOptions() const char* forcedRegisterAllocatorEnv = "JIT_OPTION_forcedRegisterAllocator"; if (const char* env = getenv(forcedRegisterAllocatorEnv)) { forcedRegisterAllocator = LookupRegisterAllocator(env); if (!forcedRegisterAllocator.isSome()) Warn(forcedRegisterAllocatorEnv, env); } SET_DEFAULT(spectreIndexMasking, true); + SET_DEFAULT(spectreStringMitigations, false); // Toggles whether unboxed plain objects can be created by the VM. SET_DEFAULT(disableUnboxedObjects, false); // Test whether Atomics are allowed in asm.js code. SET_DEFAULT(asmJSAtomicsEnable, false); // Toggles the optimization whereby offsets are folded into loads and not
--- a/js/src/jit/JitOptions.h +++ b/js/src/jit/JitOptions.h @@ -90,16 +90,17 @@ struct DefaultJitOptions uint32_t branchPruningThreshold; uint32_t wasmBatchIonThreshold; uint32_t wasmBatchBaselineThreshold; mozilla::Maybe<uint32_t> forcedDefaultIonWarmUpThreshold; mozilla::Maybe<uint32_t> forcedDefaultIonSmallFunctionWarmUpThreshold; mozilla::Maybe<IonRegisterAllocator> forcedRegisterAllocator; bool spectreIndexMasking; + bool spectreStringMitigations; // The options below affect the rest of the VM, and not just the JIT. bool disableUnboxedObjects; DefaultJitOptions(); bool isSmallFunction(JSScript* script) const; void setEagerCompilation(); void setCompilerWarmUpThreshold(uint32_t warmUpThreshold);
--- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -1362,29 +1362,165 @@ MacroAssembler::compareStrings(JSOp op, loadStringLength(left, result); branch32(Assembler::Equal, Address(right, JSString::offsetOfLength()), result, fail); move32(Imm32(op == JSOP_NE || op == JSOP_STRICTNE), result); bind(&done); } void -MacroAssembler::loadStringChars(Register str, Register dest) +MacroAssembler::loadStringChars(Register str, Register dest, CharEncoding encoding) { - Label isInline, done; - branchTest32(Assembler::NonZero, Address(str, JSString::offsetOfFlags()), - Imm32(JSString::INLINE_CHARS_BIT), &isInline); + MOZ_ASSERT(str != dest); + + if (JitOptions.spectreStringMitigations) { + if (encoding == CharEncoding::Latin1) { + // If the string is a rope, zero the |str| register. The code below + // depends on str->flags so this should block speculative execution. + movePtr(ImmWord(0), dest); + test32MovePtr(Assembler::Zero, + Address(str, JSString::offsetOfFlags()), Imm32(JSString::LINEAR_BIT), + dest, str); + } else { + // If we're loading TwoByte chars, there's an additional risk: + // if the string has Latin1 chars, we could read out-of-bounds. To + // prevent this, we check both the Linear and Latin1 bits. We don't + // have a scratch register, so we use these flags also to block + // speculative execution, similar to the use of 0 above. + MOZ_ASSERT(encoding == CharEncoding::TwoByte); + static constexpr uint32_t Mask = JSString::LINEAR_BIT | JSString::LATIN1_CHARS_BIT; + static_assert(Mask < 1024, + "Mask should be a small, near-null value to ensure we " + "block speculative execution when it's used as string " + "pointer"); + move32(Imm32(Mask), dest); + and32(Address(str, JSString::offsetOfFlags()), dest); + cmp32MovePtr(Assembler::NotEqual, dest, Imm32(JSString::LINEAR_BIT), + dest, str); + } + } + + // Load the inline chars. + computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), dest); + + // If it's not an inline string, load the non-inline chars. Use a + // conditional move to prevent speculative execution. + test32LoadPtr(Assembler::Zero, + Address(str, JSString::offsetOfFlags()), Imm32(JSString::INLINE_CHARS_BIT), + Address(str, JSString::offsetOfNonInlineChars()), dest); +} + +void +MacroAssembler::loadNonInlineStringChars(Register str, Register dest, CharEncoding encoding) +{ + MOZ_ASSERT(str != dest); + + if (JitOptions.spectreStringMitigations) { + // If the string is a rope, has inline chars, or has a different + // character encoding, set str to a near-null value to prevent + // speculative execution below (when reading str->nonInlineChars). + + static constexpr uint32_t Mask = + JSString::LINEAR_BIT | + JSString::INLINE_CHARS_BIT | + JSString::LATIN1_CHARS_BIT; + static_assert(Mask < 1024, + "Mask should be a small, near-null value to ensure we " + "block speculative execution when it's used as string " + "pointer"); + + uint32_t expectedBits = JSString::LINEAR_BIT; + if (encoding == CharEncoding::Latin1) + expectedBits |= JSString::LATIN1_CHARS_BIT; + + move32(Imm32(Mask), dest); + and32(Address(str, JSString::offsetOfFlags()), dest); + + cmp32MovePtr(Assembler::NotEqual, dest, Imm32(expectedBits), + dest, str); + } loadPtr(Address(str, JSString::offsetOfNonInlineChars()), dest); - jump(&done); - - bind(&isInline); +} + +void +MacroAssembler::storeNonInlineStringChars(Register chars, Register str) +{ + MOZ_ASSERT(chars != str); + storePtr(chars, Address(str, JSString::offsetOfNonInlineChars())); +} + +void +MacroAssembler::loadInlineStringCharsForStore(Register str, Register dest) +{ computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), dest); - - bind(&done); +} + +void +MacroAssembler::loadInlineStringChars(Register str, Register dest, CharEncoding encoding) +{ + MOZ_ASSERT(str != dest); + + if (JitOptions.spectreStringMitigations) { + // Making this Spectre-safe is a bit complicated: using + // computeEffectiveAddress and then zeroing the output register if + // non-inline is not sufficient: when the index is very large, it would + // allow reading |nullptr + index|. Just fall back to loadStringChars + // for now. + loadStringChars(str, dest, encoding); + } else { + computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), dest); + } +} + +void +MacroAssembler::loadRopeLeftChild(Register str, Register dest) +{ + MOZ_ASSERT(str != dest); + + if (JitOptions.spectreStringMitigations) { + // Zero the output register if the input was not a rope. + movePtr(ImmWord(0), dest); + test32LoadPtr(Assembler::Zero, + Address(str, JSString::offsetOfFlags()), Imm32(JSString::LINEAR_BIT), + Address(str, JSRope::offsetOfLeft()), dest); + } else { + loadPtr(Address(str, JSRope::offsetOfLeft()), dest); + } +} + +void +MacroAssembler::storeRopeChildren(Register left, Register right, Register str) +{ + storePtr(left, Address(str, JSRope::offsetOfLeft())); + storePtr(right, Address(str, JSRope::offsetOfRight())); +} + +void +MacroAssembler::loadDependentStringBase(Register str, Register dest) +{ + MOZ_ASSERT(str != dest); + + if (JitOptions.spectreStringMitigations) { + // If the string does not have a base-string, zero the |str| register. + // The code below loads str->base so this should block speculative + // execution. + movePtr(ImmWord(0), dest); + test32MovePtr(Assembler::Zero, + Address(str, JSString::offsetOfFlags()), Imm32(JSString::HAS_BASE_BIT), + dest, str); + } + + loadPtr(Address(str, JSDependentString::offsetOfBase()), dest); +} + +void +MacroAssembler::storeDependentStringBase(Register base, Register str) +{ + storePtr(base, Address(str, JSDependentString::offsetOfBase())); } void MacroAssembler::loadStringChar(Register str, Register index, Register output, Register scratch, Label* fail) { MOZ_ASSERT(str != output); MOZ_ASSERT(str != index); @@ -1392,40 +1528,38 @@ MacroAssembler::loadStringChar(Register MOZ_ASSERT(output != scratch); movePtr(str, output); // This follows JSString::getChar. Label notRope; branchIfNotRope(str, ¬Rope); - // Load leftChild. - loadPtr(Address(str, JSRope::offsetOfLeft()), output); + loadRopeLeftChild(str, output); // Check if the index is contained in the leftChild. // Todo: Handle index in the rightChild. boundsCheck32ForLoad(index, Address(output, JSString::offsetOfLength()), scratch, fail); // If the left side is another rope, give up. branchIfRope(output, fail); bind(¬Rope); Label isLatin1, done; // We have to check the left/right side for ropes, // because a TwoByte rope might have a Latin1 child. branchLatin1String(output, &isLatin1); - - loadStringChars(output, output); - load16ZeroExtend(BaseIndex(output, index, TimesTwo), output); + loadStringChars(output, scratch, CharEncoding::TwoByte); + load16ZeroExtend(BaseIndex(scratch, index, TimesTwo), output); jump(&done); bind(&isLatin1); - loadStringChars(output, output); - load8ZeroExtend(BaseIndex(output, index, TimesOne), output); + loadStringChars(output, scratch, CharEncoding::Latin1); + load8ZeroExtend(BaseIndex(scratch, index, TimesOne), output); bind(&done); } void MacroAssembler::loadStringIndexValue(Register str, Register dest, Label* fail) { MOZ_ASSERT(str != dest);
--- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -223,16 +223,18 @@ enum class CheckUnsafeCallWithABI { DontCheckHasExitFrame, // Don't check this callWithABI uses AutoUnsafeCallWithABI, for instance // because we're calling a simple helper function (like malloc or js_free) // that we can't change and/or that we know won't GC. DontCheckOther, }; +enum class CharEncoding { Latin1, TwoByte }; + // The public entrypoint for emitting assembly. Note that a MacroAssembler can // use cx->lifoAlloc, so take care not to interleave masm use with other // lifoAlloc use if one will be destroyed before the other. class MacroAssembler : public MacroAssemblerSpecific { MacroAssembler* thisFromCtor() { return this; } @@ -1363,16 +1365,28 @@ class MacroAssembler : public MacroAssem inline void cmp32Move32(Condition cond, Register lhs, Register rhs, Register src, Register dest) DEFINED_ON(arm, arm64, x86_shared); inline void cmp32Move32(Condition cond, Register lhs, const Address& rhs, Register src, Register dest) DEFINED_ON(arm, arm64, x86_shared); + inline void cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src, + Register dest) + DEFINED_ON(arm, arm64, x86, x64); + + inline void test32LoadPtr(Condition cond, const Address& addr, Imm32 mask, const Address& src, + Register dest) + DEFINED_ON(arm, arm64, x86, x64); + + inline void test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src, + Register dest) + DEFINED_ON(arm, arm64, x86, x64); + // Performs a bounds check and zeroes the index register if out-of-bounds // (to mitigate Spectre). inline void boundsCheck32ForLoad(Register index, Register length, Register scratch, Label* failure) DEFINED_ON(arm, arm64, x86_shared); inline void boundsCheck32ForLoad(Register index, const Address& length, Register scratch, Label* failure) DEFINED_ON(arm, arm64, x86_shared); @@ -1949,20 +1963,34 @@ class MacroAssembler : public MacroAssem loadPtr(Address(obj, JSObject::offsetOfGroup()), dest); loadPtr(Address(dest, ObjectGroup::offsetOfProto()), dest); } void loadStringLength(Register str, Register dest) { load32(Address(str, JSString::offsetOfLength()), dest); } - void loadStringChars(Register str, Register dest); + void loadStringChars(Register str, Register dest, CharEncoding encoding); + + void loadNonInlineStringChars(Register str, Register dest, CharEncoding encoding); + void loadNonInlineStringCharsForStore(Register str, Register dest); + void storeNonInlineStringChars(Register chars, Register str); + + void loadInlineStringChars(Register str, Register dest, CharEncoding encoding); + void loadInlineStringCharsForStore(Register str, Register dest); + void loadStringChar(Register str, Register index, Register output, Register scratch, Label* fail); + void loadRopeLeftChild(Register str, Register dest); + void storeRopeChildren(Register left, Register right, Register str); + + void loadDependentStringBase(Register str, Register dest); + void storeDependentStringBase(Register base, Register str); + void loadStringIndexValue(Register str, Register dest, Label* fail); void loadJSContext(Register dest); void loadJitActivation(Register dest) { loadJSContext(dest); loadPtr(Address(dest, offsetof(JSContext, activation_)), dest); }
--- a/js/src/jit/arm/MacroAssembler-arm-inl.h +++ b/js/src/jit/arm/MacroAssembler-arm-inl.h @@ -2140,26 +2140,53 @@ void MacroAssembler::cmp32Move32(Condition cond, Register lhs, Register rhs, Register src, Register dest) { cmp32(lhs, rhs); ma_mov(src, dest, LeaveCC, cond); } void +MacroAssembler::cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src, + Register dest) +{ + cmp32(lhs, rhs); + ma_mov(src, dest, LeaveCC, cond); +} + +void MacroAssembler::cmp32Move32(Condition cond, Register lhs, const Address& rhs, Register src, Register dest) { ScratchRegisterScope scratch(*this); SecondScratchRegisterScope scratch2(*this); ma_ldr(rhs, scratch, scratch2); cmp32Move32(cond, lhs, scratch, src, dest); } void +MacroAssembler::test32LoadPtr(Condition cond, const Address& addr, Imm32 mask, const Address& src, + Register dest) +{ + MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero); + test32(addr, mask); + ScratchRegisterScope scratch(*this); + ma_ldr(src, dest, scratch, Offset, cond); +} + +void +MacroAssembler::test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src, + Register dest) +{ + MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero); + test32(addr, mask); + ma_mov(src, dest, LeaveCC, cond); +} + +void MacroAssembler::boundsCheck32ForLoad(Register index, Register length, Register scratch, Label* failure) { MOZ_ASSERT(index != length); MOZ_ASSERT(length != scratch); MOZ_ASSERT(index != scratch); if (JitOptions.spectreIndexMasking)
--- a/js/src/jit/arm64/MacroAssembler-arm64-inl.h +++ b/js/src/jit/arm64/MacroAssembler-arm64-inl.h @@ -1722,16 +1722,50 @@ void MacroAssembler::cmp32Move32(Condition cond, Register lhs, const Address& rhs, Register src, Register dest) { cmp32(lhs, rhs); Csel(ARMRegister(dest, 32), ARMRegister(src, 32), ARMRegister(dest, 32), cond); } void +MacroAssembler::cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src, + Register dest) +{ + cmp32(lhs, rhs); + Csel(ARMRegister(dest, 64), ARMRegister(src, 64), ARMRegister(dest, 64), cond); +} + +void +MacroAssembler::test32LoadPtr(Condition cond, const Address& addr, Imm32 mask, const Address& src, + Register dest) +{ + MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero); + + // ARM64 does not support conditional loads, so we use a branch with a CSel + // (to prevent Spectre attacks). + vixl::UseScratchRegisterScope temps(this); + const ARMRegister scratch64 = temps.AcquireX(); + Label done; + branchTest32(Assembler::InvertCondition(cond), addr, mask, &done); + loadPtr(src, scratch64.asUnsized()); + Csel(ARMRegister(dest, 64), scratch64, ARMRegister(dest, 64), cond); + bind(&done); +} + +void +MacroAssembler::test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src, + Register dest) +{ + MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero); + test32(addr, mask); + Csel(ARMRegister(dest, 64), ARMRegister(src, 64), ARMRegister(dest, 64), cond); +} + +void MacroAssembler::boundsCheck32ForLoad(Register index, Register length, Register scratch, Label* failure) { MOZ_ASSERT(index != length); MOZ_ASSERT(length != scratch); MOZ_ASSERT(index != scratch); branch32(Assembler::BelowOrEqual, length, index, failure);
--- a/js/src/jit/x64/MacroAssembler-x64-inl.h +++ b/js/src/jit/x64/MacroAssembler-x64-inl.h @@ -815,16 +815,42 @@ MacroAssembler::branchTestMagic(Conditio } void MacroAssembler::branchToComputedAddress(const BaseIndex& address) { jmp(Operand(address)); } +void +MacroAssembler::cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src, + Register dest) +{ + cmp32(lhs, rhs); + cmovCCq(cond, Operand(src), dest); +} + +void +MacroAssembler::test32LoadPtr(Condition cond, const Address& addr, Imm32 mask, const Address& src, + Register dest) +{ + MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero); + test32(addr, mask); + cmovCCq(cond, Operand(src), dest); +} + +void +MacroAssembler::test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src, + Register dest) +{ + MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero); + test32(addr, mask); + cmovCCq(cond, Operand(src), dest); +} + // ======================================================================== // Truncate floating point. void MacroAssembler::truncateFloat32ToUInt64(Address src, Address dest, Register temp, FloatRegister floatTemp) { Label done;
--- a/js/src/jit/x86/MacroAssembler-x86-inl.h +++ b/js/src/jit/x86/MacroAssembler-x86-inl.h @@ -1004,16 +1004,42 @@ MacroAssembler::branchTestMagic(Conditio } void MacroAssembler::branchToComputedAddress(const BaseIndex& addr) { jmp(Operand(addr)); } +void +MacroAssembler::cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src, + Register dest) +{ + cmp32(lhs, rhs); + cmovCCl(cond, Operand(src), dest); +} + +void +MacroAssembler::test32LoadPtr(Condition cond, const Address& addr, Imm32 mask, const Address& src, + Register dest) +{ + MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero); + test32(addr, mask); + cmovCCl(cond, Operand(src), dest); +} + +void +MacroAssembler::test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src, + Register dest) +{ + MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero); + test32(addr, mask); + cmovCCl(cond, Operand(src), dest); +} + // ======================================================================== // Truncate floating point. void MacroAssembler::truncateFloat32ToUInt64(Address src, Address dest, Register temp, FloatRegister floatTemp) { Label done;
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -7251,16 +7251,19 @@ JS_SetGlobalJitCompilerOption(JSContext* jit::JitOptions.jumpThreshold = value; break; case JSJITCOMPILER_SIMULATOR_ALWAYS_INTERRUPT: jit::JitOptions.simulatorAlwaysInterrupt = !!value; break; case JSJITCOMPILER_SPECTRE_INDEX_MASKING: jit::JitOptions.spectreIndexMasking = !!value; break; + case JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS: + jit::JitOptions.spectreStringMitigations = !!value; + break; case JSJITCOMPILER_ASMJS_ATOMICS_ENABLE: jit::JitOptions.asmJSAtomicsEnable = !!value; break; case JSJITCOMPILER_WASM_FOLD_OFFSETS: jit::JitOptions.wasmFoldOffsets = !!value; break; case JSJITCOMPILER_ION_INTERRUPT_WITHOUT_SIGNAL: jit::JitOptions.ionInterruptWithoutSignals = !!value;
--- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -5907,17 +5907,18 @@ JS_SetOffthreadIonCompilationEnabled(JSC Register(ION_INTERRUPT_WITHOUT_SIGNAL, "ion.interrupt-without-signals") \ Register(ION_CHECK_RANGE_ANALYSIS, "ion.check-range-analysis") \ Register(BASELINE_ENABLE, "baseline.enable") \ Register(OFFTHREAD_COMPILATION_ENABLE, "offthread-compilation.enable") \ Register(FULL_DEBUG_CHECKS, "jit.full-debug-checks") \ Register(JUMP_THRESHOLD, "jump-threshold") \ Register(SIMULATOR_ALWAYS_INTERRUPT, "simulator.always-interrupt") \ Register(SPECTRE_INDEX_MASKING, "spectre.index-masking") \ - Register(ASMJS_ATOMICS_ENABLE, "asmjs.atomics.enable") \ + Register(SPECTRE_STRING_MITIGATIONS, "spectre.string-mitigations") \ +Register(ASMJS_ATOMICS_ENABLE, "asmjs.atomics.enable") \ Register(WASM_FOLD_OFFSETS, "wasm.fold-offsets") typedef enum JSJitCompilerOption { #define JIT_COMPILER_DECLARE(key, str) \ JSJITCOMPILER_ ## key, JIT_COMPILER_OPTIONS(JIT_COMPILER_DECLARE) #undef JIT_COMPILER_DECLARE
--- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -8518,22 +8518,25 @@ SetContextOptions(JSContext* cx, const O jit::JitOptions.disableCacheIR = false; else if (strcmp(str, "off") == 0) jit::JitOptions.disableCacheIR = true; else return OptionFailure("cache-ir-stubs", str); } if (const char* str = op.getStringOption("spectre-mitigations")) { - if (strcmp(str, "on") == 0) + if (strcmp(str, "on") == 0) { jit::JitOptions.spectreIndexMasking = true; - else if (strcmp(str, "off") == 0) + jit::JitOptions.spectreStringMitigations = true; + } else if (strcmp(str, "off") == 0) { jit::JitOptions.spectreIndexMasking = false; - else + jit::JitOptions.spectreStringMitigations = false; + } else { return OptionFailure("spectre-mitigations", str); + } } if (const char* str = op.getStringOption("ion-scalar-replacement")) { if (strcmp(str, "on") == 0) jit::JitOptions.disableScalarReplacement = false; else if (strcmp(str, "off") == 0) jit::JitOptions.disableScalarReplacement = true; else
--- a/js/src/vm/String.h +++ b/js/src/vm/String.h @@ -511,23 +511,28 @@ class JSString : public js::gc::TenuredC static size_t offsetOfLength() { return offsetof(JSString, d.u1.length); } static size_t offsetOfFlags() { return offsetof(JSString, d.u1.flags); } + private: + // To help avoid writing Spectre-unsafe code, we only allow MacroAssembler + // to call the method below. + friend class js::jit::MacroAssembler; static size_t offsetOfNonInlineChars() { static_assert(offsetof(JSString, d.s.u2.nonInlineCharsTwoByte) == offsetof(JSString, d.s.u2.nonInlineCharsLatin1), "nonInlineCharsTwoByte and nonInlineCharsLatin1 must have same offset"); return offsetof(JSString, d.s.u2.nonInlineCharsTwoByte); } + public: static const JS::TraceKind TraceKind = JS::TraceKind::String; #ifdef DEBUG void dump(); // Debugger-friendly stderr dump. void dump(js::GenericPrinter& out); void dumpNoNewline(js::GenericPrinter& out); void dumpCharsNoNewline(js::GenericPrinter& out); void dumpRepresentation(js::GenericPrinter& out, int indent) const; @@ -605,26 +610,31 @@ class JSRope : public JSString JSString* rightChild() const { MOZ_ASSERT(isRope()); return d.s.u3.right; } void traceChildren(JSTracer* trc); +#ifdef DEBUG + void dumpRepresentation(js::GenericPrinter& out, int indent) const; +#endif + + private: + // To help avoid writing Spectre-unsafe code, we only allow MacroAssembler + // to call the methods below. + friend class js::jit::MacroAssembler; + static size_t offsetOfLeft() { return offsetof(JSRope, d.s.u2.left); } static size_t offsetOfRight() { return offsetof(JSRope, d.s.u3.right); } - -#ifdef DEBUG - void dumpRepresentation(js::GenericPrinter& out, int indent) const; -#endif }; static_assert(sizeof(JSRope) == sizeof(JSString), "string subclasses must be binary-compatible with JSString"); class JSLinearString : public JSString { friend class JSString; @@ -737,23 +747,28 @@ class JSDependentString : public JSLinea MOZ_ASSERT(offset < base()->length()); return mozilla::Some(offset); } public: static inline JSLinearString* new_(JSContext* cx, JSLinearString* base, size_t start, size_t length); +#ifdef DEBUG + void dumpRepresentation(js::GenericPrinter& out, int indent) const; +#endif + + private: + // To help avoid writing Spectre-unsafe code, we only allow MacroAssembler + // to call the method below. + friend class js::jit::MacroAssembler; + inline static size_t offsetOfBase() { return offsetof(JSDependentString, d.s.u3.base); } - -#ifdef DEBUG - void dumpRepresentation(js::GenericPrinter& out, int indent) const; -#endif }; static_assert(sizeof(JSDependentString) == sizeof(JSString), "string subclasses must be binary-compatible with JSString"); class JSFlatString : public JSLinearString { /* Vacuous and therefore unimplemented. */ @@ -875,23 +890,27 @@ class JSInlineString : public JSFlatStri MOZ_ASSERT(JSString::isInline()); MOZ_ASSERT(hasTwoByteChars()); return d.inlineStorageTwoByte; } template<typename CharT> static bool lengthFits(size_t length); +#ifdef DEBUG + void dumpRepresentation(js::GenericPrinter& out, int indent) const; +#endif + + private: + // To help avoid writing Spectre-unsafe code, we only allow MacroAssembler + // to call the method below. + friend class js::jit::MacroAssembler; static size_t offsetOfInlineStorage() { return offsetof(JSInlineString, d.inlineStorageTwoByte); } - -#ifdef DEBUG - void dumpRepresentation(js::GenericPrinter& out, int indent) const; -#endif }; static_assert(sizeof(JSInlineString) == sizeof(JSString), "string subclasses must be binary-compatible with JSString"); /* * On 32-bit platforms, JSThinInlineString can store 7 Latin1 characters or 3 * TwoByte characters (excluding null terminator) inline. On 64-bit platforms,
--- a/js/xpconnect/src/XPCJSContext.cpp +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -804,16 +804,18 @@ ReloadPrefsCallback(const char* pref, vo bool werror = Preferences::GetBool(JS_OPTIONS_DOT_STR "werror"); bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict"); bool streams = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams"); bool spectreIndexMasking = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.index_masking"); + bool spectreStringMitigations = + Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.string_mitigations"); sSharedMemoryEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory"); #ifdef DEBUG sExtraWarningsForSystemJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict.debug"); #endif #ifdef JS_GC_ZEAL @@ -866,16 +868,18 @@ ReloadPrefsCallback(const char* pref, vo useBaselineEager ? 0 : baselineThreshold); JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_WARMUP_TRIGGER, useIonEager ? 0 : ionThreshold); #ifdef DEBUG JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_FULL_DEBUG_CHECKS, fullJitDebugChecks); #endif JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_INDEX_MASKING, spectreIndexMasking); + JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS, + spectreStringMitigations); } XPCJSContext::~XPCJSContext() { MOZ_COUNT_DTOR_INHERITED(XPCJSContext, CycleCollectedJSContext); // Elsewhere we abort immediately if XPCJSContext initialization fails. // Therefore the context must be non-null. MOZ_ASSERT(MaybeContext());
--- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -3626,16 +3626,17 @@ GetOrCreateRetainedDisplayListBuilder(ns nsresult nsLayoutUtils::PaintFrame(gfxContext* aRenderingContext, nsIFrame* aFrame, const nsRegion& aDirtyRegion, nscolor aBackstop, nsDisplayListBuilderMode aBuilderMode, PaintFrameFlags aFlags) { AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame", GRAPHICS); + typedef RetainedDisplayListBuilder::PartialUpdateResult PartialUpdateResult; #ifdef MOZ_DUMP_PAINTING if (!gPaintCountStack) { gPaintCountStack = new nsTArray<int>(); ClearOnShutdown(&gPaintCountStack); gPaintCountStack->AppendElement(0); } @@ -3783,16 +3784,17 @@ nsLayoutUtils::PaintFrame(gfxContext* aR } builder.ClearHaveScrollableDisplayPort(); if (builder.IsPaintingToWindow()) { MaybeCreateDisplayPortInFirstScrollFrameEncountered(aFrame, builder); } nsRect visibleRect = visibleRegion.GetBounds(); + PartialUpdateResult updateState = PartialUpdateResult::Failed; { AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame:BuildDisplayList", GRAPHICS); AUTO_PROFILER_TRACING("Paint", "DisplayList"); PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::DisplayList); TimeStamp dlStart = TimeStamp::Now(); @@ -3832,51 +3834,50 @@ nsLayoutUtils::PaintFrame(gfxContext* aR DisplayListChecker beforeMergeChecker; DisplayListChecker toBeMergedChecker; DisplayListChecker afterMergeChecker; // Attempt to do a partial build and merge into the existing list. // This calls BuildDisplayListForStacking context on a subset of the // viewport. - bool merged = false; - if (useRetainedBuilder) { if (gfxPrefs::LayoutVerifyRetainDisplayList()) { beforeMergeChecker.Set(&list, "BM"); } - merged = retainedBuilder->AttemptPartialUpdate( + updateState = retainedBuilder->AttemptPartialUpdate( aBackstop, beforeMergeChecker ? &toBeMergedChecker : nullptr); - if (merged && beforeMergeChecker) { + if ((updateState != PartialUpdateResult::Failed) && beforeMergeChecker) { afterMergeChecker.Set(&list, "AM"); } } - if (merged && + if ((updateState != PartialUpdateResult::Failed) && (gfxPrefs::LayoutDisplayListBuildTwice() || afterMergeChecker)) { - merged = false; + updateState = PartialUpdateResult::Failed; if (gfxPrefs::LayersDrawFPS()) { if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) { if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(lm)) { pt->dl2Ms() = (TimeStamp::Now() - dlStart).ToMilliseconds(); } } } dlStart = TimeStamp::Now(); } - if (!merged) { + if (updateState == PartialUpdateResult::Failed) { list.DeleteAll(&builder); builder.EnterPresShell(aFrame); builder.SetDirtyRect(visibleRect); builder.ClearRetainedWindowRegions(); aFrame->BuildDisplayListForStackingContext(&builder, &list); AddExtraBackgroundItems(builder, list, aFrame, canvasArea, visibleRegion, aBackstop); builder.LeavePresShell(aFrame, &list); + updateState = PartialUpdateResult::Updated; if (afterMergeChecker) { DisplayListChecker nonRetainedChecker(&list, "NR"); std::stringstream ss; ss << "**** Differences between retained-after-merged (AM) and " << "non-retained (NR) display lists:"; if (!nonRetainedChecker.CompareList(afterMergeChecker, ss)) { ss << "\n\n*** non-retained display items:"; @@ -3904,16 +3905,17 @@ nsLayoutUtils::PaintFrame(gfxContext* aR if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) { if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(lm)) { pt->dlMs() = (TimeStamp::Now() - dlStart).ToMilliseconds(); } } } } + MOZ_ASSERT(updateState != PartialUpdateResult::Failed); builder.Check(); Telemetry::AccumulateTimeDelta(Telemetry::PAINT_BUILD_DISPLAYLIST_TIME, startBuildDisplayList); bool consoleNeedsDisplayList = gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint(); #ifdef MOZ_DUMP_PAINTING FILE* savedDumpFile = gfxUtils::sDumpPaintFile; @@ -3978,16 +3980,19 @@ nsLayoutUtils::PaintFrame(gfxContext* aR flags |= nsDisplayList::PAINT_EXISTING_TRANSACTION; } if (aFlags & PaintFrameFlags::PAINT_NO_COMPOSITE) { flags |= nsDisplayList::PAINT_NO_COMPOSITE; } if (aFlags & PaintFrameFlags::PAINT_COMPRESSED) { flags |= nsDisplayList::PAINT_COMPRESSED; } + if (updateState == PartialUpdateResult::NoChange) { + flags |= nsDisplayList::PAINT_IDENTICAL_DISPLAY_LIST; + } TimeStamp paintStart = TimeStamp::Now(); RefPtr<LayerManager> layerManager = list.PaintRoot(&builder, aRenderingContext, flags); Telemetry::AccumulateTimeDelta(Telemetry::PAINT_RASTERIZE_TIME, paintStart); builder.Check();
--- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -189,26 +189,25 @@ nsPresContext::MakeColorPref(const nsStr } return result; } bool nsPresContext::IsDOMPaintEventPending() { - if (mFireAfterPaintEvents) { + if (!mTransactions.IsEmpty()) { return true; } nsRootPresContext* drpc = GetRootPresContext(); if (drpc && drpc->mRefreshDriver->ViewManagerFlushIsPending()) { // Since we're promising that there will be a MozAfterPaint event // fired, we record an empty invalidation in case display list // invalidation doesn't invalidate anything further. NotifyInvalidation(drpc->mRefreshDriver->LastTransactionId() + 1, nsRect(0, 0, 0, 0)); - NS_ASSERTION(mFireAfterPaintEvents, "Why aren't we planning to fire the event?"); return true; } return false; } void nsPresContext::PrefChangedCallback(const char* aPrefName, void* instance_data) { @@ -310,17 +309,16 @@ nsPresContext::nsPresContext(nsIDocument mIsEmulatingMedia(false), mIsGlyph(false), mUsesRootEMUnits(false), mUsesExChUnits(false), mCounterStylesDirty(true), mFontFeatureValuesDirty(true), mSuppressResizeReflow(false), mIsVisual(false), - mFireAfterPaintEvents(false), mIsChrome(false), mIsChromeOriginImage(false), mPaintFlashing(false), mPaintFlashingInitialized(false), mHasWarnedAboutPositionedTableParts(false), mHasWarnedAboutTooLargeDashedOrDottedRadius(false), mQuirkSheetAdded(false), mNeedsPrefUpdate(false), @@ -2591,52 +2589,57 @@ nsPresContext::NotifyInvalidation(uint64 nsRect rect(DevPixelsToAppUnits(clampedRect.x), DevPixelsToAppUnits(clampedRect.y), DevPixelsToAppUnits(clampedRect.width), DevPixelsToAppUnits(clampedRect.height)); NotifyInvalidation(aTransactionId, rect); } +nsPresContext::TransactionInvalidations* +nsPresContext::GetInvalidations(uint64_t aTransactionId) +{ + for (TransactionInvalidations& t : mTransactions) { + if (t.mTransactionId == aTransactionId) { + return &t; + } + } + return nullptr; +} + void nsPresContext::NotifyInvalidation(uint64_t aTransactionId, const nsRect& aRect) { MOZ_ASSERT(GetContainerWeak(), "Invalidation in detached pres context"); // If there is no paint event listener, then we don't need to fire // the asynchronous event. We don't even need to record invalidation. // MayHavePaintEventListener is pretty cheap and we could make it // even cheaper by providing a more efficient // nsPIDOMWindow::GetListenerManager. nsPresContext* pc; for (pc = this; pc; pc = pc->GetParentPresContext()) { - if (pc->mFireAfterPaintEvents) + TransactionInvalidations* transaction = pc->GetInvalidations(aTransactionId); + if (transaction) { break; - pc->mFireAfterPaintEvents = true; + } else { + transaction = pc->mTransactions.AppendElement(); + transaction->mTransactionId = aTransactionId; + } } if (!pc) { nsRootPresContext* rpc = GetRootPresContext(); if (rpc) { rpc->EnsureEventualDidPaintEvent(aTransactionId); } } - TransactionInvalidations* transaction = nullptr; - for (TransactionInvalidations& t : mTransactions) { - if (t.mTransactionId == aTransactionId) { - transaction = &t; - break; - } - } - if (!transaction) { - transaction = mTransactions.AppendElement(); - transaction->mTransactionId = aTransactionId; - } - + TransactionInvalidations* transaction = GetInvalidations(aTransactionId); + MOZ_ASSERT(transaction); transaction->mInvalidations.AppendElement(aRect); } /* static */ void nsPresContext::NotifySubDocInvalidation(ContainerLayer* aContainer, const nsIntRegion* aRegion) { ContainerLayerPresContext *data = @@ -2678,32 +2681,28 @@ nsPresContext::SetNotifySubDocInvalidati nsPresContext::ClearNotifySubDocInvalidationData(ContainerLayer* aContainer) { aContainer->SetUserData(&gNotifySubDocInvalidationData, nullptr); } struct NotifyDidPaintSubdocumentCallbackClosure { uint64_t mTransactionId; const mozilla::TimeStamp& mTimeStamp; - bool mNeedsAnotherDidPaintNotification; }; /* static */ bool nsPresContext::NotifyDidPaintSubdocumentCallback(nsIDocument* aDocument, void* aData) { NotifyDidPaintSubdocumentCallbackClosure* closure = static_cast<NotifyDidPaintSubdocumentCallbackClosure*>(aData); nsIPresShell* shell = aDocument->GetShell(); if (shell) { nsPresContext* pc = shell->GetPresContext(); if (pc) { pc->NotifyDidPaintForSubtree(closure->mTransactionId, closure->mTimeStamp); - if (pc->mFireAfterPaintEvents) { - closure->mNeedsAnotherDidPaintNotification = true; - } } } return true; } class DelayedFireDOMPaintEvent : public Runnable { public: DelayedFireDOMPaintEvent( @@ -2738,62 +2737,58 @@ public: void nsPresContext::NotifyDidPaintForSubtree(uint64_t aTransactionId, const mozilla::TimeStamp& aTimeStamp) { if (IsRoot()) { static_cast<nsRootPresContext*>(this)->CancelDidPaintTimers(aTransactionId); - if (!mFireAfterPaintEvents) { + if (mTransactions.IsEmpty()) { return; } } - if (!PresShell()->IsVisible() && !mFireAfterPaintEvents) { + if (!PresShell()->IsVisible() && mTransactions.IsEmpty()) { return; } // Non-root prescontexts fire MozAfterPaint to all their descendants // unconditionally, even if no invalidations have been collected. This is // because we don't want to eat the cost of collecting invalidations for // every subdocument (which would require putting every subdocument in its // own layer). bool sent = false; uint32_t i = 0; while (i < mTransactions.Length()) { if (mTransactions[i].mTransactionId <= aTransactionId) { - nsCOMPtr<nsIRunnable> ev = - new DelayedFireDOMPaintEvent(this, &mTransactions[i].mInvalidations, - mTransactions[i].mTransactionId, aTimeStamp); - nsContentUtils::AddScriptRunner(ev); - sent = true; + if (!mTransactions[i].mInvalidations.IsEmpty()) { + nsCOMPtr<nsIRunnable> ev = + new DelayedFireDOMPaintEvent(this, &mTransactions[i].mInvalidations, + mTransactions[i].mTransactionId, aTimeStamp); + nsContentUtils::AddScriptRunner(ev); + sent = true; + } mTransactions.RemoveElementAt(i); } else { i++; } } if (!sent) { nsTArray<nsRect> dummy; nsCOMPtr<nsIRunnable> ev = new DelayedFireDOMPaintEvent(this, &dummy, aTransactionId, aTimeStamp); nsContentUtils::AddScriptRunner(ev); } - NotifyDidPaintSubdocumentCallbackClosure closure = { aTransactionId, aTimeStamp, false }; + NotifyDidPaintSubdocumentCallbackClosure closure = { aTransactionId, aTimeStamp }; mDocument->EnumerateSubDocuments(nsPresContext::NotifyDidPaintSubdocumentCallback, &closure); - - if (!closure.mNeedsAnotherDidPaintNotification && - mTransactions.IsEmpty()) { - // Nothing more to do for the moment. - mFireAfterPaintEvents = false; - } } bool nsPresContext::HasCachedStyleData() { if (!mShell) { return false; }
--- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -1286,16 +1286,22 @@ protected: bool HasCachedStyleData(); // Creates a one-shot timer with the given aCallback & aDelay. // Returns a refcounted pointer to the timer (or nullptr on failure). already_AddRefed<nsITimer> CreateTimer(nsTimerCallbackFunc aCallback, const char* aName, uint32_t aDelay); + struct TransactionInvalidations { + uint64_t mTransactionId; + nsTArray<nsRect> mInvalidations; + }; + TransactionInvalidations* GetInvalidations(uint64_t aTransactionId); + // IMPORTANT: The ownership implicit in the following member variables // has been explicitly checked. If you add any members to this class, // please make the ownership explicit (pinkerton, scc). nsPresContextType mType; // the nsPresShell owns a strong reference to the nsPresContext, and is responsible // for nulling this pointer before it is destroyed nsIPresShell* MOZ_NON_OWNING_REF mShell; // [WEAK] @@ -1354,20 +1360,16 @@ protected: nsCOMPtr<nsITheme> mTheme; nsLanguageAtomService* mLangService; nsCOMPtr<nsIPrintSettings> mPrintSettings; nsCOMPtr<nsITimer> mPrefChangedTimer; mozilla::UniquePtr<nsBidi> mBidiEngine; - struct TransactionInvalidations { - uint64_t mTransactionId; - nsTArray<nsRect> mInvalidations; - }; AutoTArray<TransactionInvalidations, 4> mTransactions; // text performance metrics nsAutoPtr<gfxTextPerfMetrics> mTextPerf; nsAutoPtr<gfxMissingFontRecorder> mMissingFonts; nsRect mVisibleArea; @@ -1484,18 +1486,16 @@ protected: unsigned mFontFeatureValuesDirty : 1; // resize reflow is suppressed when the only change has been to zoom // the document rather than to change the document's dimensions unsigned mSuppressResizeReflow : 1; unsigned mIsVisual : 1; - unsigned mFireAfterPaintEvents : 1; - unsigned mIsChrome : 1; unsigned mIsChromeOriginImage : 1; // Should we paint flash in this context? Do not use this variable directly. // Use GetPaintFlashing() method instead. mutable unsigned mPaintFlashing : 1; mutable unsigned mPaintFlashingInitialized : 1;
--- a/layout/painting/RetainedDisplayListBuilder.cpp +++ b/layout/painting/RetainedDisplayListBuilder.cpp @@ -99,49 +99,55 @@ SelectAGRForFrame(nsIFrame* aFrame, Anim } // Removes any display items that belonged to a frame that was deleted, // and mark frames that belong to a different AGR so that get their // items built again. // TODO: We currently descend into all children even if we don't have an AGR // to mark, as child stacking contexts might. It would be nice if we could // jump into those immediately rather than walking the entire thing. -void +bool RetainedDisplayListBuilder::PreProcessDisplayList(nsDisplayList* aList, AnimatedGeometryRoot* aAGR) { + bool modified = false; nsDisplayList saved; while (nsDisplayItem* i = aList->RemoveBottom()) { if (i->HasDeletedFrame() || !i->CanBeReused()) { i->Destroy(&mBuilder); + modified = true; continue; } nsIFrame* f = i->Frame(); if (i->GetChildren()) { - PreProcessDisplayList(i->GetChildren(), SelectAGRForFrame(f, aAGR)); + if (PreProcessDisplayList(i->GetChildren(), SelectAGRForFrame(f, aAGR))) { + modified = true; + } } // TODO: We should be able to check the clipped bounds relative // to the common AGR (of both the existing item and the invalidated // frame) and determine if they can ever intersect. if (aAGR && i->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) { mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame()); + modified = true; } // TODO: This is here because we sometimes reuse the previous display list // completely. For optimization, we could only restore the state for reused // display items. i->RestoreState(); saved.AppendToTop(i); } aList->AppendToTop(&saved); aList->RestoreState(); + return modified; } bool IsSameItem(nsDisplayItem* aFirst, nsDisplayItem* aSecond) { return aFirst->Frame() == aSecond->Frame() && aFirst->GetPerFrameKey() == aSecond->GetPerFrameKey(); } @@ -194,42 +200,44 @@ void SwapAndRemove(nsTArray<T>& aArray, T last = aArray.LastElement(); aArray.LastElement() = aArray[aIndex]; aArray[aIndex] = last; } aArray.RemoveElementAt(aArray.Length() - 1); } -static void +static bool MergeFrameRects(nsDisplayLayerEventRegions* aOldItem, nsDisplayLayerEventRegions* aNewItem, nsDisplayLayerEventRegions::FrameRects nsDisplayLayerEventRegions::*aRectList, nsTArray<nsIFrame*>& aAddedFrames) { + bool modified = false; // Go through the old item's rect list and remove any rectangles // belonging to invalidated frames (deleted frames should // already be gone at this point) nsDisplayLayerEventRegions::FrameRects& oldRects = aOldItem->*aRectList; uint32_t i = 0; while (i < oldRects.mFrames.Length()) { // TODO: As mentioned in nsDisplayLayerEventRegions, this // operation might perform really poorly on a vector. nsIFrame* f = oldRects.mFrames[i]; if (IsAnyAncestorModified(f)) { MOZ_ASSERT(f != aOldItem->Frame()); f->RemoveDisplayItem(aOldItem); SwapAndRemove(oldRects.mFrames, i); SwapAndRemove(oldRects.mBoxes, i); + modified = true; } else { i++; } } if (!aNewItem) { - return; + return modified; } // Copy items from the source list to the dest list, but // only if the dest doesn't already include them. nsDisplayItem* destItem = aOldItem; nsDisplayLayerEventRegions::FrameRects* destRects = &(aOldItem->*aRectList); nsDisplayLayerEventRegions::FrameRects* srcRects = &(aNewItem->*aRectList); @@ -240,46 +248,51 @@ MergeFrameRects(nsDisplayLayerEventRegio // then add it! destRects->Add(f, srcRects->mBoxes[i]); // We also need to update RealDisplayItemData for 'f', // but that'll mess up this check for the following // FrameRects lists, so defer that until the end. aAddedFrames.AppendElement(f); MOZ_ASSERT(f != aOldItem->Frame()); + + modified = true; } } + return modified; } -void MergeLayerEventRegions(nsDisplayItem* aOldItem, +bool MergeLayerEventRegions(nsDisplayItem* aOldItem, nsDisplayItem* aNewItem) { nsDisplayLayerEventRegions* oldItem = static_cast<nsDisplayLayerEventRegions*>(aOldItem); nsDisplayLayerEventRegions* newItem = static_cast<nsDisplayLayerEventRegions*>(aNewItem); nsTArray<nsIFrame*> addedFrames; - MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHitRegion, addedFrames); - MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mMaybeHitRegion, addedFrames); - MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mDispatchToContentHitRegion, addedFrames); - MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mNoActionRegion, addedFrames); - MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHorizontalPanRegion, addedFrames); - MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mVerticalPanRegion, addedFrames); + bool modified = false; + modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHitRegion, addedFrames); + modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mMaybeHitRegion, addedFrames); + modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mDispatchToContentHitRegion, addedFrames); + modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mNoActionRegion, addedFrames); + modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHorizontalPanRegion, addedFrames); + modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mVerticalPanRegion, addedFrames); // MergeFrameRects deferred updating the display item data list during // processing so that earlier calls didn't change the result of later // ones. Fix that up now. for (nsIFrame* f : addedFrames) { if (!f->HasDisplayItem(aOldItem)) { f->AddDisplayItem(aOldItem); } } + return modified; } void RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount(nsDisplayItem* aItem) { MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT); nsSubDocumentFrame* subDocFrame = @@ -383,22 +396,24 @@ void UpdateASR(nsDisplayItem* aItem, * We then match A, add the new version the merged list and delete the old version. * * We then process the remainder of the old list, B is added (since it is valid, * and hasn't been mark as reused), C is destroyed since it's marked as reused and * is already present in the merged list. * * Merged List: C, A, B */ -void +bool RetainedDisplayListBuilder::MergeDisplayLists(nsDisplayList* aNewList, nsDisplayList* aOldList, nsDisplayList* aOutList, Maybe<const ActiveScrolledRoot*>& aOutContainerASR) { + bool modified = false; + nsDisplayList merged; const auto UseItem = [&](nsDisplayItem* aItem) { const ActiveScrolledRoot* itemClipASR = aItem->GetClipChain() ? aItem->GetClipChain()->mASR : nullptr; const ActiveScrolledRoot* finiteBoundsASR = ActiveScrolledRoot::PickDescendant( itemClipASR, aItem->GetActiveScrolledRoot()); if (!aOutContainerASR) { @@ -452,28 +467,32 @@ RetainedDisplayListBuilder::MergeDisplay // matched item. nsDisplayItem* old = nullptr; while ((old = aOldList->GetBottom()) && old != oldItem) { if (IsAnyAncestorModified(old->FrameForInvalidation())) { // The old item is invalid, discard it. oldListLookup.Remove({ old->Frame(), old->GetPerFrameKey() }); aOldList->RemoveBottom(); old->Destroy(&mBuilder); + modified = true; } else if (newListLookup.Get({ old->Frame(), old->GetPerFrameKey() })) { // This old item is also in the new list, but we haven't got to it yet. // Stop now, and we'll deal with it when we get to the new entry. + modified = true; break; } else { // Recurse into the child list (without a matching new list) to // ensure that we find and remove any invalidated items. if (old->GetChildren()) { nsDisplayList empty; Maybe<const ActiveScrolledRoot*> containerASRForChildren; - MergeDisplayLists(&empty, old->GetChildren(), - old->GetChildren(), containerASRForChildren); + if (MergeDisplayLists(&empty, old->GetChildren(), + old->GetChildren(), containerASRForChildren)) { + modified = true; + } UpdateASR(old, containerASRForChildren); old->UpdateBounds(&mBuilder); } aOldList->RemoveBottom(); ReuseItem(old); } } bool destroy = false; @@ -493,38 +512,44 @@ RetainedDisplayListBuilder::MergeDisplay // the new one to the list. if (destroy && oldItem->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS && !IsAnyAncestorModified(oldItem->FrameForInvalidation())) { // Event regions items don't have anything interesting other than // the lists of regions and frames, so we have no need to use the // newer item. Always use the old item instead since we assume it's // likely to have the bigger lists and merging will be quicker. - MergeLayerEventRegions(oldItem, newItem); + if (MergeLayerEventRegions(oldItem, newItem)) { + modified = true; + } ReuseItem(oldItem); newItem->Destroy(&mBuilder); } else { - if (!IsAnyAncestorModified(oldItem->FrameForInvalidation()) && - oldItem->GetChildren()) { + if (IsAnyAncestorModified(oldItem->FrameForInvalidation())) { + modified = true; + } else if (oldItem->GetChildren()) { MOZ_ASSERT(newItem->GetChildren()); Maybe<const ActiveScrolledRoot*> containerASRForChildren; - MergeDisplayLists(newItem->GetChildren(), oldItem->GetChildren(), - newItem->GetChildren(), containerASRForChildren); + if (MergeDisplayLists(newItem->GetChildren(), oldItem->GetChildren(), + newItem->GetChildren(), containerASRForChildren)) { + modified = true; + } UpdateASR(newItem, containerASRForChildren); newItem->UpdateBounds(&mBuilder); } if (destroy) { oldItem->Destroy(&mBuilder); } UseItem(newItem); } } else { // If there was no matching item in the old list, then we only need to // add the new item to the merged list. + modified = true; UseItem(newItem); } } } // Reuse the remaining valid items from the old display list. while (nsDisplayItem* old = aOldList->RemoveBottom()) { if (!IsAnyAncestorModified(old->FrameForInvalidation()) && @@ -532,31 +557,37 @@ RetainedDisplayListBuilder::MergeDisplay if (old->GetChildren()) { // We are calling MergeDisplayLists() to ensure that the display items // with modified or deleted children will be correctly handled. // Passing an empty new display list as an argument skips the merging // loop above and jumps back here. nsDisplayList empty; Maybe<const ActiveScrolledRoot*> containerASRForChildren; - MergeDisplayLists(&empty, old->GetChildren(), - old->GetChildren(), containerASRForChildren); + if (MergeDisplayLists(&empty, old->GetChildren(), + old->GetChildren(), containerASRForChildren)) { + modified = true; + } UpdateASR(old, containerASRForChildren); old->UpdateBounds(&mBuilder); } if (old->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) { - MergeLayerEventRegions(old, nullptr); + if (MergeLayerEventRegions(old, nullptr)) { + modified = true; + } } ReuseItem(old); } else { old->Destroy(&mBuilder); + modified = true; } } aOutList->AppendToTop(&merged); + return modified; } static void TakeAndAddModifiedAndFramesWithPropsFromRootFrame( nsTArray<nsIFrame*>* aModifiedFrames, nsTArray<nsIFrame*>* aFramesWithProps, nsIFrame* aRootFrame) { @@ -714,16 +745,18 @@ HandlePreserve3D(nsIFrame* aFrame, nsRec static void ProcessFrame(nsIFrame* aFrame, nsDisplayListBuilder& aBuilder, AnimatedGeometryRoot** aAGR, nsRect& aOverflow, nsIFrame* aStopAtFrame, nsTArray<nsIFrame*>& aOutFramesWithProps, const bool aStopAtStackingContext) { nsIFrame* currentFrame = aFrame; + aBuilder.MarkFrameForDisplayIfVisible(aFrame, aBuilder.RootReferenceFrame()); + while (currentFrame != aStopAtFrame) { CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n", currentFrame, !aStopAtStackingContext, aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height); currentFrame = HandlePreserve3D(currentFrame, aOverflow); // If the current frame is an OOF frame, DisplayListBuildingData needs to be @@ -764,16 +797,20 @@ ProcessFrame(nsIFrame* aFrame, nsDisplay // Convert 'aOverflow' into the coordinate space of the nearest stacking context // or display port ancestor and update 'currentFrame' to point to that frame. aOverflow = nsLayoutUtils::TransformFrameRectToAncestor(currentFrame, aOverflow, aStopAtFrame, nullptr, nullptr, /* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true, ¤tFrame); MOZ_ASSERT(currentFrame); + aOverflow.IntersectRect(aOverflow, currentFrame->GetVisualOverflowRectRelativeToSelf()); + if (aOverflow.IsEmpty()) { + break; + } if (nsLayoutUtils::FrameHasDisplayPort(currentFrame)) { CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame); nsIScrollableFrame* sf = do_QueryFrame(currentFrame); MOZ_ASSERT(sf); nsRect displayPort; DebugOnly<bool> hasDisplayPort = nsLayoutUtils::GetDisplayPort(currentFrame->GetContent(), &displayPort, @@ -920,28 +957,30 @@ RetainedDisplayListBuilder::ComputeRebui // might have an empty overflow rect. if (f == mBuilder.GetCaretFrame()) { overflow.UnionRect(overflow, mBuilder.GetCaretRect()); } ProcessFrame(f, mBuilder, &agr, overflow, mBuilder.RootReferenceFrame(), aOutFramesWithProps, true); - aOutDirty->UnionRect(*aOutDirty, overflow); - CRR_LOG("Adding area to root draw area: %d %d %d %d\n", - overflow.x, overflow.y, overflow.width, overflow.height); + if (!overflow.IsEmpty()) { + aOutDirty->UnionRect(*aOutDirty, overflow); + CRR_LOG("Adding area to root draw area: %d %d %d %d\n", + overflow.x, overflow.y, overflow.width, overflow.height); - // If we get changed frames from multiple AGRS, then just give up as it gets really complex to - // track which items would need to be marked in MarkFramesForDifferentAGR. - if (!*aOutModifiedAGR) { - CRR_LOG("Setting %p as root stacking context AGR\n", agr); - *aOutModifiedAGR = agr; - } else if (agr && *aOutModifiedAGR != agr) { - CRR_LOG("Found multiple AGRs in root stacking context, giving up\n"); - return false; + // If we get changed frames from multiple AGRS, then just give up as it gets really complex to + // track which items would need to be marked in MarkFramesForDifferentAGR. + if (!*aOutModifiedAGR) { + CRR_LOG("Setting %p as root stacking context AGR\n", agr); + *aOutModifiedAGR = agr; + } else if (agr && *aOutModifiedAGR != agr) { + CRR_LOG("Found multiple AGRs in root stacking context, giving up\n"); + return false; + } } } return true; } /* * A simple early exit heuristic to avoid slow partial display list rebuilds. @@ -984,117 +1023,134 @@ ClearFrameProps(nsTArray<nsIFrame*>& aFr f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingRect()); f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); } f->SetFrameIsModified(false); } } +class AutoClearFramePropsArray +{ +public: + AutoClearFramePropsArray() = default; + + ~AutoClearFramePropsArray() + { + ClearFrameProps(mFrames); + } + + nsTArray<nsIFrame*>& Frames() { return mFrames; } + + bool IsEmpty() const { return mFrames.IsEmpty(); } + +private: + nsTArray<nsIFrame*> mFrames; +}; + void RetainedDisplayListBuilder::ClearFramesWithProps() { - nsTArray<nsIFrame*> modifiedFrames; - nsTArray<nsIFrame*> framesWithProps; - GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames, &framesWithProps); - - ClearFrameProps(modifiedFrames); - ClearFrameProps(framesWithProps); + AutoClearFramePropsArray modifiedFrames; + AutoClearFramePropsArray framesWithProps; + GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames.Frames(), &framesWithProps.Frames()); } -bool +auto RetainedDisplayListBuilder::AttemptPartialUpdate( nscolor aBackstop, - mozilla::DisplayListChecker* aChecker) + mozilla::DisplayListChecker* aChecker) -> PartialUpdateResult { mBuilder.RemoveModifiedWindowRegions(); mBuilder.ClearWindowOpaqueRegion(); if (mBuilder.ShouldSyncDecodeImages()) { MarkFramesWithItemsAndImagesModified(&mList); } mBuilder.EnterPresShell(mBuilder.RootReferenceFrame()); - nsTArray<nsIFrame*> modifiedFrames; - nsTArray<nsIFrame*> framesWithProps; - GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames, &framesWithProps); + // We set the override dirty regions during ComputeRebuildRegion or in + // nsLayoutUtils::InvalidateForDisplayPortChange. The display port change also + // marks the frame modified, so those regions are cleared here as well. + AutoClearFramePropsArray modifiedFrames; + AutoClearFramePropsArray framesWithProps; + GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames.Frames(), &framesWithProps.Frames()); // Do not allow partial builds if the retained display list is empty, or if // ShouldBuildPartial heuristic fails. - const bool shouldBuildPartial = !mList.IsEmpty() && ShouldBuildPartial(modifiedFrames); + const bool shouldBuildPartial = !mList.IsEmpty() && ShouldBuildPartial(modifiedFrames.Frames()); if (mPreviousCaret != mBuilder.GetCaretFrame()) { if (mPreviousCaret) { if (mBuilder.MarkFrameModifiedDuringBuilding(mPreviousCaret)) { - modifiedFrames.AppendElement(mPreviousCaret); + modifiedFrames.Frames().AppendElement(mPreviousCaret); } } if (mBuilder.GetCaretFrame()) { if (mBuilder.MarkFrameModifiedDuringBuilding(mBuilder.GetCaretFrame())) { - modifiedFrames.AppendElement(mBuilder.GetCaretFrame()); + modifiedFrames.Frames().AppendElement(mBuilder.GetCaretFrame()); } } mPreviousCaret = mBuilder.GetCaretFrame(); } nsRect modifiedDirty; AnimatedGeometryRoot* modifiedAGR = nullptr; - bool merged = false; - if (shouldBuildPartial && - ComputeRebuildRegion(modifiedFrames, &modifiedDirty, - &modifiedAGR, framesWithProps)) { - modifiedDirty.IntersectRect(modifiedDirty, mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf()); - - PreProcessDisplayList(&mList, modifiedAGR); - - nsDisplayList modifiedDL; - if (!modifiedDirty.IsEmpty() || !framesWithProps.IsEmpty()) { - mBuilder.SetDirtyRect(modifiedDirty); - mBuilder.SetPartialUpdate(true); - mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder, &modifiedDL); - nsLayoutUtils::AddExtraBackgroundItems(mBuilder, modifiedDL, mBuilder.RootReferenceFrame(), - nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()), - mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf(), - aBackstop); - mBuilder.SetPartialUpdate(false); - - //printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n", - // modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, modifiedDirty.height); - //nsFrame::PrintDisplayList(&mBuilder, modifiedDL); + if (!shouldBuildPartial || + !ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty, + &modifiedAGR, framesWithProps.Frames())) { + mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList); + return PartialUpdateResult::Failed; + } - } else { - // TODO: We can also skip layer building and painting if - // PreProcessDisplayList didn't end up changing anything - // Invariant: display items should have their original state here. - // printf_stderr("Skipping display list building since nothing needed to be done\n"); - } - - if (aChecker) { - aChecker->Set(&modifiedDL, "TM"); - } + modifiedDirty.IntersectRect(modifiedDirty, mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf()); - // |modifiedDL| can sometimes be empty here. We still perform the - // display list merging to prune unused items (for example, items that - // are not visible anymore) from the old list. - // TODO: Optimization opportunity. In this case, MergeDisplayLists() - // unnecessarily creates a hashtable of the old items. - Maybe<const ActiveScrolledRoot*> dummy; - MergeDisplayLists(&modifiedDL, &mList, &mList, dummy); - - //printf_stderr("Painting --- Merged list:\n"); - //nsFrame::PrintDisplayList(&mBuilder, mList); - - merged = true; + PartialUpdateResult result = PartialUpdateResult::NoChange; + if (PreProcessDisplayList(&mList, modifiedAGR) || + !modifiedDirty.IsEmpty() || + !framesWithProps.IsEmpty()) { + result = PartialUpdateResult::Updated; } - mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList); + mBuilder.SetDirtyRect(modifiedDirty); + mBuilder.SetPartialUpdate(true); + + nsDisplayList modifiedDL; + mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder, &modifiedDL); + if (!modifiedDL.IsEmpty()) { + nsLayoutUtils::AddExtraBackgroundItems(mBuilder, modifiedDL, mBuilder.RootReferenceFrame(), + nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()), + mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf(), + aBackstop); + } + mBuilder.SetPartialUpdate(false); + + if (aChecker) { + aChecker->Set(&modifiedDL, "TM"); + } + + //printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n", + // modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, modifiedDirty.height); + //nsFrame::PrintDisplayList(&mBuilder, modifiedDL); - // We set the override dirty regions during ComputeRebuildRegion or in - // nsLayoutUtils::InvalidateForDisplayPortChange. The display port change also - // marks the frame modified, so those regions are cleared here as well. - ClearFrameProps(modifiedFrames); - ClearFrameProps(framesWithProps); + // |modifiedDL| can sometimes be empty here. We still perform the + // display list merging to prune unused items (for example, items that + // are not visible anymore) from the old list. + // TODO: Optimization opportunity. In this case, MergeDisplayLists() + // unnecessarily creates a hashtable of the old items. + // TODO: Ideally we could skip this if result is NoChange, but currently when + // we call RestoreState on nsDisplayWrapList it resets the clip to the base + // clip, and we need the UpdateBounds call (within MergeDisplayLists) to + // move it to the correct inner clip. + Maybe<const ActiveScrolledRoot*> dummy; + if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) { + result = PartialUpdateResult::Updated; + } - return merged; + //printf_stderr("Painting --- Merged list:\n"); + //nsFrame::PrintDisplayList(&mBuilder, mList); + + mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList); + return result; }
--- a/layout/painting/RetainedDisplayListBuilder.h +++ b/layout/painting/RetainedDisplayListBuilder.h @@ -24,33 +24,39 @@ struct RetainedDisplayListBuilder { { mList.DeleteAll(&mBuilder); } nsDisplayListBuilder* Builder() { return &mBuilder; } nsDisplayList* List() { return &mList; } - bool AttemptPartialUpdate(nscolor aBackstop, - mozilla::DisplayListChecker* aChecker); + enum class PartialUpdateResult { + Failed, + NoChange, + Updated + }; + + PartialUpdateResult AttemptPartialUpdate(nscolor aBackstop, + mozilla::DisplayListChecker* aChecker); /** * Iterates through the display list builder reference frame document and * subdocuments, and clears the modified frame lists from the root frames. * Also clears the frame properties set by RetainedDisplayListBuilder for all * the frames in the modified frame lists. */ void ClearFramesWithProps(); NS_DECLARE_FRAME_PROPERTY_DELETABLE(Cached, RetainedDisplayListBuilder) private: - void PreProcessDisplayList(nsDisplayList* aList, AnimatedGeometryRoot* aAGR); + bool PreProcessDisplayList(nsDisplayList* aList, AnimatedGeometryRoot* aAGR); - void MergeDisplayLists(nsDisplayList* aNewList, + bool MergeDisplayLists(nsDisplayList* aNewList, nsDisplayList* aOldList, nsDisplayList* aOutList, mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR); bool ComputeRebuildRegion(nsTArray<nsIFrame*>& aModifiedFrames, nsRect* aOutDirty, AnimatedGeometryRoot** aOutModifiedAGR, nsTArray<nsIFrame*>& aOutFramesWithProps);
--- a/layout/painting/nsDisplayItemTypesList.h +++ b/layout/painting/nsDisplayItemTypesList.h @@ -49,17 +49,17 @@ DECLARE_DISPLAY_ITEM_TYPE(PRINT_PLUGIN, DECLARE_DISPLAY_ITEM_TYPE(RANGE_FOCUS_RING, 0) DECLARE_DISPLAY_ITEM_TYPE(REMOTE, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(RESOLUTION, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(SCROLL_INFO_LAYER, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(SELECTION_OVERLAY, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR_REGION, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(SUBDOCUMENT, TYPE_RENDERS_NO_IMAGES) -DECLARE_DISPLAY_ITEM_TYPE(MASK, TYPE_RENDERS_NO_IMAGES) +DECLARE_DISPLAY_ITEM_TYPE(MASK, 0) DECLARE_DISPLAY_ITEM_TYPE(FILTER, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(SVG_OUTER_SVG, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(SVG_GEOMETRY, 0) DECLARE_DISPLAY_ITEM_TYPE(SVG_TEXT, 0) DECLARE_DISPLAY_ITEM_TYPE(SVG_WRAPPER, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_BACKGROUND, 0) DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_SELECTION, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_COLLAPSE, 0)
--- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -2415,16 +2415,110 @@ nsDisplayListBuilder::GetWidgetLayerMana } nsIWidget* window = RootReferenceFrame()->GetNearestWidget(); if (window) { return window->GetLayerManager(); } return nullptr; } +FrameLayerBuilder* +nsDisplayList::BuildLayers(nsDisplayListBuilder* aBuilder, + LayerManager* aLayerManager, + uint32_t aFlags, + bool aIsWidgetTransaction) +{ + nsIFrame* frame = aBuilder->RootReferenceFrame(); + nsPresContext* presContext = frame->PresContext(); + nsIPresShell* presShell = presContext->PresShell(); + + FrameLayerBuilder *layerBuilder = new FrameLayerBuilder(); + layerBuilder->Init(aBuilder, aLayerManager); + + if (aFlags & PAINT_COMPRESSED) { + layerBuilder->SetLayerTreeCompressionMode(); + } + + RefPtr<ContainerLayer> root; + { + AUTO_PROFILER_TRACING("Paint", "LayerBuilding"); + + if (XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) { + FrameLayerBuilder::InvalidateAllLayers(aLayerManager); + } + + if (aIsWidgetTransaction) { + layerBuilder->DidBeginRetainedLayerTransaction(aLayerManager); + } + + // Clear any ScrollMetadata that may have been set on the root layer on a + // previous paint. This paint will set new metrics if necessary, and if we + // don't clear the old one here, we may be left with extra metrics. + if (Layer* rootLayer = aLayerManager->GetRoot()) { + rootLayer->SetScrollMetadata(nsTArray<ScrollMetadata>()); + } + + ContainerLayerParameters containerParameters + (presShell->GetResolution(), presShell->GetResolution()); + + { + PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization); + + root = layerBuilder-> + BuildContainerLayerFor(aBuilder, aLayerManager, frame, nullptr, this, + containerParameters, nullptr); + + if (!record.GetStart().IsNull() && gfxPrefs::LayersDrawFPS()) { + if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(aLayerManager)) { + pt->flbMs() = (TimeStamp::Now() - record.GetStart()).ToMilliseconds(); + } + } + } + + if (!root) { + return nullptr; + } + // Root is being scaled up by the X/Y resolution. Scale it back down. + root->SetPostScale(1.0f/containerParameters.mXScale, + 1.0f/containerParameters.mYScale); + root->SetScaleToResolution(presShell->ScaleToResolution(), + containerParameters.mXScale); + + auto callback = [root](FrameMetrics::ViewID aScrollId) -> bool { + return nsLayoutUtils::ContainsMetricsWithId(root, aScrollId); + }; + if (Maybe<ScrollMetadata> rootMetadata = nsLayoutUtils::GetRootMetadata( + aBuilder, root->Manager(), containerParameters, callback)) { + root->SetScrollMetadata(rootMetadata.value()); + } + + // NS_WARNING is debug-only, so don't even bother checking the conditions in + // a release build. +#ifdef DEBUG + bool usingDisplayport = false; + if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) { + nsIContent* content = rootScrollFrame->GetContent(); + if (content) { + usingDisplayport = nsLayoutUtils::HasDisplayPort(content); + } + } + if (usingDisplayport && + !(root->GetContentFlags() & Layer::CONTENT_OPAQUE) && + SpammyLayoutWarningsEnabled()) { + // See bug 693938, attachment 567017 + NS_WARNING("Transparent content with displayports can be expensive."); + } +#endif + + aLayerManager->SetRoot(root); + layerBuilder->WillEndTransaction(); + } + return layerBuilder; +} + /** * We paint by executing a layer manager transaction, constructing a * single layer representing the display list, and then making it the * root of the layer manager, drawing into the PaintedLayers. */ already_AddRefed<LayerManager> nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, uint32_t aFlags) @@ -2507,177 +2601,111 @@ already_AddRefed<LayerManager> nsDisplay return layerManager.forget(); } NotifySubDocInvalidationFunc computeInvalidFunc = presContext->MayHavePaintEventListenerInSubDocument() ? nsPresContext::NotifySubDocInvalidation : 0; UniquePtr<LayerProperties> props; - RefPtr<ContainerLayer> root; - - // Store the existing layer builder to reinstate it on return. - FrameLayerBuilder *oldBuilder = layerManager->GetLayerBuilder(); - - FrameLayerBuilder *layerBuilder = new FrameLayerBuilder(); - layerBuilder->Init(aBuilder, layerManager); - - if (aFlags & PAINT_COMPRESSED) { - layerBuilder->SetLayerTreeCompressionMode(); - } - - { - AUTO_PROFILER_TRACING("Paint", "LayerBuilding"); - - if (doBeginTransaction) { - if (aCtx) { - if (!layerManager->BeginTransactionWithTarget(aCtx)) { - return nullptr; - } - } else { - if (!layerManager->BeginTransaction()) { - return nullptr; - } + + bool computeInvalidRect = (computeInvalidFunc || + (!layerManager->IsCompositingCheap() && layerManager->NeedsWidgetInvalidation())) && + widgetTransaction; + + if (computeInvalidRect) { + props = Move(LayerProperties::CloneFrom(layerManager->GetRoot())); + } + + if (doBeginTransaction) { + if (aCtx) { + if (!layerManager->BeginTransactionWithTarget(aCtx)) { + return nullptr; } - } - - if (XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) { - FrameLayerBuilder::InvalidateAllLayers(layerManager); - } - - if (widgetTransaction) { - layerBuilder->DidBeginRetainedLayerTransaction(layerManager); - } - - bool computeInvalidRect = (computeInvalidFunc || - (!layerManager->IsCompositingCheap() && layerManager->NeedsWidgetInvalidation())) && - widgetTransaction; - - if (computeInvalidRect) { - props = Move(LayerProperties::CloneFrom(layerManager->GetRoot())); - } - - // Clear any ScrollMetadata that may have been set on the root layer on a - // previous paint. This paint will set new metrics if necessary, and if we - // don't clear the old one here, we may be left with extra metrics. - if (Layer* rootLayer = layerManager->GetRoot()) { - rootLayer->SetScrollMetadata(nsTArray<ScrollMetadata>()); - } - - ContainerLayerParameters containerParameters - (presShell->GetResolution(), presShell->GetResolution()); - - { - PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization); - - root = layerBuilder-> - BuildContainerLayerFor(aBuilder, layerManager, frame, nullptr, this, - containerParameters, nullptr); - - if (!record.GetStart().IsNull() && gfxPrefs::LayersDrawFPS()) { - if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(layerManager)) { - pt->flbMs() = (TimeStamp::Now() - record.GetStart()).ToMilliseconds(); - } + } else { + if (!layerManager->BeginTransaction()) { + return nullptr; } } - - if (!root) { - layerManager->SetUserData(&gLayerManagerLayerBuilder, oldBuilder); - return nullptr; - } - // Root is being scaled up by the X/Y resolution. Scale it back down. - root->SetPostScale(1.0f/containerParameters.mXScale, - 1.0f/containerParameters.mYScale); - root->SetScaleToResolution(presShell->ScaleToResolution(), - containerParameters.mXScale); - - auto callback = [root](FrameMetrics::ViewID aScrollId) -> bool { - return nsLayoutUtils::ContainsMetricsWithId(root, aScrollId); - }; - if (Maybe<ScrollMetadata> rootMetadata = nsLayoutUtils::GetRootMetadata( - aBuilder, root->Manager(), containerParameters, callback)) { - root->SetScrollMetadata(rootMetadata.value()); - } - - // NS_WARNING is debug-only, so don't even bother checking the conditions in - // a release build. -#ifdef DEBUG - bool usingDisplayport = false; - if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) { - nsIContent* content = rootScrollFrame->GetContent(); - if (content) { - usingDisplayport = nsLayoutUtils::HasDisplayPort(content); - } - } - if (usingDisplayport && - !(root->GetContentFlags() & Layer::CONTENT_OPAQUE) && - SpammyLayoutWarningsEnabled()) { - // See bug 693938, attachment 567017 - NS_WARNING("Transparent content with displayports can be expensive."); - } -#endif - - layerManager->SetRoot(root); - layerBuilder->WillEndTransaction(); - } - - if (widgetTransaction || - // SVG-as-an-image docs don't paint as part of the retained layer tree, - // but they still need the invalidation state bits cleared in order for - // invalidation for CSS/SMIL animation to work properly. - (document && document->IsBeingUsedAsImage())) { - frame->ClearInvalidationStateBits(); } bool temp = aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap()); LayerManager::EndTransactionFlags flags = LayerManager::END_DEFAULT; if (layerManager->NeedsWidgetInvalidation()) { if (aFlags & PAINT_NO_COMPOSITE) { flags = LayerManager::END_NO_COMPOSITE; } } else { // Client layer managers never composite directly, so // we don't need to worry about END_NO_COMPOSITE. if (aBuilder->WillComputePluginGeometry()) { flags = LayerManager::END_NO_REMOTE_COMPOSITE; } } - // If this is the content process, we ship plugin geometry updates over with layer - // updates, so calculate that now before we call EndTransaction. - nsRootPresContext* rootPresContext = presContext->GetRootPresContext(); - if (rootPresContext && XRE_IsContentProcess()) { - if (aBuilder->WillComputePluginGeometry()) { - rootPresContext->ComputePluginGeometryUpdates(aBuilder->RootReferenceFrame(), aBuilder, this); - } - // The layer system caches plugin configuration information for forwarding - // with layer updates which needs to get set during reflow. This must be - // called even if there are no windowed plugins in the page. - rootPresContext->CollectPluginGeometryUpdates(layerManager); - } - MaybeSetupTransactionIdAllocator(layerManager, presContext); - layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, - aBuilder, flags); + // Store the existing layer builder to reinstate it on return. + FrameLayerBuilder *oldBuilder = layerManager->GetLayerBuilder(); + FrameLayerBuilder *layerBuilder = nullptr; + + bool sent = false; + if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) { + sent = layerManager->EndEmptyTransaction(flags); + } + + if (!sent) { + layerBuilder = BuildLayers(aBuilder, layerManager, + aFlags, widgetTransaction); + + if (!layerBuilder) { + layerManager->SetUserData(&gLayerManagerLayerBuilder, oldBuilder); + return nullptr; + } + + // If this is the content process, we ship plugin geometry updates over with layer + // updates, so calculate that now before we call EndTransaction. + nsRootPresContext* rootPresContext = presContext->GetRootPresContext(); + if (rootPresContext && XRE_IsContentProcess()) { + if (aBuilder->WillComputePluginGeometry()) { + rootPresContext->ComputePluginGeometryUpdates(aBuilder->RootReferenceFrame(), aBuilder, this); + } + // The layer system caches plugin configuration information for forwarding + // with layer updates which needs to get set during reflow. This must be + // called even if there are no windowed plugins in the page. + rootPresContext->CollectPluginGeometryUpdates(layerManager); + } + + layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, + aBuilder, flags); + layerBuilder->DidEndTransaction(); + } + + if (widgetTransaction || + // SVG-as-an-image docs don't paint as part of the retained layer tree, + // but they still need the invalidation state bits cleared in order for + // invalidation for CSS/SMIL animation to work properly. + (document && document->IsBeingUsedAsImage())) { + frame->ClearInvalidationStateBits(); + } + aBuilder->SetIsCompositingCheap(temp); - layerBuilder->DidEndTransaction(); if (document && widgetTransaction) { TriggerPendingAnimations(document, layerManager->GetAnimationReadyTime()); } nsIntRegion invalid; bool areaOverflowed = false; if (props) { - if (!props->ComputeDifferences(root, invalid, computeInvalidFunc)) { + if (!props->ComputeDifferences(layerManager->GetRoot(), invalid, computeInvalidFunc)) { areaOverflowed = true; } } else if (widgetTransaction) { - LayerProperties::ClearInvalidations(root); + LayerProperties::ClearInvalidations(layerManager->GetRoot()); } bool shouldInvalidate = layerManager->NeedsWidgetInvalidation(); if (view) { if (props && !areaOverflowed) { if (!invalid.IsEmpty()) { nsIntRect bounds = invalid.GetBounds(); nsRect rect(presContext->DevPixelsToAppUnits(bounds.x),
--- a/layout/painting/nsDisplayList.h +++ b/layout/painting/nsDisplayList.h @@ -3059,21 +3059,27 @@ public: * We return the layer manager used for painting --- mainly so that * callers can dump its layer tree if necessary. */ enum { PAINT_DEFAULT = 0, PAINT_USE_WIDGET_LAYERS = 0x01, PAINT_EXISTING_TRANSACTION = 0x04, PAINT_NO_COMPOSITE = 0x08, - PAINT_COMPRESSED = 0x10 + PAINT_COMPRESSED = 0x10, + PAINT_IDENTICAL_DISPLAY_LIST = 0x20 }; already_AddRefed<LayerManager> PaintRoot(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, uint32_t aFlags); + + mozilla::FrameLayerBuilder* BuildLayers(nsDisplayListBuilder* aBuilder, + LayerManager* aLayerManager, + uint32_t aFlags, + bool aIsWidgetTransaction); /** * Get the bounds. Takes the union of the bounds of all children. * The result is not cached. */ nsRect GetBounds(nsDisplayListBuilder* aBuilder) const; /** * Get this list's bounds, respecting clips relative to aASR. The result is @@ -4732,17 +4738,17 @@ private: { for (nsIFrame* f : aFrameRects.mFrames) { if (f != mFrame) { f->RemoveDisplayItem(this); } } } - friend void MergeLayerEventRegions(nsDisplayItem*, nsDisplayItem*); + friend bool MergeLayerEventRegions(nsDisplayItem*, nsDisplayItem*); // Relative to aFrame's reference frame. // These are the points that are definitely in the hit region. FrameRects mHitRegion; // These are points that may or may not be in the hit region. Only main-thread // event handling can tell for sure (e.g. because complex shapes are present). FrameRects mMaybeHitRegion; // These are points that need to be dispatched to the content thread for
new file mode 100644 --- /dev/null +++ b/layout/reftests/display-list/1436189-1-ref.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html lang="en"> +<body> +<div style="opacity:0.5" id="hi"> + <div style="position:fixed; width:200px; height:200px; background-color:blue"></div> +</div> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/display-list/1436189-1.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html lang="en" class="reftest-wait"> +<body> +<div style="opacity:0.5" id="hi"> + <div style="position:fixed; width:200px; height:200px; background-color:blue"></div> +</div> + +<script> + +function doTest() +{ + var opacityElement = document.getElementById("hi"); + opacityElement.style.left = '100px'; + document.documentElement.removeAttribute("class"); +} + +document.addEventListener("MozReftestInvalidate", doTest); +</script> +</body> +</html>
--- a/layout/reftests/display-list/reftest.list +++ b/layout/reftests/display-list/reftest.list @@ -14,8 +14,9 @@ fuzzy(1,235200) == 1413073.html 1413073- == 1416291.html 1416291-ref.html == 1417601-1.html 1417601-1-ref.html == 1418945-1.html 1418945-1-ref.html skip-if(Android) == 1428993-1.html 1428993-1-ref.html == 1428993-2.html 1428993-2-ref.html needs-focus == 1429027-1.html 1429027-1-ref.html == 1432553-1.html 1432553-1-ref.html == 1432553-2.html 1432553-2-ref.html +== 1436189-1.html 1436189-1-ref.html
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -1547,16 +1547,17 @@ pref("javascript.options.showInConsole", pref("javascript.options.shared_memory", false); pref("javascript.options.throw_on_debuggee_would_run", false); pref("javascript.options.dump_stack_on_debuggee_would_run", false); // Spectre security vulnerability mitigations. pref("javascript.options.spectre.index_masking", true); +pref("javascript.options.spectre.string_mitigations", false); // Streams API pref("javascript.options.streams", false); // advanced prefs pref("advanced.mailftp", false); pref("image.animation_mode", "normal");
--- a/toolkit/content/process-content.js +++ b/toolkit/content/process-content.js @@ -44,21 +44,23 @@ if (gInContentProcess) { if (msg.name != "Memory:GetSummary") { return; } let pid = Services.appinfo.processID; let memMgr = Cc["@mozilla.org/memory-reporter-manager;1"] .getService(Ci.nsIMemoryReporterManager); let rss = memMgr.resident; let uss = memMgr.residentUnique; + let ghosts = memMgr.ghostWindows; Services.cpmm.sendAsyncMessage("Memory:Summary", { pid, summary: { uss, rss, + ghosts, } }); }, observe(subject, topic, data) { switch (topic) { case "inner-window-destroyed": { // Forward inner-window-destroyed notifications with the
--- a/toolkit/modules/Memory.jsm +++ b/toolkit/modules/Memory.jsm @@ -17,20 +17,22 @@ this.Memory = { * process. * @returns Promise * @resolves JS Object * An Object in the following format: * { * "parent": { * uss: <int>, * rss: <int>, + * ghosts: <int>, * }, * <pid>: { * uss: <int>, * rss: <int>, + * ghosts: <int>, * }, * ... * } */ summary() { if (!this._pendingPromise) { this._pendingPromise = new Promise((resolve) => { this._pendingResolve = resolve; @@ -60,17 +62,18 @@ this.Memory = { finish() { // Code to gather the USS and RSS values for the parent process. This // functions the same way as in process-content.js. let memMgr = Cc["@mozilla.org/memory-reporter-manager;1"] .getService(Ci.nsIMemoryReporterManager); let rss = memMgr.resident; let uss = memMgr.residentUnique; - this._summaries.Parent = { uss, rss }; + let ghosts = memMgr.ghostWindows; + this._summaries.Parent = { uss, rss, ghosts }; this._pendingResolve(this._summaries); this._pendingResolve = null; this._summaries = null; this._pendingPromise = null; clearTimeout(this._pendingTimeout); Services.ppmm.removeMessageListener("Memory:Summary", this); } };
--- a/widget/gtk/nsDragService.cpp +++ b/widget/gtk/nsDragService.cpp @@ -300,32 +300,34 @@ GetGtkWindow(nsIDOMDocument *aDocument) return GTK_WINDOW(toplevel); } // nsIDragService NS_IMETHODIMP nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode, + const nsACString& aPrincipalURISpec, nsIArray * aArrayTransferables, nsIScriptableRegion * aRegion, uint32_t aActionType, nsContentPolicyType aContentPolicyType = nsIContentPolicy::TYPE_OTHER) { MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::InvokeDragSession")); // If the previous source drag has not yet completed, signal handlers need // to be removed from sGrabWidget and dragend needs to be dispatched to // the source node, but we can't call EndDragSession yet because we don't // know whether or not the drag succeeded. if (mSourceNode) return NS_ERROR_NOT_AVAILABLE; - return nsBaseDragService::InvokeDragSession(aDOMNode, aArrayTransferables, + return nsBaseDragService::InvokeDragSession(aDOMNode, aPrincipalURISpec, + aArrayTransferables, aRegion, aActionType, aContentPolicyType); } // nsBaseDragService nsresult nsDragService::InvokeDragSessionImpl(nsIArray* aArrayTransferables, nsIScriptableRegion* aRegion,
--- a/widget/gtk/nsDragService.h +++ b/widget/gtk/nsDragService.h @@ -59,16 +59,17 @@ public: NS_DECL_NSIOBSERVER // nsBaseDragService virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables, nsIScriptableRegion* aRegion, uint32_t aActionType) override; // nsIDragService NS_IMETHOD InvokeDragSession (nsIDOMNode *aDOMNode, + const nsACString& aPrincipalURISpec, nsIArray * anArrayTransferables, nsIScriptableRegion * aRegion, uint32_t aActionType, nsContentPolicyType aContentPolicyType) override; NS_IMETHOD StartDragSession() override; NS_IMETHOD EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) override; // nsIDragSession
--- a/widget/nsBaseDragService.cpp +++ b/widget/nsBaseDragService.cpp @@ -164,16 +164,29 @@ NS_IMETHODIMP nsBaseDragService::GetSourceNode(nsIDOMNode** aSourceNode) { *aSourceNode = mSourceNode.get(); NS_IF_ADDREF(*aSourceNode); return NS_OK; } +NS_IMETHODIMP +nsBaseDragService::GetTriggeringPrincipalURISpec(nsACString& aPrincipalURISpec) +{ + aPrincipalURISpec = mTriggeringPrincipalURISpec; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseDragService::SetTriggeringPrincipalURISpec(const nsACString& aPrincipalURISpec) +{ + mTriggeringPrincipalURISpec = aPrincipalURISpec; + return NS_OK; +} //------------------------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::GetData(nsITransferable * aTransferable, uint32_t aItemIndex) { return NS_ERROR_FAILURE; @@ -200,53 +213,57 @@ nsBaseDragService::SetDataTransfer(nsIDO { mDataTransfer = aDataTransfer; return NS_OK; } //------------------------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::InvokeDragSession(nsIDOMNode *aDOMNode, + const nsACString& aPrincipalURISpec, nsIArray* aTransferableArray, nsIScriptableRegion* aDragRgn, uint32_t aActionType, nsContentPolicyType aContentPolicyType = nsIContentPolicy::TYPE_OTHER) { AUTO_PROFILER_LABEL("nsBaseDragService::InvokeDragSession", OTHER); NS_ENSURE_TRUE(aDOMNode, NS_ERROR_INVALID_ARG); NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE); // stash the document of the dom node nsCOMPtr<nsINode> node = do_QueryInterface(aDOMNode); mSourceDocument = do_QueryInterface(node->OwnerDoc()); + mTriggeringPrincipalURISpec.Assign(aPrincipalURISpec); mSourceNode = aDOMNode; mContentPolicyType = aContentPolicyType; mEndDragPoint = LayoutDeviceIntPoint(0, 0); // When the mouse goes down, the selection code starts a mouse // capture. However, this gets in the way of determining drag // feedback for things like trees because the event coordinates // are in the wrong coord system, so turn off mouse capture. nsIPresShell::ClearMouseCapture(nullptr); nsresult rv = InvokeDragSessionImpl(aTransferableArray, aDragRgn, aActionType); if (NS_FAILED(rv)) { mSourceNode = nullptr; + mTriggeringPrincipalURISpec.Truncate(0); mSourceDocument = nullptr; } return rv; } NS_IMETHODIMP nsBaseDragService::InvokeDragSessionWithImage(nsIDOMNode* aDOMNode, + const nsACString& aPrincipalURISpec, nsIArray* aTransferableArray, nsIScriptableRegion* aRegion, uint32_t aActionType, nsIDOMNode* aImage, int32_t aImageX, int32_t aImageY, nsIDOMDragEvent* aDragEvent, nsIDOMDataTransfer* aDataTransfer) { @@ -260,31 +277,33 @@ nsBaseDragService::InvokeDragSessionWith mDragPopup = nullptr; mImage = aImage; mImageOffset = CSSIntPoint(aImageX, aImageY); aDragEvent->GetScreenX(&mScreenPosition.x); aDragEvent->GetScreenY(&mScreenPosition.y); aDragEvent->GetMozInputSource(&mInputSource); - nsresult rv = InvokeDragSession(aDOMNode, aTransferableArray, + nsresult rv = InvokeDragSession(aDOMNode, aPrincipalURISpec, + aTransferableArray, aRegion, aActionType, nsIContentPolicy::TYPE_INTERNAL_IMAGE); if (NS_FAILED(rv)) { mImage = nullptr; mHasImage = false; mDataTransfer = nullptr; } return rv; } NS_IMETHODIMP nsBaseDragService::InvokeDragSessionWithSelection(nsISelection* aSelection, + const nsACString& aPrincipalURISpec, nsIArray* aTransferableArray, uint32_t aActionType, nsIDOMDragEvent* aDragEvent, nsIDOMDataTransfer* aDataTransfer) { NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE); @@ -301,17 +320,18 @@ nsBaseDragService::InvokeDragSessionWith aDragEvent->GetMozInputSource(&mInputSource); // just get the focused node from the selection // XXXndeakin this should actually be the deepest node that contains both // endpoints of the selection nsCOMPtr<nsIDOMNode> node; aSelection->GetFocusNode(getter_AddRefs(node)); - nsresult rv = InvokeDragSession(node, aTransferableArray, + nsresult rv = InvokeDragSession(node, aPrincipalURISpec, + aTransferableArray, nullptr, aActionType, nsIContentPolicy::TYPE_OTHER); if (NS_FAILED(rv)) { mHasImage = false; mSelection = nullptr; mDataTransfer = nullptr; } @@ -417,16 +437,17 @@ nsBaseDragService::EndDragSession(bool a } mDoingDrag = false; mCanDrop = false; // release the source we've been holding on to. mSourceDocument = nullptr; mSourceNode = nullptr; + mTriggeringPrincipalURISpec.Truncate(0); mSelection = nullptr; mDataTransfer = nullptr; mHasImage = false; mUserCancelled = false; mDragPopup = nullptr; mImage = nullptr; mImageOffset = CSSIntPoint(); mScreenPosition = CSSIntPoint();
--- a/widget/nsBaseDragService.h +++ b/widget/nsBaseDragService.h @@ -9,16 +9,17 @@ #include "nsIDragService.h" #include "nsIDragSession.h" #include "nsITransferable.h" #include "nsIDOMDocument.h" #include "nsIDOMDataTransfer.h" #include "nsCOMPtr.h" #include "nsRect.h" #include "nsPoint.h" +#include "nsString.h" #include "mozilla/RefPtr.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/HTMLCanvasElement.h" #include "nsTArray.h" #include "Units.h" // translucency level for drag images #define DRAG_TRANSLUCENCY 0.65 @@ -154,16 +155,17 @@ protected: bool mDragEventDispatchedToChildProcess; uint32_t mDragAction; uint32_t mDragActionFromChildProcess; nsSize mTargetSize; nsCOMPtr<nsIDOMNode> mSourceNode; + nsCString mTriggeringPrincipalURISpec; nsCOMPtr<nsIDOMDocument> mSourceDocument; // the document at the drag source. will be null // if it came from outside the app. nsContentPolicyType mContentPolicyType; // the contentpolicy type passed to the channel // when initiating the drag session nsCOMPtr<nsIDOMDataTransfer> mDataTransfer; // used to determine the image to appear on the cursor while dragging nsCOMPtr<nsIDOMNode> mImage;
--- a/widget/nsDragServiceProxy.cpp +++ b/widget/nsDragServiceProxy.cpp @@ -24,32 +24,54 @@ NS_IMPL_ISUPPORTS_INHERITED0(nsDragServi nsDragServiceProxy::nsDragServiceProxy() { } nsDragServiceProxy::~nsDragServiceProxy() { } +static void +GetPrincipalURIFromNode(nsCOMPtr<nsIDOMNode>& sourceNode, + nsCString& aPrincipalURISpec) +{ + nsCOMPtr<nsINode> node = do_QueryInterface(sourceNode); + if (!node) { + return; + } + + nsCOMPtr<nsIPrincipal> principal = node->NodePrincipal(); + nsCOMPtr<nsIURI> principalURI; + nsresult rv = principal->GetURI(getter_AddRefs(principalURI)); + if (NS_FAILED(rv)) { + return; + } + + principalURI->GetSpec(aPrincipalURISpec); +} + nsresult nsDragServiceProxy::InvokeDragSessionImpl(nsIArray* aArrayTransferables, nsIScriptableRegion* aRegion, uint32_t aActionType) { nsCOMPtr<nsIDocument> doc = do_QueryInterface(mSourceDocument); NS_ENSURE_STATE(doc->GetDocShell()); TabChild* child = TabChild::GetFrom(doc->GetDocShell()); NS_ENSURE_STATE(child); nsTArray<mozilla::dom::IPCDataTransfer> dataTransfers; nsContentUtils::TransferablesToIPCTransferables(aArrayTransferables, dataTransfers, false, child->Manager(), nullptr); + nsCString principalURISpec; + GetPrincipalURIFromNode(mSourceNode, principalURISpec); + LayoutDeviceIntRect dragRect; if (mHasImage || mSelection) { nsPresContext* pc; RefPtr<mozilla::gfx::SourceSurface> surface; DrawDrag(mSourceNode, aRegion, mScreenPosition, &dragRect, &surface, &pc); if (surface) { RefPtr<mozilla::gfx::DataSourceSurface> dataSurface = @@ -71,20 +93,21 @@ nsDragServiceProxy::InvokeDragSessionImp if (!surfaceData.IsReadable() || !surfaceData.get<char>()) { NS_WARNING("Failed to create shared memory for drag session."); return NS_ERROR_FAILURE; } mozilla::Unused << child->SendInvokeDragSession(dataTransfers, aActionType, surfaceData, stride, static_cast<uint8_t>(dataSurface->GetFormat()), - dragRect); + dragRect, principalURISpec); StartDragSession(); return NS_OK; } } } mozilla::Unused << child->SendInvokeDragSession(dataTransfers, aActionType, - mozilla::void_t(), 0, 0, dragRect); + mozilla::void_t(), 0, 0, dragRect, + principalURISpec); StartDragSession(); return NS_OK; }
--- a/widget/nsIDragService.idl +++ b/widget/nsIDragService.idl @@ -37,25 +37,28 @@ interface nsIDragService : nsISupports const long DRAGDROP_ACTION_LINK = 4; const long DRAGDROP_ACTION_UNINITIALIZED = 64; /** * Starts a modal drag session with an array of transaferables. * * Note: This method is deprecated for non-native code. * + * @param aPrincipalURISpec - the URI of the triggering principal of the + * drag, or an empty string if it's from browser chrome or OS * @param aTransferables - an array of transferables to be dragged * @param aRegion - a region containing rectangles for cursor feedback, * in window coordinates. * @param aActionType - specified which of copy/move/link are allowed * @param aContentPolicyType - the contentPolicyType that will be * passed to the loadInfo when creating a new channel * (defaults to TYPE_OTHER) */ void invokeDragSession (in nsIDOMNode aDOMNode, + in AUTF8String aPrincipalURISpec, in nsIArray aTransferables, in nsIScriptableRegion aRegion, in unsigned long aActionType, [optional] in nsContentPolicyType aContentPolicyType); /** * Starts a modal drag session using an image. The first four arguments are * the same as invokeDragSession. @@ -79,16 +82,17 @@ interface nsIDragService : nsISupports * rendered at its real size. For other types of elements, the element is * rendered into an offscreen buffer in the same manner as it is currently * displayed. The document selection is hidden while drawing. * * The aDragEvent must be supplied as the current screen coordinates of the * event are needed to calculate the image location. */ void invokeDragSessionWithImage(in nsIDOMNode aDOMNode, + in AUTF8String aPrincipalURISpec, in nsIArray aTransferableArray, in nsIScriptableRegion aRegion, in unsigned long aActionType, in nsIDOMNode aImage, in long aImageX, in long aImageY, in nsIDOMDragEvent aDragEvent, in nsIDOMDataTransfer aDataTransfer); @@ -96,16 +100,17 @@ interface nsIDragService : nsISupports /** * Start a modal drag session using the selection as the drag image. * The aDragEvent must be supplied as the current screen coordinates of the * event are needed to calculate the image location. * * Note: This method is deprecated for non-native code. */ void invokeDragSessionWithSelection(in nsISelection aSelection, + in AUTF8String aPrincipalURISpec, in nsIArray aTransferableArray, in unsigned long aActionType, in nsIDOMDragEvent aDragEvent, in nsIDOMDataTransfer aDataTransfer); /** * Returns the current Drag Session */
--- a/widget/nsIDragSession.idl +++ b/widget/nsIDragSession.idl @@ -58,16 +58,23 @@ interface nsIDragSession : nsISupports /** * The dom node that was originally dragged to start the session, which will be null if the * drag originated outside the application. */ readonly attribute nsIDOMNode sourceNode; /** + * The URI spec of the triggering principal. This may be different than + * sourceNode's principal when sourceNode is xul:browser and the drag is + * triggered in a browsing context inside it. + */ + attribute AUTF8String triggeringPrincipalURISpec; + + /** * The data transfer object for the current drag. */ attribute nsIDOMDataTransfer dataTransfer; /** * Get data from a Drag&Drop. Can be called while the drag is in process * or after the drop has completed. *