author | Wes Kocher <wkocher@mozilla.com> |
Fri, 14 Aug 2015 16:31:17 -0700 | |
changeset 257898 | 1c99f0dbfcf80bf70f7a4a477666457a70035211 |
parent 257887 | 6bb8ca8a857c6ba3e6e642e166de126b9c413200 (current diff) |
parent 257897 | e8ae270ae0d5abf285d55b35fe8ea8d3bb255e4e (diff) |
child 257899 | 1e3f628235f74732a08e9db11c2e9af28b1e0337 |
push id | 29233 |
push user | kwierso@gmail.com |
push date | Fri, 14 Aug 2015 23:32:11 +0000 |
treeherder | mozilla-central@45bea43ad812 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 43.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -1387,16 +1387,31 @@ nsresult Element::GetElementsByClassName(const nsAString& aClassNames, nsIDOMHTMLCollection** aResult) { *aResult = nsContentUtils::GetElementsByClassName(this, aClassNames).take(); return NS_OK; } +/** + * Returns the count of descendants (inclusive of aContent) in + * the uncomposed document that are explicitly set as editable. + */ +static uint32_t +EditableInclusiveDescendantCount(nsIContent* aContent) +{ + auto htmlElem = nsGenericHTMLElement::FromContent(aContent); + if (htmlElem) { + return htmlElem->EditableInclusiveDescendantCount(); + } + + return aContent->EditableDescendantCount(); +} + nsresult Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, bool aCompileEventHandlers) { NS_PRECONDITION(aParent || aDocument, "Must have document if no parent!"); NS_PRECONDITION((NODE_FROM(aParent, aDocument)->OwnerDoc() == OwnerDoc()), "Must have the same owner document"); @@ -1459,16 +1474,18 @@ Element::BindToTree(nsIDocument* aDocume ShadowRoot* parentContainingShadow = aParent->GetContainingShadow(); if (parentContainingShadow) { DOMSlots()->mContainingShadow = parentContainingShadow; } } bool hadForceXBL = HasFlag(NODE_FORCE_XBL_BINDINGS); + bool hadParent = !!GetParentNode(); + // Now set the parent and set the "Force attach xbl" flag if needed. if (aParent) { if (!GetParent()) { NS_ADDREF(aParent); } mParent = aParent; if (aParent->HasFlag(NODE_FORCE_XBL_BINDINGS)) { @@ -1550,44 +1567,70 @@ Element::BindToTree(nsIDocument* aDocume // This has to be here, rather than in nsGenericHTMLElement::BindToTree, // because it has to happen after updating the parent pointer, but before // recursively binding the kids. if (IsHTMLElement()) { SetDirOnBind(this, aParent); } + uint32_t editableDescendantCount = 0; + // If NODE_FORCE_XBL_BINDINGS was set we might have anonymous children // that also need to be told that they are moving. nsresult rv; if (hadForceXBL) { nsBindingManager* bmgr = OwnerDoc()->BindingManager(); nsXBLBinding* contBinding = bmgr->GetBindingWithContent(this); // First check if we have a binding... if (contBinding) { nsCOMPtr<nsIContent> anonRoot = contBinding->GetAnonymousContent(); bool allowScripts = contBinding->AllowScripts(); for (nsCOMPtr<nsIContent> child = anonRoot->GetFirstChild(); child; child = child->GetNextSibling()) { rv = child->BindToTree(aDocument, this, this, allowScripts); NS_ENSURE_SUCCESS(rv, rv); + + editableDescendantCount += EditableInclusiveDescendantCount(child); } } } UpdateEditableState(false); // Now recurse into our kids for (nsIContent* child = GetFirstChild(); child; child = child->GetNextSibling()) { rv = child->BindToTree(aDocument, this, aBindingParent, aCompileEventHandlers); NS_ENSURE_SUCCESS(rv, rv); + + editableDescendantCount += EditableInclusiveDescendantCount(child); + } + + if (aDocument) { + // Update our editable descendant count because we don't keep track of it + // for content that is not in the uncomposed document. + MOZ_ASSERT(EditableDescendantCount() == 0); + ChangeEditableDescendantCount(editableDescendantCount); + + if (!hadParent) { + uint32_t editableDescendantChange = EditableInclusiveDescendantCount(this); + if (editableDescendantChange != 0) { + // If we are binding a subtree root to the document, we need to update + // the editable descendant count of all the ancestors. + nsIContent* parent = GetParent(); + while (parent) { + parent->ChangeEditableDescendantCount(editableDescendantChange); + parent = parent->GetParent(); + } + } + } } nsNodeUtils::ParentChainChanged(this); if (HasID()) { AddToIdTable(DoGetID()); } @@ -1681,27 +1724,45 @@ Element::UnbindFromTree(bool aDeep, bool nsContentUtils::eDOM_PROPERTIES, "RemovedFullScreenElement"); // Fully exit full-screen. nsIDocument::ExitFullscreenInDocTree(OwnerDoc()); } if (HasPointerLock()) { nsIDocument::UnlockPointer(); } + + if (GetParent() && GetParent()->IsInUncomposedDoc()) { + // Update the editable descendant count in the ancestors before we + // lose the reference to the parent. + int32_t editableDescendantChange = -1 * EditableInclusiveDescendantCount(this); + if (editableDescendantChange != 0) { + nsIContent* parent = GetParent(); + while (parent) { + parent->ChangeEditableDescendantCount(editableDescendantChange); + parent = parent->GetParent(); + } + } + } + if (GetParent()) { nsINode* p = mParent; mParent = nullptr; NS_RELEASE(p); } else { mParent = nullptr; } SetParentIsContent(false); } ClearInDocument(); + // Editable descendant count only counts descendants that + // are in the uncomposed document. + ResetEditableDescendantCount(); + if (aNullParent || !mParent->IsInShadowTree()) { UnsetFlags(NODE_IS_IN_SHADOW_TREE); // Begin keeping track of our subtree root. SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot()); } if (document) {
--- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -9002,17 +9002,17 @@ nsDocument::UnblockOnload(bool aFireSync PostUnblockOnloadEvent(); } } else if (mIsBeingUsedAsImage) { // To correctly unblock onload for a document that contains an SVG // image, we need to know when all of the SVG document's resources are // done loading, in a way comparable to |window.onload|. We fire this // event to indicate that the SVG should be considered fully loaded. // Because scripting is disabled on SVG-as-image documents, this event - // is not accessible to content authors. (See bug 837135.) + // is not accessible to content authors. (See bug 837315.) nsRefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(this, NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"), false, false); asyncDispatcher->PostDOMEvent(); } }
--- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -105,17 +105,18 @@ #include "GeometryUtils.h" #include "nsIAnimationObserver.h" #include "nsChildContentList.h" using namespace mozilla; using namespace mozilla::dom; nsINode::nsSlots::nsSlots() - : mWeakReference(nullptr) + : mWeakReference(nullptr), + mEditableDescendantCount(0) { } nsINode::nsSlots::~nsSlots() { if (mChildNodes) { mChildNodes->DropReference(); } @@ -1340,16 +1341,48 @@ nsINode::GetOwnerGlobalForBindings() nsIGlobalObject* nsINode::GetOwnerGlobal() const { bool dummy; return OwnerDoc()->GetScriptHandlingObject(dummy); } +void +nsINode::ChangeEditableDescendantCount(int32_t aDelta) +{ + if (aDelta == 0) { + return; + } + + nsSlots* s = Slots(); + MOZ_ASSERT(aDelta > 0 || + s->mEditableDescendantCount >= (uint32_t) (-1 * aDelta)); + s->mEditableDescendantCount += aDelta; +} + +void +nsINode::ResetEditableDescendantCount() +{ + nsSlots* s = GetExistingSlots(); + if (s) { + s->mEditableDescendantCount = 0; + } +} + +uint32_t +nsINode::EditableDescendantCount() +{ + nsSlots* s = GetExistingSlots(); + if (s) { + return s->mEditableDescendantCount; + } + return 0; +} + bool nsINode::UnoptimizableCCNode() const { const uintptr_t problematicFlags = (NODE_IS_ANONYMOUS_ROOT | NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE | NODE_IS_NATIVE_ANONYMOUS_ROOT | NODE_MAY_BE_IN_BINDING_MNGR | NODE_IS_IN_SHADOW_TREE);
--- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h @@ -1049,16 +1049,22 @@ public: */ nsRefPtr<nsChildContentList> mChildNodes; /** * Weak reference to this node. This is cleared by the destructor of * nsNodeWeakReference. */ nsNodeWeakReference* MOZ_NON_OWNING_REF mWeakReference; + + /** + * Number of descendant nodes in the uncomposed document that have been + * explicitly set as editable. + */ + uint32_t mEditableDescendantCount; }; /** * Functions for managing flags and slots */ #ifdef DEBUG nsSlots* DebugGetSlots() { @@ -1084,16 +1090,32 @@ public: NS_ASSERTION(!(aFlagsToUnset & (NODE_IS_ANONYMOUS_ROOT | NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE | NODE_IS_NATIVE_ANONYMOUS_ROOT)), "Trying to unset write-only flags"); nsWrapperCache::UnsetFlags(aFlagsToUnset); } + void ChangeEditableDescendantCount(int32_t aDelta); + + /** + * Returns the count of descendant nodes in the uncomposed + * document that are explicitly set as editable. + */ + uint32_t EditableDescendantCount(); + + /** + * Sets the editable descendant count to 0. The editable + * descendant count only counts explicitly editable nodes + * that are in the uncomposed document so this method + * should be called when nodes are are removed from it. + */ + void ResetEditableDescendantCount(); + void SetEditableFlag(bool aEditable) { if (aEditable) { SetFlags(NODE_IS_EDITABLE); } else { UnsetFlags(NODE_IS_EDITABLE); }
--- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -520,16 +520,24 @@ nsGenericHTMLElement::IntrinsicState() c "HTML element's directionality must be either RTL or LTR"); state |= NS_EVENT_STATE_LTR; state &= ~NS_EVENT_STATE_RTL; } return state; } +uint32_t +nsGenericHTMLElement::EditableInclusiveDescendantCount() +{ + bool isEditable = IsInUncomposedDoc() && HasFlag(NODE_IS_EDITABLE) && + GetContentEditableValue() == eTrue; + return EditableDescendantCount() + isEditable; +} + nsresult nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, bool aCompileEventHandlers) { nsresult rv = nsGenericHTMLElementBase::BindToTree(aDocument, aParent, aBindingParent, aCompileEventHandlers); @@ -543,16 +551,17 @@ nsGenericHTMLElement::BindToTree(nsIDocu properties->SetDocument(aDocument); } } RegAccessKey(); if (HasName()) { aDocument-> AddToNameTable(this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue()); } + if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue) { nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(aDocument); if (htmlDocument) { htmlDocument->ChangeContentEditableCount(this, +1); } } } @@ -2912,16 +2921,22 @@ nsGenericHTMLElement::ChangeEditableStat } if (aChange != 0) { nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(document); if (htmlDocument) { htmlDocument->ChangeContentEditableCount(this, aChange); } + + nsIContent* parent = GetParent(); + while (parent) { + parent->ChangeEditableDescendantCount(aChange); + parent = parent->GetParent(); + } } if (document->HasFlag(NODE_IS_EDITABLE)) { document = nullptr; } // MakeContentDescendantsEditable is going to call ContentStateChanged for // this element and all descendants if editable state has changed.
--- a/dom/html/nsGenericHTMLElement.h +++ b/dom/html/nsGenericHTMLElement.h @@ -210,16 +210,23 @@ public: ContentEditableTristate value = element->GetContentEditableValue(); if (value != eInherit) { return value == eTrue; } } } return false; } + + /** + * Returns the count of descendants (inclusive of this node) in + * the uncomposed document that are explicitly set as editable. + */ + uint32_t EditableInclusiveDescendantCount(); + mozilla::dom::HTMLMenuElement* GetContextMenu() const; bool Spellcheck(); void SetSpellcheck(bool aSpellcheck, mozilla::ErrorResult& aError) { SetHTMLAttr(nsGkAtoms::spellcheck, aSpellcheck ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"), aError);
--- a/dom/requestsync/RequestSyncService.jsm +++ b/dom/requestsync/RequestSyncService.jsm @@ -42,16 +42,19 @@ XPCOMUtils.defineLazyServiceGetter(this, XPCOMUtils.defineLazyServiceGetter(this, "systemMessenger", "@mozilla.org/system-message-internal;1", "nsISystemMessagesInternal"); XPCOMUtils.defineLazyServiceGetter(this, "secMan", "@mozilla.org/scriptsecuritymanager;1", "nsIScriptSecurityManager"); +XPCOMUtils.defineLazyModuleGetter(this, "AlarmService", + "resource://gre/modules/AlarmService.jsm"); + this.RequestSyncService = { __proto__: IndexedDBHelper.prototype, children: [], _messages: [ "RequestSync:Register", "RequestSync:Unregister", "RequestSync:Registrations", @@ -858,36 +861,35 @@ this.RequestSyncService = { } self.scheduleTimer(aObj); } }); }, createTimer: function(aObj) { - this._timers[aObj.dbKey] = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - let interval = aObj.data.minInterval; if (aObj.data.overwrittenMinInterval > 0) { interval = aObj.data.overwrittenMinInterval; } - let self = this; - this._timers[aObj.dbKey].initWithCallback(function() { self.timeout(aObj); }, - interval * 1000, - Ci.nsITimer.TYPE_ONE_SHOT); + AlarmService.add( + { date: new Date(Date.now() + interval * 1000), + ignoreTimezone: false }, + () => this.timeout(aObj), + aTimerId => this._timers[aObj.dbKey] = aTimerId); }, hasTimer: function(aObj) { return (aObj.dbKey in this._timers); }, removeTimer: function(aObj) { if (aObj.dbKey in this._timers) { - this._timers[aObj.dbKey].cancel(); + AlarmService.remove(this._timers[aObj.dbKey]); delete this._timers[aObj.dbKey]; } }, storePendingRequest: function(aObj, aTarget, aRequestID) { if (!(aObj.dbKey in this._pendingRequests)) { this._pendingRequests[aObj.dbKey] = []; }
--- a/dom/requestsync/tests/mochitest.ini +++ b/dom/requestsync/tests/mochitest.ini @@ -4,18 +4,23 @@ support-files = file_app.template.webapp file_app.sjs file_basic_app.html common_app.js common_basic.js system_message_chrome_script.js [test_webidl.html] +skip-if = os == "android" || toolkit == "gonk" [test_minInterval.html] +skip-if = os == "android" || toolkit == "gonk" [test_basic.html] +skip-if = os == "android" || toolkit == "gonk" [test_basic_app.html] -skip-if = buildapp == 'b2g' +skip-if = os == "android" || buildapp == 'b2g' [test_wakeUp.html] -skip-if = !(buildapp == 'b2g' && toolkit == 'gonk') +run-if = buildapp == 'b2g' && toolkit == 'gonk' [test_runNow.html] run-if = buildapp == 'b2g' && toolkit == 'gonk' [test_promise.html] +skip-if = os == "android" || toolkit == "gonk" [test_bug1151082.html] +skip-if = os == "android" || toolkit == "gonk"
--- a/editor/libeditor/tests/mochitest.ini +++ b/editor/libeditor/tests/mochitest.ini @@ -160,8 +160,10 @@ skip-if = toolkit == 'android' # bug 105 [test_select_all_without_body.html] skip-if = e10s [test_spellcheck_pref.html] skip-if = toolkit == 'android' [test_bug1068979.html] [test_bug1109465.html] [test_bug1162952.html] [test_bug1186799.html] +[test_bug1181130-1.html] +[test_bug1181130-2.html]
new file mode 100644 --- /dev/null +++ b/editor/libeditor/tests/test_bug1181130-1.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1181130 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1181130</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1181130">Mozilla Bug 1181130</a> +<p id="display"></p> +<div id="container" contenteditable="true"> + editable div + <div id="noneditable" contenteditable="false"> + non-editable div + <div id="editable" contenteditable="true">nested editable div</div> + </div> +</div> +<script type="application/javascript"> +/** Test for Bug 1181130 **/ +var container = document.getElementById("container"); +var noneditable = document.getElementById("noneditable"); +var editable = document.getElementById("editable"); + +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(function() { + synthesizeMouseAtCenter(noneditable, {}); + ok(!document.getSelection().toString().includes("nested editable div"), + "Selection should not include non-editable content"); + + synthesizeMouseAtCenter(container, {}); + ok(!document.getSelection().toString().includes("nested editable div"), + "Selection should not include non-editable content"); + + synthesizeMouseAtCenter(editable, {}); + ok(!document.getSelection().toString().includes("nested editable div"), + "Selection should not include non-editable content"); + + SimpleTest.finish(); +}); +</script> +<pre id="test"> +</pre> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/editor/libeditor/tests/test_bug1181130-2.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1181130 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1181130</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1181130">Mozilla Bug 1181130</a> +<p id="display"></p> +<div id="container" contenteditable="true"> + editable div + <div id="noneditable" contenteditable="false"> + non-editable div + </div> +</div> +<script type="application/javascript"> +/** Test for Bug 1181130 **/ +var container = document.getElementById("container"); +var noneditable = document.getElementById("noneditable"); + +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(function() { + var nonHTMLElement = document.createElementNS("http://www.example.com", "element"); + nonHTMLElement.innerHTML = '<div contenteditable="true">nested editable div</div>'; + noneditable.appendChild(nonHTMLElement); + + synthesizeMouseAtCenter(noneditable, {}); + ok(!document.getSelection().toString().includes("nested editable div"), + "Selection should not include non-editable content"); + + SimpleTest.finish(); +}); +</script> +<pre id="test"> +</pre> +</body> +</html>
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp +++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp @@ -387,18 +387,18 @@ APZCCallbackHelper::AcknowledgeScrollUpd nsCOMPtr<nsIRunnable> r1 = new AcknowledgeScrollUpdateEvent(aScrollId, aScrollGeneration); if (!NS_IsMainThread()) { NS_DispatchToMainThread(r1); } else { r1->Run(); } } -static nsIPresShell* -GetRootContentDocumentPresShellForContent(nsIContent* aContent) +nsIPresShell* +APZCCallbackHelper::GetRootContentDocumentPresShellForContent(nsIContent* aContent) { nsIDocument* doc = aContent->GetComposedDoc(); if (!doc) { return nullptr; } nsIPresShell* shell = doc->GetShell(); if (!shell) { return nullptr;
--- a/gfx/layers/apz/util/APZCCallbackHelper.h +++ b/gfx/layers/apz/util/APZCCallbackHelper.h @@ -80,16 +80,19 @@ public: static void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination); /* Tell layout that we received the scroll offset update for the given view ID, so that it accepts future scroll offset updates from APZ. */ static void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId, const uint32_t& aScrollGeneration); + /* Get the pres shell associated with the root content document enclosing |aContent|. */ + static nsIPresShell* GetRootContentDocumentPresShellForContent(nsIContent* aContent); + /* Apply an "input transform" to the given |aInput| and return the transformed value. The input transform applied is the one for the content element corresponding to |aGuid|; this is populated in a previous call to UpdateCallbackTransform. See that method's documentations for details. This method additionally adjusts |aInput| by inversely scaling by the provided pres shell resolution, to cancel out a compositor-side transform (added in bug 1076241) that APZ doesn't unapply. */ static CSSPoint ApplyCallbackTransform(const CSSPoint& aInput,
--- a/gfx/layers/apz/util/ChromeProcessController.cpp +++ b/gfx/layers/apz/util/ChromeProcessController.cpp @@ -6,40 +6,48 @@ #include "ChromeProcessController.h" #include "MainThreadUtils.h" // for NS_IsMainThread() #include "base/message_loop.h" // for MessageLoop #include "mozilla/dom/Element.h" #include "mozilla/layers/CompositorParent.h" #include "mozilla/layers/APZCCallbackHelper.h" #include "mozilla/layers/APZEventState.h" +#include "mozilla/layers/APZCTreeManager.h" +#include "mozilla/layers/DoubleTapToZoom.h" #include "nsIDocument.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIPresShell.h" #include "nsLayoutUtils.h" #include "nsView.h" using namespace mozilla; using namespace mozilla::layers; using namespace mozilla::widget; ChromeProcessController::ChromeProcessController(nsIWidget* aWidget, - APZEventState* aAPZEventState) + APZEventState* aAPZEventState, + APZCTreeManager* aAPZCTreeManager) : mWidget(aWidget) , mAPZEventState(aAPZEventState) + , mAPZCTreeManager(aAPZCTreeManager) , mUILoop(MessageLoop::current()) { // Otherwise we're initializing mUILoop incorrectly. MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aAPZEventState); + MOZ_ASSERT(aAPZCTreeManager); mUILoop->PostTask( FROM_HERE, NewRunnableMethod(this, &ChromeProcessController::InitializeRoot)); } +ChromeProcessController::~ChromeProcessController() {} + void ChromeProcessController::InitializeRoot() { APZCCallbackHelper::InitializeRootDisplayport(GetPresShell()); } void ChromeProcessController::RequestContentRepaint(const FrameMetrics& aFrameMetrics) @@ -93,35 +101,69 @@ ChromeProcessController::GetPresShell() { if (nsView* view = nsView::GetViewFor(mWidget)) { return view->GetPresShell(); } return nullptr; } nsIDocument* -ChromeProcessController::GetDocument() const +ChromeProcessController::GetRootDocument() const { if (nsIPresShell* presShell = GetPresShell()) { return presShell->GetDocument(); } return nullptr; } -already_AddRefed<nsIDOMWindowUtils> -ChromeProcessController::GetDOMWindowUtils() const +nsIDocument* +ChromeProcessController::GetRootContentDocument(const FrameMetrics::ViewID& aScrollId) const { - if (nsIDocument* doc = GetDocument()) { - nsCOMPtr<nsIDOMWindowUtils> result = do_GetInterface(doc->GetWindow()); - return result.forget(); + nsIContent* content = nsLayoutUtils::FindContentFor(aScrollId); + if (!content) { + return nullptr; + } + nsIPresShell* presShell = APZCCallbackHelper::GetRootContentDocumentPresShellForContent(content); + if (presShell) { + return presShell->GetDocument(); } return nullptr; } void +ChromeProcessController::HandleDoubleTap(const mozilla::CSSPoint& aPoint, + Modifiers aModifiers, + const ScrollableLayerGuid& aGuid) +{ + if (MessageLoop::current() != mUILoop) { + mUILoop->PostTask( + FROM_HERE, + NewRunnableMethod(this, &ChromeProcessController::HandleDoubleTap, + aPoint, aModifiers, aGuid)); + return; + } + + nsCOMPtr<nsIDocument> document = GetRootContentDocument(aGuid.mScrollId); + if (!document.get()) { + return; + } + + CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid); + CSSRect zoomToRect = CalculateRectToZoomTo(document, point); + + uint32_t presShellId; + FrameMetrics::ViewID viewId; + if (APZCCallbackHelper::GetOrCreateScrollIdentifiers( + document->GetDocumentElement(), &presShellId, &viewId)) { + mAPZCTreeManager->ZoomToRect( + ScrollableLayerGuid(aGuid.mLayersId, presShellId, viewId), zoomToRect); + } +} + +void ChromeProcessController::HandleSingleTap(const CSSPoint& aPoint, Modifiers aModifiers, const ScrollableLayerGuid& aGuid) { if (MessageLoop::current() != mUILoop) { mUILoop->PostTask( FROM_HERE, NewRunnableMethod(this, &ChromeProcessController::HandleSingleTap, @@ -157,17 +199,17 @@ ChromeProcessController::NotifyAPZStateC if (MessageLoop::current() != mUILoop) { mUILoop->PostTask( FROM_HERE, NewRunnableMethod(this, &ChromeProcessController::NotifyAPZStateChange, aGuid, aChange, aArg)); return; } - mAPZEventState->ProcessAPZStateChange(GetDocument(), aGuid.mScrollId, aChange, aArg); + mAPZEventState->ProcessAPZStateChange(GetRootDocument(), aGuid.mScrollId, aChange, aArg); } void ChromeProcessController::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent) { if (MessageLoop::current() != mUILoop) { mUILoop->PostTask( FROM_HERE,
--- a/gfx/layers/apz/util/ChromeProcessController.h +++ b/gfx/layers/apz/util/ChromeProcessController.h @@ -16,59 +16,62 @@ class nsIPresShell; class nsIWidget; class MessageLoop; namespace mozilla { namespace layers { +class APZCTreeManager; class APZEventState; // A ChromeProcessController is attached to the root of a compositor's layer // tree. class ChromeProcessController : public mozilla::layers::GeckoContentController { typedef mozilla::layers::FrameMetrics FrameMetrics; typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid; public: - explicit ChromeProcessController(nsIWidget* aWidget, APZEventState* aAPZEventState); + explicit ChromeProcessController(nsIWidget* aWidget, APZEventState* aAPZEventState, APZCTreeManager* aAPZCTreeManager); + ~ChromeProcessController(); virtual void Destroy() override; // GeckoContentController interface virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) override; virtual void PostDelayedTask(Task* aTask, int aDelayMs) override; virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination) override; virtual void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId, const uint32_t& aScrollGeneration) override; virtual void HandleDoubleTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers, - const ScrollableLayerGuid& aGuid) override {} + const ScrollableLayerGuid& aGuid) override; virtual void HandleSingleTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers, const ScrollableLayerGuid& aGuid) override; virtual void HandleLongTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers, const ScrollableLayerGuid& aGuid, uint64_t aInputBlockId) override; virtual void SendAsyncScrollDOMEvent(bool aIsRootContent, const mozilla::CSSRect &aContentRect, const mozilla::CSSSize &aScrollableSize) override {} virtual void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg) override; virtual void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent) override; virtual void NotifyFlushComplete() override; private: nsCOMPtr<nsIWidget> mWidget; nsRefPtr<APZEventState> mAPZEventState; + nsRefPtr<APZCTreeManager> mAPZCTreeManager; MessageLoop* mUILoop; void InitializeRoot(); nsIPresShell* GetPresShell() const; - nsIDocument* GetDocument() const; - already_AddRefed<nsIDOMWindowUtils> GetDOMWindowUtils() const; + nsIDocument* GetRootDocument() const; + nsIDocument* GetRootContentDocument(const FrameMetrics::ViewID& aScrollId) const; }; } // namespace layers } // namespace mozilla #endif /* mozilla_layers_ChromeProcessController_h */
--- a/js/public/TracingAPI.h +++ b/js/public/TracingAPI.h @@ -22,51 +22,67 @@ template <typename T> class TenuredHeap; // Returns a static string equivalent of |kind|. JS_FRIEND_API(const char*) GCTraceKindToAscii(JS::TraceKind kind); } // namespace JS enum WeakMapTraceKind { - DoNotTraceWeakMaps = 0, - TraceWeakMapValues = 1, - TraceWeakMapKeysValues = 2 + // Do true ephemeron marking with an iterative weak marking phase. + DoNotTraceWeakMaps, + + // Do true ephemeron marking with a weak key lookup marking phase. This is + // expected to be constant for the lifetime of a JSTracer; it does not + // change when switching from "plain" marking to weak marking. + ExpandWeakMaps, + + // Trace through to all values, irrespective of whether the keys are live + // or not. Used for non-marking tracers. + TraceWeakMapValues, + + // Trace through to all keys and values, irrespective of whether the keys + // are live or not. Used for non-marking tracers. + TraceWeakMapKeysValues }; class JS_PUBLIC_API(JSTracer) { public: // Return the runtime set on the tracer. JSRuntime* runtime() const { return runtime_; } - // Return the weak map tracing behavior set on this tracer. - WeakMapTraceKind eagerlyTraceWeakMaps() const { return eagerlyTraceWeakMaps_; } + // Return the weak map tracing behavior currently set on this tracer. + WeakMapTraceKind weakMapAction() const { return weakMapAction_; } // An intermediate state on the road from C to C++ style dispatch. enum class TracerKindTag { Marking, + WeakMarking, // In weak marking phase: looking up every marked obj/script. Tenuring, Callback }; - bool isMarkingTracer() const { return tag_ == TracerKindTag::Marking; } + bool isMarkingTracer() const { return tag_ == TracerKindTag::Marking || tag_ == TracerKindTag::WeakMarking; } + bool isWeakMarkingTracer() const { return tag_ == TracerKindTag::WeakMarking; } bool isTenuringTracer() const { return tag_ == TracerKindTag::Tenuring; } bool isCallbackTracer() const { return tag_ == TracerKindTag::Callback; } inline JS::CallbackTracer* asCallbackTracer(); protected: JSTracer(JSRuntime* rt, TracerKindTag tag, WeakMapTraceKind weakTraceKind = TraceWeakMapValues) - : runtime_(rt), tag_(tag), eagerlyTraceWeakMaps_(weakTraceKind) + : runtime_(rt), weakMapAction_(weakTraceKind), tag_(tag) {} private: JSRuntime* runtime_; + WeakMapTraceKind weakMapAction_; + + protected: TracerKindTag tag_; - WeakMapTraceKind eagerlyTraceWeakMaps_; }; namespace JS { class AutoTracingName; class AutoTracingIndex; class AutoTracingCallback;
--- a/js/src/devtools/rootAnalysis/annotations.js +++ b/js/src/devtools/rootAnalysis/annotations.js @@ -234,16 +234,20 @@ function ignoreGCFunction(mangled) // and rely on only the dynamic checks provided by AutoAssertCannotGC. if (isHeapSnapshotMockClass(fun) || isGTest(fun)) return true; // Templatized function if (fun.indexOf("void nsCOMPtr<T>::Assert_NoQueryNeeded()") >= 0) return true; + // These call through an 'op' function pointer. + if (fun.indexOf("js::WeakMap<Key, Value, HashPolicy>::getDelegate(") >= 0) + return true; + // XXX modify refillFreeList<NoGC> to not need data flow analysis to understand it cannot GC. if (/refillFreeList/.test(fun) && /\(js::AllowGC\)0u/.test(fun)) return true; return false; } function stripUCSAndNamespace(name) {
--- a/js/src/ds/OrderedHashTable.h +++ b/js/src/ds/OrderedHashTable.h @@ -667,17 +667,18 @@ class OrderedHashMap void operator=(Entry&& rhs) { MOZ_ASSERT(this != &rhs, "self-move assignment is prohibited"); const_cast<Key&>(key) = Move(rhs.key); value = Move(rhs.value); } public: Entry() : key(), value() {} - Entry(const Key& k, const Value& v) : key(k), value(v) {} + template <typename V> + Entry(const Key& k, V&& v) : key(k), value(Forward<V>(v)) {} Entry(Entry&& rhs) : key(Move(rhs.key)), value(Move(rhs.value)) {} const Key key; Value value; }; private: struct MapOps : OrderedHashPolicy @@ -702,17 +703,18 @@ class OrderedHashMap explicit OrderedHashMap(AllocPolicy ap = AllocPolicy()) : impl(ap) {} bool init() { return impl.init(); } uint32_t count() const { return impl.count(); } bool has(const Key& key) const { return impl.has(key); } Range all() { return impl.all(); } const Entry* get(const Key& key) const { return impl.get(key); } Entry* get(const Key& key) { return impl.get(key); } - bool put(const Key& key, const Value& value) { return impl.put(Entry(key, value)); } + template <typename V> + bool put(const Key& key, V&& value) { return impl.put(Entry(key, Forward<V>(value))); } bool remove(const Key& key, bool* foundp) { return impl.remove(key, foundp); } bool clear() { return impl.clear(); } void rekeyOneEntry(const Key& current, const Key& newKey) { const Entry* e = get(current); if (!e) return; return impl.rekeyOneEntry(current, newKey, Entry(newKey, e->value));
--- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -597,16 +597,84 @@ DispatchToTracer(JSTracer* trc, T* thing return static_cast<TenuringTracer*>(trc)->traverse(thingp); MOZ_ASSERT(trc->isCallbackTracer()); DoCallback(trc->asCallbackTracer(), thingp, name); } /*** GC Marking Interface *************************************************************************/ +namespace js { + +typedef bool DoNothingMarkingType; + +template <typename T> +struct LinearlyMarkedEphemeronKeyType { + typedef DoNothingMarkingType Type; +}; + +// For now, we only handle JSObject* keys, but the linear time algorithm can be +// easily extended by adding in more types here, then making +// GCMarker::traverse<T> call markPotentialEphemeronKey. +template <> +struct LinearlyMarkedEphemeronKeyType<JSObject*> { + typedef JSObject* Type; +}; + +template <> +struct LinearlyMarkedEphemeronKeyType<JSScript*> { + typedef JSScript* Type; +}; + +void +GCMarker::markEphemeronValues(gc::Cell* markedCell, WeakEntryVector& values) +{ + size_t initialLen = values.length(); + for (size_t i = 0; i < initialLen; i++) + values[i].weakmap->maybeMarkEntry(this, markedCell, values[i].key); + + // The vector should not be appended to during iteration because the key is + // already marked, and even in cases where we have a multipart key, we + // should only be inserting entries for the unmarked portions. + MOZ_ASSERT(values.length() == initialLen); +} + +template <typename T> +void +GCMarker::markPotentialEphemeronKeyHelper(T markedThing) +{ + if (!isWeakMarkingTracer()) + return; + + MOZ_ASSERT(gc::TenuredCell::fromPointer(markedThing)->zone()->isGCMarking()); + MOZ_ASSERT(!gc::TenuredCell::fromPointer(markedThing)->zone()->isGCSweeping()); + + auto weakValues = weakKeys.get(JS::GCCellPtr(markedThing)); + if (!weakValues) + return; + + markEphemeronValues(markedThing, weakValues->value); + weakValues->value.clear(); // If key address is reused, it should do nothing +} + +template <> +void +GCMarker::markPotentialEphemeronKeyHelper(bool) +{ +} + +template <typename T> +void +GCMarker::markPotentialEphemeronKey(T* thing) +{ + markPotentialEphemeronKeyHelper<typename LinearlyMarkedEphemeronKeyType<T*>::Type>(thing); +} + +} // namespace js + template <typename T> static inline bool MustSkipMarking(T thing) { // Don't mark things outside a zone if we are in a per-zone GC. return !thing->zone()->isGCMarking(); } @@ -695,17 +763,16 @@ js::GCMarker::markAndTraceChildren(T* th if (ThingIsPermanentAtomOrWellKnownSymbol(thing)) return; if (mark(thing)) thing->traceChildren(this); } namespace js { template <> void GCMarker::traverse(BaseShape* thing) { markAndTraceChildren(thing); } template <> void GCMarker::traverse(JS::Symbol* thing) { markAndTraceChildren(thing); } -template <> void GCMarker::traverse(JSScript* thing) { markAndTraceChildren(thing); } } // namespace js // Shape, BaseShape, String, and Symbol are extremely common, but have simple // patterns of recursion. We traverse trees of these edges immediately, with // aggressive, manual inlining, implemented by eagerlyTraceChildren. template <typename T> void js::GCMarker::markAndScan(T* thing) @@ -719,28 +786,32 @@ namespace js { template <> void GCMarker::traverse(JSString* thing) { markAndScan(thing); } template <> void GCMarker::traverse(LazyScript* thing) { markAndScan(thing); } template <> void GCMarker::traverse(Shape* thing) { markAndScan(thing); } } // namespace js // Object and ObjectGroup are extremely common and can contain arbitrarily // nested graphs, so are not trivially inlined. In this case we use a mark // stack to control recursion. JitCode shares none of these properties, but is -// included for historical reasons. +// included for historical reasons. JSScript normally cannot recurse, but may +// be used as a weakmap key and thereby recurse into weakmapped values. template <typename T> void js::GCMarker::markAndPush(StackTag tag, T* thing) { - if (mark(thing)) - pushTaggedPtr(tag, thing); + if (!mark(thing)) + return; + pushTaggedPtr(tag, thing); + markPotentialEphemeronKey(thing); } namespace js { template <> void GCMarker::traverse(JSObject* thing) { markAndPush(ObjectTag, thing); } template <> void GCMarker::traverse(ObjectGroup* thing) { markAndPush(GroupTag, thing); } template <> void GCMarker::traverse(jit::JitCode* thing) { markAndPush(JitCodeTag, thing); } +template <> void GCMarker::traverse(JSScript* thing) { markAndPush(ScriptTag, thing); } } // namespace js namespace js { template <> void GCMarker::traverse(AccessorShape* thing) { MOZ_CRASH("AccessorShape must be marked as a Shape"); } @@ -1269,16 +1340,20 @@ GCMarker::processMarkStackTop(SliceBudge case GroupTag: { return lazilyMarkChildren(reinterpret_cast<ObjectGroup*>(addr)); } case JitCodeTag: { return reinterpret_cast<jit::JitCode*>(addr)->traceChildren(this); } + case ScriptTag: { + return reinterpret_cast<JSScript*>(addr)->traceChildren(this); + } + case SavedValueArrayTag: { MOZ_ASSERT(!(addr & CellMask)); JSObject* obj = reinterpret_cast<JSObject*>(addr); HeapSlot* vp; HeapSlot* end; if (restoreValueArray(obj, (void**)&vp, (void**)&end)) pushValueArray(&obj->as<NativeObject>(), vp, end); else @@ -1322,16 +1397,17 @@ GCMarker::processMarkStackTop(SliceBudge AssertZoneIsMarking(obj); budget.step(); if (budget.isOverBudget()) { repush(obj); return; } + markPotentialEphemeronKey(obj); ObjectGroup* group = obj->groupFromGC(); traverseEdge(obj, group); NativeObject *nobj = CallTraceHook(TraverseObjectFunctor(), this, obj, CheckGeneration::DoChecks, this, obj); if (!nobj) return; @@ -1586,61 +1662,64 @@ MarkStack::sizeOfExcludingThis(mozilla:: { return mallocSizeOf(stack_); } /*** GCMarker *************************************************************************************/ /* - * DoNotTraceWeakMaps: the GC is recomputing the liveness of WeakMap entries, - * so we delay visting entries. + * ExpandWeakMaps: the GC is recomputing the liveness of WeakMap entries by + * expanding each live WeakMap into its constituent key->value edges, a table + * of which will be consulted in a later phase whenever marking a potential + * key. */ GCMarker::GCMarker(JSRuntime* rt) - : JSTracer(rt, JSTracer::TracerKindTag::Marking, DoNotTraceWeakMaps), + : JSTracer(rt, JSTracer::TracerKindTag::Marking, ExpandWeakMaps), stack(size_t(-1)), color(BLACK), unmarkedArenaStackTop(nullptr), markLaterArenas(0), started(false), strictCompartmentChecking(false) { } bool GCMarker::init(JSGCMode gcMode) { - return stack.init(gcMode); + return stack.init(gcMode) && weakKeys.init(); } void GCMarker::start() { MOZ_ASSERT(!started); started = true; color = BLACK; + linearWeakMarkingDisabled_ = false; MOZ_ASSERT(!unmarkedArenaStackTop); MOZ_ASSERT(markLaterArenas == 0); - } void GCMarker::stop() { MOZ_ASSERT(isDrained()); MOZ_ASSERT(started); started = false; MOZ_ASSERT(!unmarkedArenaStackTop); MOZ_ASSERT(markLaterArenas == 0); /* Free non-ballast stack memory. */ stack.reset(); + weakKeys.clear(); } void GCMarker::reset() { color = BLACK; stack.reset(); @@ -1656,16 +1735,37 @@ GCMarker::reset() aheader->allocatedDuringIncremental = 0; markLaterArenas--; } MOZ_ASSERT(isDrained()); MOZ_ASSERT(!markLaterArenas); } void +GCMarker::enterWeakMarkingMode() +{ + MOZ_ASSERT(tag_ == TracerKindTag::Marking); + if (linearWeakMarkingDisabled_) + return; + + // During weak marking mode, we maintain a table mapping weak keys to + // entries in known-live weakmaps. + if (weakMapAction() == ExpandWeakMaps) { + tag_ = TracerKindTag::WeakMarking; + + for (GCCompartmentGroupIter c(runtime()); !c.done(); c.next()) { + for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next) { + if (m->marked) + m->markEphemeronEntries(this); + } + } + } +} + +void GCMarker::markDelayedChildren(ArenaHeader* aheader) { if (aheader->markOverflow) { bool always = aheader->allocatedDuringIncremental; aheader->markOverflow = 0; for (ArenaCellIterUnderGC i(aheader); !i.done(); i.next()) { TenuredCell* t = i.getCell(); @@ -2346,18 +2446,18 @@ struct AssertNonGrayTracer : public JS:: !thing.asCell()->asTenured().isMarked(js::gc::GRAY)); } }; #endif struct UnmarkGrayTracer : public JS::CallbackTracer { /* - * We set eagerlyTraceWeakMaps to false because the cycle collector will fix - * up any color mismatches involving weakmaps when it runs. + * We set weakMapAction to DoNotTraceWeakMaps because the cycle collector + * will fix up any color mismatches involving weakmaps when it runs. */ explicit UnmarkGrayTracer(JSRuntime* rt) : JS::CallbackTracer(rt, DoNotTraceWeakMaps), tracingShape(false), previousShape(nullptr), unmarkedAny(false) {}
--- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -3,22 +3,26 @@ * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef gc_Marking_h #define gc_Marking_h #include "mozilla/DebugOnly.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/Move.h" #include "jsfriendapi.h" +#include "ds/OrderedHashTable.h" #include "gc/Heap.h" #include "gc/Tracer.h" #include "js/GCAPI.h" +#include "js/HeapAPI.h" #include "js/SliceBudget.h" #include "js/TracingAPI.h" class JSLinearString; class JSRope; namespace js { class BaseShape; class GCMarker; @@ -127,16 +131,45 @@ class MarkStack /* Grow the stack, ensuring there is space for at least count elements. */ bool enlarge(unsigned count); void setGCMode(JSGCMode gcMode); size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; }; +class WeakMapBase; + +namespace gc { + +struct WeakKeyTableHashPolicy { + typedef JS::GCCellPtr Lookup; + static HashNumber hash(const Lookup& v) { return mozilla::HashGeneric(v.asCell()); } + static bool match(const JS::GCCellPtr& k, const Lookup& l) { return k == l; } + static bool isEmpty(const JS::GCCellPtr& v) { return !v; } + static void makeEmpty(JS::GCCellPtr* vp) { *vp = nullptr; } +}; + +struct WeakMarkable { + WeakMapBase* weakmap; + JS::GCCellPtr key; + + WeakMarkable(WeakMapBase* weakmapArg, JS::GCCellPtr keyArg) + : weakmap(weakmapArg), key(keyArg) {} +}; + +typedef Vector<WeakMarkable, 2, js::SystemAllocPolicy> WeakEntryVector; + +typedef OrderedHashMap<JS::GCCellPtr, + WeakEntryVector, + WeakKeyTableHashPolicy, + js::SystemAllocPolicy> WeakKeyTable; + +} /* namespace gc */ + class GCMarker : public JSTracer { public: explicit GCMarker(JSRuntime* rt); bool init(JSGCMode gcMode); void setMaxCapacity(size_t maxCap) { stack.setMaxCapacity(maxCap); } size_t maxCapacity() const { return stack.maxCapacity(); } @@ -168,16 +201,33 @@ class GCMarker : public JSTracer } void setMarkColorBlack() { MOZ_ASSERT(isDrained()); MOZ_ASSERT(color == gc::GRAY); color = gc::BLACK; } uint32_t markColor() const { return color; } + void enterWeakMarkingMode(); + + void leaveWeakMarkingMode() { + MOZ_ASSERT_IF(weakMapAction() == ExpandWeakMaps && !linearWeakMarkingDisabled_, tag_ == TracerKindTag::WeakMarking); + tag_ = TracerKindTag::Marking; + + // Table is expensive to maintain when not in weak marking mode, so + // we'll rebuild it upon entry rather than allow it to contain stale + // data. + weakKeys.clear(); + } + + void abortLinearWeakMarking() { + leaveWeakMarkingMode(); + linearWeakMarkingDisabled_ = true; + } + void delayMarkingArena(gc::ArenaHeader* aheader); void delayMarkingChildren(const void* thing); void markDelayedChildren(gc::ArenaHeader* aheader); bool markDelayedChildren(SliceBudget& budget); bool hasDelayedChildren() const { return !!unmarkedArenaStackTop; } @@ -190,16 +240,24 @@ class GCMarker : public JSTracer void setGCMode(JSGCMode mode) { stack.setGCMode(mode); } size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; #ifdef DEBUG bool shouldCheckCompartments() { return strictCompartmentChecking; } #endif + void markEphemeronValues(gc::Cell* markedCell, gc::WeakEntryVector& entry); + + /* + * Mapping from not yet marked keys to a vector of all values that the key + * maps to in any live weak map. + */ + gc::WeakKeyTable weakKeys; + private: #ifdef DEBUG void checkZone(void* p); #else void checkZone(void* p) {} #endif /* @@ -208,16 +266,17 @@ class GCMarker : public JSTracer * the context of push or pop operation. */ enum StackTag { ValueArrayTag, ObjectTag, GroupTag, SavedValueArrayTag, JitCodeTag, + ScriptTag, LastTag = JitCodeTag }; static const uintptr_t StackTagMask = 7; static_assert(StackTagMask >= uintptr_t(LastTag), "The tag mask must subsume the tags."); static_assert(StackTagMask <= gc::CellMask, "The tag mask must be embeddable in a Cell*."); // Push an object onto the stack for later tracing and assert that it has @@ -225,16 +284,18 @@ class GCMarker : public JSTracer void repush(JSObject* obj) { MOZ_ASSERT(gc::TenuredCell::fromPointer(obj)->isMarked(markColor())); pushTaggedPtr(ObjectTag, obj); } template <typename T> void markAndTraceChildren(T* thing); template <typename T> void markAndPush(StackTag tag, T* thing); template <typename T> void markAndScan(T* thing); + template <typename T> void markPotentialEphemeronKeyHelper(T oldThing); + template <typename T> void markPotentialEphemeronKey(T* oldThing); void eagerlyMarkChildren(JSLinearString* str); void eagerlyMarkChildren(JSRope* rope); void eagerlyMarkChildren(JSString* str); void eagerlyMarkChildren(LazyScript *thing); void eagerlyMarkChildren(Shape* shape); void lazilyMarkChildren(ObjectGroup* group); // We may not have concrete types yet, so this has to be out of the header. @@ -282,16 +343,22 @@ class GCMarker : public JSTracer MarkStack stack; /* The color is only applied to objects and functions. */ uint32_t color; /* Pointer to the top of the stack of arenas we are delaying marking on. */ js::gc::ArenaHeader* unmarkedArenaStackTop; + /* + * If the weakKeys table OOMs, disable the linear algorithm and fall back + * to iterating until the next GC. + */ + bool linearWeakMarkingDisabled_; + /* Count of arenas that are currently in the stack. */ mozilla::DebugOnly<size_t> markLaterArenas; /* Assert that start and stop are called with correct ordering. */ mozilla::DebugOnly<bool> started; /* * If this is true, all marked objects must belong to a compartment being
--- a/js/src/gc/Tracer.h +++ b/js/src/gc/Tracer.h @@ -18,21 +18,21 @@ namespace js { // // Tracing is an abstract visitation of each edge in a JS heap graph.[1] The // most common (and performance sensitive) use of this infrastructure is for GC // "marking" as part of the mark-and-sweep collector; however, this // infrastructure is much more general than that and is used for many other // purposes as well. // // One commonly misunderstood subtlety of the tracing architecture is the role -// of graph verticies versus graph edges. Graph verticies are the heap +// of graph vertices versus graph edges. Graph vertices are the heap // allocations -- GC things -- that are returned by Allocate. Graph edges are // pointers -- including tagged pointers like Value and jsid -- that link the // allocations into a complex heap. The tracing API deals *only* with edges. -// Any action taken on the target of a graph edge is independent to the tracing +// Any action taken on the target of a graph edge is independent of the tracing // itself. // // Another common misunderstanding relates to the role of the JSTracer. The // JSTracer instance determines what tracing does when visiting an edge; it // does not itself participate in the tracing process, other than to be passed // through as opaque data. It works like a closure in that respect. // // Tracing implementations internal to SpiderMonkey should use these interfaces
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/gc/weak-marking-01.js @@ -0,0 +1,193 @@ +// These tests will be using object literals as keys, and we want some of them +// to be dead after being inserted into a WeakMap. That means we must wrap +// everything in functions because it seems like the toplevel script hangs onto +// its object literals. + +// All reachable keys should be found, and the rest should be swept. +function basicSweeping() { + var wm1 = new WeakMap(); + wm1.set({'name': 'obj1'}, {'name': 'val1'}); + var hold = {'name': 'obj2'}; + wm1.set(hold, {'name': 'val2'}); + wm1.set({'name': 'obj3'}, {'name': 'val3'}); + + startgc(100000, 'shrinking'); + gcslice(); + + assertEq(wm1.get(hold).name, 'val2'); + assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1); +} + +basicSweeping(); + +// Keep values alive even when they are only referenced by (live) WeakMap values. +function weakGraph() { + var wm1 = new WeakMap(); + var obj1 = {'name': 'obj1'}; + var obj2 = {'name': 'obj2'}; + var obj3 = {'name': 'obj3'}; + var obj4 = {'name': 'obj4'}; + var clear = {'name': ''}; // Make the interpreter forget about the last obj created + + wm1.set(obj2, obj3); + wm1.set(obj3, obj1); + wm1.set(obj4, obj1); // This edge will be cleared + obj1 = obj3 = obj4 = undefined; + + startgc(100000, 'shrinking'); + gcslice(); + + assertEq(obj2.name, "obj2"); + assertEq(wm1.get(obj2).name, "obj3"); + assertEq(wm1.get(wm1.get(obj2)).name, "obj1"); + print(nondeterministicGetWeakMapKeys(wm1).map(o => o.name).join(",")); + assertEq(nondeterministicGetWeakMapKeys(wm1).length, 2); +} + +weakGraph(); + +// ...but the weakmap itself has to stay alive, too. +function deadWeakMap() { + var wm1 = new WeakMap(); + var obj1 = makeFinalizeObserver(); + var obj2 = {'name': 'obj2'}; + var obj3 = {'name': 'obj3'}; + var obj4 = {'name': 'obj4'}; + var clear = {'name': ''}; // Make the interpreter forget about the last obj created + + wm1.set(obj2, obj3); + wm1.set(obj3, obj1); + wm1.set(obj4, obj1); // This edge will be cleared + var initialCount = finalizeCount(); + obj1 = obj3 = obj4 = undefined; + wm1 = undefined; + + startgc(100000, 'shrinking'); + gcslice(); + + assertEq(obj2.name, "obj2"); + assertEq(finalizeCount(), initialCount + 1); +} + +deadWeakMap(); + +// WeakMaps do not strongly reference their keys or values. (WeakMaps hold a +// collection of (strong) references to *edges* from keys to values. If the +// WeakMap is not live, then its edges are of course not live either. An edge +// holds neither its key nor its value live; it just holds a strong ref from +// the key to the value. So if the key is live, the value is live too, but the +// edge itself has no references to anything.) +function deadKeys() { + var wm1 = new WeakMap(); + var obj1 = makeFinalizeObserver(); + var obj2 = {'name': 'obj2'}; + var obj3 = makeFinalizeObserver(); + var clear = {}; // Make the interpreter forget about the last obj created + + wm1.set(obj1, obj1); + wm1.set(obj3, obj2); + obj1 = obj3 = undefined; + var initialCount = finalizeCount(); + + startgc(100000, 'shrinking'); + gcslice(); + + assertEq(finalizeCount(), initialCount + 2); + assertEq(nondeterministicGetWeakMapKeys(wm1).length, 0); +} + +deadKeys(); + +// The weakKeys table has to grow if it encounters enough new unmarked weakmap +// keys. Trigger this to happen during weakmap marking. +// +// There's some trickiness involved in getting it to test the right thing, +// because if a key is marked before the weakmap, then it won't get entered +// into the weakKeys table. This chains through multiple weakmap layers to +// ensure that the objects can't get marked before the weakmaps. +function weakKeysRealloc() { + var wm1 = new WeakMap; + var wm2 = new WeakMap; + var wm3 = new WeakMap; + var obj1 = {'name': 'obj1'}; + var obj2 = {'name': 'obj2'}; + wm1.set(obj1, wm2); + wm2.set(obj2, wm3); + for (var i = 0; i < 10000; i++) { + wm3.set(Object.create(null), wm2); + } + wm3.set(Object.create(null), makeFinalizeObserver()); + wm2 = undefined; + wm3 = undefined; + obj2 = undefined; + + var initialCount = finalizeCount(); + startgc(100000, 'shrinking'); + gcslice(); + assertEq(finalizeCount(), initialCount + 1); +} + +weakKeysRealloc(); + +// The weakKeys table is populated during regular marking. When a key is later +// deleted, both it and its delegate should be removed from weakKeys. +// Otherwise, it will hold its value live if it gets marked, and our table +// traversals will include non-keys, etc. +function deletedKeys() { + var wm = new WeakMap; + var g = newGlobal(); + + for (var i = 0; i < 1000; i++) + wm.set(g.Object.create(null), i); + + startgc(100, 'shrinking'); + for (var key of nondeterministicGetWeakMapKeys(wm)) { + if (wm.get(key) % 2) + wm.delete(key); + } + + gc(); +} + +deletedKeys(); + +// Test adding keys during incremental GC. +function incrementalAdds() { + var initialCount = finalizeCount(); + + var wm1 = new WeakMap; + var wm2 = new WeakMap; + var wm3 = new WeakMap; + var obj1 = {'name': 'obj1'}; + var obj2 = {'name': 'obj2'}; + wm1.set(obj1, wm2); + wm2.set(obj2, wm3); + for (var i = 0; i < 10000; i++) { + wm3.set(Object.create(null), wm2); + } + wm3.set(Object.create(null), makeFinalizeObserver()); + obj2 = undefined; + + var obj3 = []; + startgc(100, 'shrinking'); + var M = 10; + var N = 800; + for (var j = 0; j < M; j++) { + for (var i = 0; i < N; i++) + wm3.set(Object.create(null), makeFinalizeObserver()); // Should be swept + for (var i = 0; i < N; i++) { + obj3.push({'name': 'obj3'}); + wm1.set(obj3[obj3.length - 1], makeFinalizeObserver()); // Should not be swept + } + gcslice(); + } + + wm2 = undefined; + wm3 = undefined; + + gc(); + print("initialCount = " + initialCount); + assertEq(finalizeCount(), initialCount + 1 + M * N); +} + +incrementalAdds();
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/gc/weak-marking-02.js @@ -0,0 +1,128 @@ +// These tests will be using object literals as keys, and we want some of them +// to be dead after being inserted into a WeakMap. That means we must wrap +// everything in functions because it seems like the toplevel script hangs onto +// its object literals. + +// Cross-compartment WeakMap keys work by storing a cross-compartment wrapper +// in the WeakMap, and the actual "delegate" object in the target compartment +// is the thing whose liveness is checked. + +var g2 = newGlobal(); +g2.eval('function genObj(name) { return {"name": name} }'); + +function basicSweeping() { + var wm1 = new WeakMap(); + wm1.set({'name': 'obj1'}, {'name': 'val1'}); + var hold = g2.genObj('obj2'); + wm1.set(hold, {'name': 'val2'}); + wm1.set({'name': 'obj3'}, {'name': 'val3'}); + var obj4 = g2.genObj('obj4'); + wm1.set(obj4, {'name': 'val3'}); + obj4 = undefined; + + startgc(100000, 'shrinking'); + gcslice(); + assertEq(wm1.get(hold).name, 'val2'); + assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1); +} + +basicSweeping(); + +// Same, but behind an additional WM layer, to avoid ordering problems (not +// that I've checked that basicSweeping even has any problems.) + +function basicSweeping2() { + var wm1 = new WeakMap(); + wm1.set({'name': 'obj1'}, {'name': 'val1'}); + var hold = g2.genObj('obj2'); + wm1.set(hold, {'name': 'val2'}); + wm1.set({'name': 'obj3'}, {'name': 'val3'}); + var obj4 = g2.genObj('obj4'); + wm1.set(obj4, {'name': 'val3'}); + obj4 = undefined; + + var base1 = {'name': 'base1'}; + var base2 = {'name': 'base2'}; + var wm_base1 = new WeakMap(); + var wm_base2 = new WeakMap(); + wm_base1.set(base1, wm_base2); + wm_base2.set(base2, wm1); + wm1 = wm_base2 = undefined; + + startgc(100000, 'shrinking'); + gcslice(); + + assertEq(nondeterministicGetWeakMapKeys(wm_base1).length, 1); + wm_base2 = wm_base1.get(base1); + assertEq(nondeterministicGetWeakMapKeys(wm_base2).length, 1); + assertEq(nondeterministicGetWeakMapKeys(wm_base1)[0], base1); + assertEq(nondeterministicGetWeakMapKeys(wm_base2)[0], base2); + wm_base2 = wm_base1.get(base1); + wm1 = wm_base2.get(base2); + assertEq(wm1.get(hold).name, 'val2'); + assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1); +} + +basicSweeping2(); + +// Scatter the weakmap, the keys, and the values among different compartments. + +function tripleZoneMarking() { + var g1 = newGlobal(); + var g2 = newGlobal(); + var g3 = newGlobal(); + + var wm = g1.eval("new WeakMap()"); + var key = g2.eval("({'name': 'obj1'})"); + var value = g3.eval("({'name': 'val1'})"); + g1 = g2 = g3 = undefined; + wm.set(key, value); + + // Make all of it only reachable via a weakmap in the main test compartment, + // so that all of this happens during weak marking mode. Use the weakmap as + // its own key, so we know that the weakmap will get traced before the key + // and therefore will populate the weakKeys table and all of that jazz. + var base_wm = new WeakMap(); + base_wm.set(base_wm, [ wm, key ]); + + wm = key = value = undefined; + + startgc(100000, 'shrinking'); + gcslice(); + + var keys = nondeterministicGetWeakMapKeys(base_wm); + assertEq(keys.length, 1); + var [ wm, key ] = base_wm.get(keys[0]); + assertEq(key.name, "obj1"); + value = wm.get(key); + assertEq(value.name, "val1"); +} + +tripleZoneMarking(); + +function enbugger() { + var g = newGlobal(); + var dbg = new Debugger; + g.eval("function debuggee_f() { return 1; }"); + g.eval("function debuggee_g() { return 1; }"); + dbg.addDebuggee(g); + var [ s ] = dbg.findScripts({global: g}).filter(s => s.displayName == "debuggee_f"); + var [ s2 ] = dbg.findScripts({global: g}).filter(s => s.displayName == "debuggee_g"); + g.eval("debuggee_f = null"); + gc(); + dbg.removeAllDebuggees(); + gc(); + assertEq(s.displayName, "debuggee_f"); + + var wm = new WeakMap; + var obj = Object.create(null); + var obj2 = Object.create(null); + wm.set(obj, s); + wm.set(obj2, obj); + wm.set(s2, obj2); + s = s2 = obj = obj2 = null; + + gc(); +} + +enbugger();
--- a/js/src/jsapi-tests/testWeakMap.cpp +++ b/js/src/jsapi-tests/testWeakMap.cpp @@ -65,62 +65,71 @@ checkSize(JS::HandleObject map, uint32_t return true; } END_TEST(testWeakMap_basicOperations) BEGIN_TEST(testWeakMap_keyDelegates) { JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); JS_GC(rt); - JS::RootedObject map(cx, JS::NewWeakMapObject(cx)); CHECK(map); JS::RootedObject key(cx, newKey()); CHECK(key); JS::RootedObject delegate(cx, newDelegate()); CHECK(delegate); keyDelegate = delegate; + JS::RootedObject delegateRoot(cx); + { + JSAutoCompartment ac(cx, delegate); + delegateRoot = JS_NewPlainObject(cx); + CHECK(delegateRoot); + JS::RootedValue delegateValue(cx, ObjectValue(*delegate)); + CHECK(JS_DefineProperty(cx, delegateRoot, "delegate", delegateValue, 0)); + } + delegate = nullptr; + /* * Perform an incremental GC, introducing an unmarked CCW to force the map * zone to finish marking before the delegate zone. */ - CHECK(newCCW(map, delegate)); + CHECK(newCCW(map, delegateRoot)); js::SliceBudget budget(js::WorkBudget(1000000)); rt->gc.startDebugGC(GC_NORMAL, budget); CHECK(!JS::IsIncrementalGCInProgress(rt)); #ifdef DEBUG - CHECK(map->zone()->lastZoneGroupIndex() < delegate->zone()->lastZoneGroupIndex()); + CHECK(map->zone()->lastZoneGroupIndex() < delegateRoot->zone()->lastZoneGroupIndex()); #endif /* Add our entry to the weakmap. */ JS::RootedValue val(cx, JS::Int32Value(1)); CHECK(SetWeakMapEntry(cx, map, key, val)); CHECK(checkSize(map, 1)); /* Check the delegate keeps the entry alive even if the key is not reachable. */ key = nullptr; - CHECK(newCCW(map, delegate)); + CHECK(newCCW(map, delegateRoot)); budget = js::SliceBudget(js::WorkBudget(100000)); rt->gc.startDebugGC(GC_NORMAL, budget); CHECK(!JS::IsIncrementalGCInProgress(rt)); CHECK(checkSize(map, 1)); /* * Check that the zones finished marking at the same time, which is - * neccessary because of the presence of the delegate and the CCW. + * necessary because of the presence of the delegate and the CCW. */ #ifdef DEBUG - CHECK(map->zone()->lastZoneGroupIndex() == delegate->zone()->lastZoneGroupIndex()); + CHECK(map->zone()->lastZoneGroupIndex() == delegateRoot->zone()->lastZoneGroupIndex()); #endif /* Check that when the delegate becomes unreachable the entry is removed. */ - delegate = nullptr; + delegateRoot = nullptr; keyDelegate = nullptr; JS_GC(rt); CHECK(checkSize(map, 0)); return true; } static void DelegateObjectMoved(JSObject* obj, const JSObject* old)
--- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -4018,32 +4018,42 @@ GCRuntime::beginMarkPhase(JS::gcreason:: template <class CompartmentIterT> void GCRuntime::markWeakReferences(gcstats::Phase phase) { MOZ_ASSERT(marker.isDrained()); gcstats::AutoPhase ap1(stats, phase); + marker.enterWeakMarkingMode(); + + // TODO bug 1167452: Make weak marking incremental + SliceBudget budget = SliceBudget::unlimited(); + marker.drainMarkStack(budget); + for (;;) { bool markedAny = false; for (CompartmentIterT c(rt); !c.done(); c.next()) { - markedAny |= WatchpointMap::markCompartmentIteratively(c, &marker); - markedAny |= WeakMapBase::markCompartmentIteratively(c, &marker); + if (c->watchpointMap) + markedAny |= c->watchpointMap->markIteratively(&marker); + if (marker.weakMapAction() != ExpandWeakMaps) + markedAny |= WeakMapBase::markCompartmentIteratively(c, &marker); } markedAny |= Debugger::markAllIteratively(&marker); markedAny |= jit::JitRuntime::MarkJitcodeGlobalTableIteratively(&marker); if (!markedAny) break; auto unlimited = SliceBudget::unlimited(); marker.drainMarkStack(unlimited); } MOZ_ASSERT(marker.isDrained()); + + marker.leaveWeakMarkingMode(); } void GCRuntime::markWeakReferencesInCurrentGroup(gcstats::Phase phase) { markWeakReferences<GCCompartmentGroupIter>(phase); } @@ -4176,22 +4186,33 @@ js::gc::MarkingValidator::nonIncremental if (!markedWeakMaps.init()) return; for (GCCompartmentsIter c(runtime); !c.done(); c.next()) { if (!WeakMapBase::saveCompartmentMarkedWeakMaps(c, markedWeakMaps)) return; } + gc::WeakKeyTable savedWeakKeys; + if (!savedWeakKeys.init()) + return; + + for (gc::WeakKeyTable::Range r = gc->marker.weakKeys.all(); !r.empty(); r.popFront()) { + if (!savedWeakKeys.put(Move(r.front().key), Move(r.front().value))) + CrashAtUnhandlableOOM("saving weak keys table for validator"); + } + /* * After this point, the function should run to completion, so we shouldn't * do anything fallible. */ initialized = true; + gc->marker.weakKeys.clear(); + /* Re-do all the marking, but non-incrementally. */ js::gc::State state = gc->incrementalState; gc->incrementalState = MARK_ROOTS; { gcstats::AutoPhase ap(gc->stats, gcstats::PHASE_MARK); { @@ -4244,17 +4265,23 @@ js::gc::MarkingValidator::nonIncremental for (auto chunk = gc->allNonEmptyChunks(); !chunk.done(); chunk.next()) { ChunkBitmap* bitmap = &chunk->bitmap; ChunkBitmap* entry = map.lookup(chunk)->value(); Swap(*entry, *bitmap); } for (GCCompartmentsIter c(runtime); !c.done(); c.next()) WeakMapBase::unmarkCompartment(c); - WeakMapBase::restoreCompartmentMarkedWeakMaps(markedWeakMaps); + WeakMapBase::restoreMarkedWeakMaps(markedWeakMaps); + + gc->marker.weakKeys.clear(); + for (gc::WeakKeyTable::Range r = savedWeakKeys.all(); !r.empty(); r.popFront()) { + if (!gc->marker.weakKeys.put(Move(r.front().key), Move(r.front().value))) + CrashAtUnhandlableOOM("restoring weak keys table for validator"); + } gc->incrementalState = state; } void js::gc::MarkingValidator::validate() { /* @@ -4749,17 +4776,16 @@ GCRuntime::endMarkingZoneGroup() gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_MARK); /* * Mark any incoming black pointers from previously swept compartments * whose referents are not marked. This can occur when gray cells become * black by the action of UnmarkGray. */ MarkIncomingCrossCompartmentPointers(rt, BLACK); - markWeakReferencesInCurrentGroup(gcstats::PHASE_SWEEP_MARK_WEAK); /* * Change state of current group to MarkGray to restrict marking to this * group. Note that there may be pointers to the atoms compartment, and * these will be marked through, as they are not marked with * MarkCrossCompartmentXXX. */ @@ -4910,16 +4936,27 @@ GCRuntime::beginSweepingZoneGroup() if (rt->sweepZoneCallback) rt->sweepZoneCallback(zone); zone->gcLastZoneGroupIndex = zoneGroupIndex; } validateIncrementalMarking(); + /* Clear out this zone group's keys from the weakKeys table, to prevent later accesses. */ + for (WeakKeyTable::Range r = marker.weakKeys.all(); !r.empty(); ) { + auto key(r.front().key); + r.popFront(); + if (gc::TenuredCell::fromPointer(key.asCell())->zone()->isGCSweeping()) { + bool found; + marker.weakKeys.remove(key, &found); + MOZ_ASSERT(found); + } + } + FreeOp fop(rt); SweepAtomsTask sweepAtomsTask(rt); SweepInnerViewsTask sweepInnerViewsTask(rt); SweepCCWrappersTask sweepCCWrappersTask(rt); SweepBaseShapesTask sweepBaseShapesTask(rt); SweepInitialShapesTask sweepInitialShapesTask(rt); SweepObjectGroupsTask sweepObjectGroupsTask(rt); SweepRegExpsTask sweepRegExpsTask(rt);
--- a/js/src/jswatchpoint.cpp +++ b/js/src/jswatchpoint.cpp @@ -140,24 +140,16 @@ WatchpointMap::triggerWatchpoint(JSConte // watchpoint. See the comment before UnmarkGrayChildren in gc/Marking.cpp JS::ExposeObjectToActiveJS(closure); /* Call the handler. */ return handler(cx, obj, id, old, vp.address(), closure); } bool -WatchpointMap::markCompartmentIteratively(JSCompartment* c, JSTracer* trc) -{ - if (!c->watchpointMap) - return false; - return c->watchpointMap->markIteratively(trc); -} - -bool WatchpointMap::markIteratively(JSTracer* trc) { bool marked = false; for (Map::Enum e(map); !e.empty(); e.popFront()) { Map::Entry& entry = e.front(); JSObject* priorKeyObj = entry.key().object; jsid priorKeyId(entry.key().id.get()); bool objectIsLive =
--- a/js/src/jswatchpoint.h +++ b/js/src/jswatchpoint.h @@ -65,17 +65,16 @@ class WatchpointMap { JSWatchPointHandler handler, HandleObject closure); void unwatch(JSObject* obj, jsid id, JSWatchPointHandler* handlerp, JSObject** closurep); void unwatchObject(JSObject* obj); void clear(); bool triggerWatchpoint(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp); - static bool markCompartmentIteratively(JSCompartment* c, JSTracer* trc); bool markIteratively(JSTracer* trc); void markAll(JSTracer* trc); static void sweepAll(JSRuntime* rt); void sweep(); static void traceAll(WeakMapTracer* trc); void trace(WeakMapTracer* trc);
--- a/js/src/jsweakmap.cpp +++ b/js/src/jsweakmap.cpp @@ -45,47 +45,51 @@ WeakMapBase::~WeakMapBase() removeWeakMapFromList(this); } void WeakMapBase::trace(JSTracer* tracer) { MOZ_ASSERT(isInList()); if (tracer->isMarkingTracer()) { - // We don't trace any of the WeakMap entries at this time, just record - // record the fact that the WeakMap has been marked. Entries are marked - // in the iterative marking phase by markAllIteratively(), which happens - // when as many keys as possible have been marked already. - MOZ_ASSERT(tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps); marked = true; + if (tracer->weakMapAction() == DoNotTraceWeakMaps) { + // Do not trace any WeakMap entries at this time. Just record the + // fact that the WeakMap has been marked. Entries are marked in the + // iterative marking phase by markAllIteratively(), after as many + // keys as possible have been marked already. + } else { + MOZ_ASSERT(tracer->weakMapAction() == ExpandWeakMaps); + markEphemeronEntries(tracer); + } } else { // If we're not actually doing garbage collection, the keys won't be marked // nicely as needed by the true ephemeral marking algorithm --- custom tracers // such as the cycle collector must use their own means for cycle detection. // So here we do a conservative approximation: pretend all keys are live. - if (tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps) + if (tracer->weakMapAction() == DoNotTraceWeakMaps) return; nonMarkingTraceValues(tracer); - if (tracer->eagerlyTraceWeakMaps() == TraceWeakMapKeysValues) + if (tracer->weakMapAction() == TraceWeakMapKeysValues) nonMarkingTraceKeys(tracer); } } void WeakMapBase::unmarkCompartment(JSCompartment* c) { for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next) m->marked = false; } void WeakMapBase::markAll(JSCompartment* c, JSTracer* tracer) { - MOZ_ASSERT(tracer->eagerlyTraceWeakMaps() != DoNotTraceWeakMaps); + MOZ_ASSERT(tracer->weakMapAction() != DoNotTraceWeakMaps); for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next) { m->trace(tracer); if (m->memberOf) TraceEdge(tracer, &m->memberOf, "memberOf"); } } bool @@ -153,17 +157,17 @@ WeakMapBase::saveCompartmentMarkedWeakMa for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next) { if (m->marked && !markedWeakMaps.put(m)) return false; } return true; } void -WeakMapBase::restoreCompartmentMarkedWeakMaps(WeakMapSet& markedWeakMaps) +WeakMapBase::restoreMarkedWeakMaps(WeakMapSet& markedWeakMaps) { for (WeakMapSet::Range r = markedWeakMaps.all(); !r.empty(); r.popFront()) { WeakMapBase* map = r.front(); MOZ_ASSERT(map->compartment->zone()->isGCMarking()); MOZ_ASSERT(!map->marked); map->marked = true; } } @@ -181,17 +185,17 @@ WeakMapBase::removeWeakMapFromList(WeakM } } bool ObjectValueMap::findZoneEdges() { /* * For unmarked weakmap keys with delegates in a different zone, add a zone - * edge to ensure that the delegate zone does finish marking after the key + * edge to ensure that the delegate zone finishes marking before the key * zone. */ JS::AutoSuppressGCAnalysis nogc; Zone* mapZone = compartment->zone(); for (Range r = all(); !r.empty(); r.popFront()) { JSObject* key = r.front().key(); if (key->asTenured().isMarked(BLACK) && !key->asTenured().isMarked(GRAY)) continue;
--- a/js/src/jsweakmap.h +++ b/js/src/jsweakmap.h @@ -2,16 +2,18 @@ * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef jsweakmap_h #define jsweakmap_h +#include "mozilla/Move.h" + #include "jscompartment.h" #include "jsfriendapi.h" #include "jsobj.h" #include "gc/Marking.h" #include "gc/StoreBuffer.h" #include "js/HashTable.h" @@ -34,16 +36,18 @@ namespace js { // The value for the next pointer for maps not in the map list. static WeakMapBase * const WeakMapNotInList = reinterpret_cast<WeakMapBase*>(1); typedef HashSet<WeakMapBase*, DefaultHasher<WeakMapBase*>, SystemAllocPolicy> WeakMapSet; // Common base class for all WeakMap specializations. The collector uses this to call // their markIteratively and sweep methods. class WeakMapBase { + friend void js::GCMarker::enterWeakMarkingMode(); + public: WeakMapBase(JSObject* memOf, JSCompartment* c); virtual ~WeakMapBase(); void trace(JSTracer* tracer); // Garbage collector entry points. @@ -70,21 +74,27 @@ class WeakMapBase { static void traceAllMappings(WeakMapTracer* tracer); bool isInList() { return next != WeakMapNotInList; } // Save information about which weak maps are marked for a compartment. static bool saveCompartmentMarkedWeakMaps(JSCompartment* c, WeakMapSet& markedWeakMaps); // Restore information about which weak maps are marked for many compartments. - static void restoreCompartmentMarkedWeakMaps(WeakMapSet& markedWeakMaps); + static void restoreMarkedWeakMaps(WeakMapSet& markedWeakMaps); // Remove a weakmap from its compartment's weakmaps list. static void removeWeakMapFromList(WeakMapBase* weakmap); + // Any weakmap key types that want to participate in the non-iterative + // ephemeron marking must override this method. + virtual void maybeMarkEntry(JSTracer* trc, gc::Cell* markedCell, JS::GCCellPtr l) = 0; + + virtual void markEphemeronEntries(JSTracer* trc) = 0; + protected: // Instance member functions called by the above. Instantiations of WeakMap override // these with definitions appropriate for their Key and Value types. virtual void nonMarkingTraceKeys(JSTracer* tracer) = 0; virtual void nonMarkingTraceValues(JSTracer* tracer) = 0; virtual bool markIteratively(JSTracer* tracer) = 0; virtual bool findZoneEdges() = 0; virtual void sweep() = 0; @@ -101,16 +111,27 @@ class WeakMapBase { // JSCompartment::gcWeakMapList. The last element of the list has nullptr as // its next. Maps not in the list have WeakMapNotInList as their next. WeakMapBase* next; // Whether this object has been traced during garbage collection. bool marked; }; +template <typename T> +static T extractUnbarriered(BarrieredBase<T> v) +{ + return v.get(); +} +template <typename T> +static T* extractUnbarriered(T* v) +{ + return v; +} + template <class Key, class Value, class HashPolicy = DefaultHasher<Key> > class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>, public WeakMapBase { public: typedef HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy> Base; typedef typename Base::Enum Enum; typedef typename Base::Lookup Lookup; @@ -149,125 +170,201 @@ class WeakMap : public HashMap<Key, Valu Ptr lookupWithDefault(const Key& k, const Value& defaultValue) { Ptr p = Base::lookupWithDefault(k, defaultValue); if (p) exposeGCThingToActiveJS(p->value()); return p; } + // The WeakMap and some part of the key are marked. If the entry is marked + // according to the exact semantics of this WeakMap, then mark the value. + // (For a standard WeakMap, the entry is marked if either the key its + // delegate is marked.) + void maybeMarkEntry(JSTracer* trc, gc::Cell* markedCell, JS::GCCellPtr origKey) override + { + MOZ_ASSERT(marked); + + gc::Cell* l = origKey.asCell(); + Ptr p = Base::lookup(reinterpret_cast<Lookup&>(l)); + MOZ_ASSERT(p.found()); + + Key key(p->key()); + if (gc::IsMarked(&key)) { + TraceEdge(trc, &p->value(), "ephemeron value"); + } else if (keyNeedsMark(key)) { + TraceEdge(trc, &p->value(), "WeakMap ephemeron value"); + TraceEdge(trc, &key, "proxy-preserved WeakMap ephemeron key"); + MOZ_ASSERT(key == p->key()); // No moving + } + key.unsafeSet(nullptr); // Prevent destructor from running barriers. + } + + protected: + static void addWeakEntry(JSTracer* trc, JS::GCCellPtr key, gc::WeakMarkable markable) + { + GCMarker& marker = *static_cast<GCMarker*>(trc); + + auto p = marker.weakKeys.get(key); + if (p) { + gc::WeakEntryVector& weakEntries = p->value; + if (!weakEntries.append(Move(markable))) + marker.abortLinearWeakMarking(); + } else { + gc::WeakEntryVector weakEntries; + MOZ_ALWAYS_TRUE(weakEntries.append(Move(markable))); + if (!marker.weakKeys.put(JS::GCCellPtr(key), Move(weakEntries))) + marker.abortLinearWeakMarking(); + } + } + + void markEphemeronEntries(JSTracer* trc) override { + MOZ_ASSERT(marked); + for (Enum e(*this); !e.empty(); e.popFront()) { + Key key(e.front().key()); + + // If the entry is live, ensure its key and value are marked. + if (gc::IsMarked(&key)) { + (void) markValue(trc, &e.front().value()); + MOZ_ASSERT(key == e.front().key()); // No moving + } else if (keyNeedsMark(key)) { + TraceEdge(trc, &e.front().value(), "WeakMap entry value"); + TraceEdge(trc, &key, "proxy-preserved WeakMap entry key"); + MOZ_ASSERT(key == e.front().key()); // No moving + } else if (trc->isWeakMarkingTracer()) { + // Entry is not yet known to be live. Record it in the list of + // weak keys. Or rather, record this weakmap and the lookup key + // so we can repeat the lookup when we need to (to allow + // incremental weak marking, we can't just store a pointer to + // the entry.) Also record the delegate, if any, because + // marking the delegate must also mark the entry. + JS::GCCellPtr weakKey(extractUnbarriered(key)); + gc::WeakMarkable markable(this, weakKey); + addWeakEntry(trc, weakKey, markable); + if (JSObject* delegate = getDelegate(key)) + addWeakEntry(trc, JS::GCCellPtr(delegate), markable); + } + key.unsafeSet(nullptr); // Prevent destructor from running barriers. + } + } + private: void exposeGCThingToActiveJS(const JS::Value& v) const { JS::ExposeValueToActiveJS(v); } void exposeGCThingToActiveJS(JSObject* obj) const { JS::ExposeObjectToActiveJS(obj); } bool markValue(JSTracer* trc, Value* x) { if (gc::IsMarked(x)) return false; TraceEdge(trc, x, "WeakMap entry value"); MOZ_ASSERT(gc::IsMarked(x)); return true; } - void nonMarkingTraceKeys(JSTracer* trc) { + void nonMarkingTraceKeys(JSTracer* trc) override { for (Enum e(*this); !e.empty(); e.popFront()) { Key key(e.front().key()); TraceEdge(trc, &key, "WeakMap entry key"); if (key != e.front().key()) entryMoved(e, key); } } - void nonMarkingTraceValues(JSTracer* trc) { + void nonMarkingTraceValues(JSTracer* trc) override { for (Range r = Base::all(); !r.empty(); r.popFront()) TraceEdge(trc, &r.front().value(), "WeakMap entry value"); } - bool keyNeedsMark(JSObject* key) { - if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp) { - JSObject* delegate = op(key); - /* - * Check if the delegate is marked with any color to properly handle - * gray marking when the key's delegate is black and the map is - * gray. - */ - return delegate && gc::IsMarkedUnbarriered(&delegate); - } + JSObject* getDelegate(JSObject* key) const { + JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp; + return op ? op(key) : nullptr; + } + + JSObject* getDelegate(gc::Cell* cell) const { + return nullptr; + } + + bool keyNeedsMark(JSObject* key) const { + JSObject* delegate = getDelegate(key); + /* + * Check if the delegate is marked with any color to properly handle + * gray marking when the key's delegate is black and the map is gray. + */ + return delegate && gc::IsMarkedUnbarriered(&delegate); + } + + bool keyNeedsMark(gc::Cell* cell) const { return false; } - bool keyNeedsMark(gc::Cell* cell) { - return false; - } - - bool markIteratively(JSTracer* trc) { + bool markIteratively(JSTracer* trc) override { bool markedAny = false; for (Enum e(*this); !e.empty(); e.popFront()) { /* If the entry is live, ensure its key and value are marked. */ Key key(e.front().key()); if (gc::IsMarked(const_cast<Key*>(&key))) { if (markValue(trc, &e.front().value())) markedAny = true; if (e.front().key() != key) entryMoved(e, key); } else if (keyNeedsMark(key)) { TraceEdge(trc, &e.front().value(), "WeakMap entry value"); TraceEdge(trc, &key, "proxy-preserved WeakMap entry key"); if (e.front().key() != key) entryMoved(e, key); markedAny = true; } - key.unsafeSet(nullptr); + key.unsafeSet(nullptr); // Prevent destructor from running barriers. } return markedAny; } - bool findZoneEdges() { + bool findZoneEdges() override { // This is overridden by ObjectValueMap. return true; } - void sweep() { + void sweep() override { /* Remove all entries whose keys remain unmarked. */ for (Enum e(*this); !e.empty(); e.popFront()) { Key k(e.front().key()); if (gc::IsAboutToBeFinalized(&k)) e.removeFront(); else if (k != e.front().key()) entryMoved(e, k); } /* * Once we've swept, all remaining edges should stay within the * known-live part of the graph. */ assertEntriesNotAboutToBeFinalized(); } - void finish() { + void finish() override { Base::finish(); } /* memberOf can be nullptr, which means that the map is not part of a JSObject. */ - void traceMappings(WeakMapTracer* tracer) { + void traceMappings(WeakMapTracer* tracer) override { for (Range r = Base::all(); !r.empty(); r.popFront()) { gc::Cell* key = gc::ToMarkable(r.front().key()); gc::Cell* value = gc::ToMarkable(r.front().value()); if (key && value) { tracer->trace(memberOf, JS::GCCellPtr(r.front().key().get()), JS::GCCellPtr(r.front().value().get())); } } } /* Rekey an entry when moved, ensuring we do not trigger barriers. */ void entryMoved(Enum& e, const Key& k) { e.rekeyFront(k); } -protected: + protected: void assertEntriesNotAboutToBeFinalized() { #if DEBUG for (Range r = Base::all(); !r.empty(); r.popFront()) { Key k(r.front().key()); MOZ_ASSERT(!gc::IsAboutToBeFinalized(&k)); MOZ_ASSERT(!gc::IsAboutToBeFinalized(&r.front().value())); MOZ_ASSERT(k == r.front().key()); }
--- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -46,28 +46,33 @@ typedef HashSet<ReadBarrieredGlobalObjec SystemAllocPolicy> WeakGlobalObjectSet; /* * A weakmap from GC thing keys to JSObject values that supports the keys being * in different compartments to the values. All values must be in the same * compartment. * * The purpose of this is to allow the garbage collector to easily find edges - * from debugee object compartments to debugger compartments when calculating + * from debuggee object compartments to debugger compartments when calculating * the compartment groups. Note that these edges are the inverse of the edges * stored in the cross compartment map. * * The current implementation results in all debuggee object compartments being * swept in the same group as the debugger. This is a conservative approach, * and compartments may be unnecessarily grouped, however it results in a * simpler and faster implementation. * * If InvisibleKeysOk is true, then the map can have keys in invisible-to- * debugger compartments. If it is false, we assert that such entries are never * created. + * + * Also note that keys in these weakmaps can be in any compartment, debuggee or + * not, because they cannot be deleted when a compartment is no longer a + * debuggee: the values need to maintain object identity across add/remove/add + * transitions. */ template <class UnbarrieredKey, bool InvisibleKeysOk=false> class DebuggerWeakMap : private WeakMap<PreBarriered<UnbarrieredKey>, RelocatablePtrObject> { private: typedef PreBarriered<UnbarrieredKey> Key; typedef RelocatablePtrObject Value;
--- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -2714,57 +2714,78 @@ nsFrame::IsSelectable(bool* aSelectable, // The -moz-all value acts similarly: if a frame has 'user-select:-moz-all', // all its children are selectable, even those with 'user-select:none'. // // As a result, if 'none' and '-moz-all' are not present in the frame hierarchy, // aSelectStyle returns the first style that is not AUTO. If these values // are present in the frame hierarchy, aSelectStyle returns the style of the // topmost parent that has either 'none' or '-moz-all'. // + // The -moz-text value acts as a way to override an ancestor's all/-moz-all value. + // // For instance, if the frame hierarchy is: - // AUTO -> _MOZ_ALL -> NONE -> TEXT, the returned value is _MOZ_ALL - // TEXT -> NONE -> AUTO -> _MOZ_ALL, the returned value is TEXT - // _MOZ_ALL -> TEXT -> AUTO -> AUTO, the returned value is _MOZ_ALL - // AUTO -> CELL -> TEXT -> AUTO, the returned value is TEXT + // AUTO -> _MOZ_ALL -> NONE -> TEXT, the returned value is ALL + // AUTO -> _MOZ_ALL -> NONE -> _MOZ_TEXT, the returned value is TEXT. + // TEXT -> NONE -> AUTO -> _MOZ_ALL, the returned value is TEXT + // _MOZ_ALL -> TEXT -> AUTO -> AUTO, the returned value is ALL + // _MOZ_ALL -> _MOZ_TEXT -> AUTO -> AUTO, the returned value is TEXT. + // AUTO -> CELL -> TEXT -> AUTO, the returned value is TEXT // uint8_t selectStyle = NS_STYLE_USER_SELECT_AUTO; nsIFrame* frame = const_cast<nsFrame*>(this); + bool containsEditable = false; while (frame) { const nsStyleUIReset* userinterface = frame->StyleUIReset(); switch (userinterface->mUserSelect) { case NS_STYLE_USER_SELECT_ALL: case NS_STYLE_USER_SELECT_MOZ_ALL: + { // override the previous values - selectStyle = userinterface->mUserSelect; + if (selectStyle != NS_STYLE_USER_SELECT_MOZ_TEXT) { + selectStyle = userinterface->mUserSelect; + } + nsIContent* frameContent = frame->GetContent(); + containsEditable = frameContent && + frameContent->EditableDescendantCount() > 0; break; + } default: // otherwise return the first value which is not 'auto' if (selectStyle == NS_STYLE_USER_SELECT_AUTO) { selectStyle = userinterface->mUserSelect; } break; } frame = nsLayoutUtils::GetParentOrPlaceholderFor(frame); } // convert internal values to standard values - if (selectStyle == NS_STYLE_USER_SELECT_AUTO) + if (selectStyle == NS_STYLE_USER_SELECT_AUTO || + selectStyle == NS_STYLE_USER_SELECT_MOZ_TEXT) selectStyle = NS_STYLE_USER_SELECT_TEXT; else if (selectStyle == NS_STYLE_USER_SELECT_MOZ_ALL) selectStyle = NS_STYLE_USER_SELECT_ALL; + // If user tries to select all of a non-editable content, + // prevent selection if it contains editable content. + bool allowSelection = true; + if (selectStyle == NS_STYLE_USER_SELECT_ALL) { + allowSelection = !containsEditable; + } + // return stuff if (aSelectStyle) *aSelectStyle = selectStyle; if (mState & NS_FRAME_GENERATED_CONTENT) *aSelectable = false; else - *aSelectable = (selectStyle != NS_STYLE_USER_SELECT_NONE); + *aSelectable = allowSelection && + (selectStyle != NS_STYLE_USER_SELECT_NONE); return NS_OK; } /** * Handles the Mouse Press Event for the frame */ NS_IMETHODIMP nsFrame::HandlePress(nsPresContext* aPresContext, @@ -3759,17 +3780,23 @@ nsIFrame::ContentOffsets OffsetsForSingl } static nsIFrame* AdjustFrameForSelectionStyles(nsIFrame* aFrame) { nsIFrame* adjustedFrame = aFrame; for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) { // These are the conditions that make all children not able to handle // a cursor. - if (frame->StyleUIReset()->mUserSelect == NS_STYLE_USER_SELECT_ALL || + uint8_t userSelect = frame->StyleUIReset()->mUserSelect; + if (userSelect == NS_STYLE_USER_SELECT_MOZ_TEXT) { + // If we see a -moz-text element, we shouldn't look further up the parent + // chain! + break; + } + if (userSelect == NS_STYLE_USER_SELECT_ALL || frame->IsGeneratedContentFrame()) { adjustedFrame = frame; } } return adjustedFrame; } nsIFrame::ContentOffsets nsIFrame::GetContentOffsetsFromPoint(nsPoint aPoint,
--- a/layout/style/contenteditable.css +++ b/layout/style/contenteditable.css @@ -8,16 +8,21 @@ *|*::-moz-canvas { cursor: text; } *|*:-moz-read-write :-moz-read-only { -moz-user-select: all; } +*|*:-moz-read-only > :-moz-read-write { + /* override the above -moz-user-select: all rule. */ + -moz-user-select: -moz-text; +} + input:-moz-read-write > .anonymous-div:-moz-read-only, textarea:-moz-read-write > .anonymous-div:-moz-read-only { -moz-user-select: text; } /* Use default arrow over objects with size that are selected when clicked on. Override the browser's pointer cursor over links
--- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -110,16 +110,17 @@ CSS_KEY(-moz-plaintext, _moz_plaintext) CSS_KEY(-moz-popup, _moz_popup) CSS_KEY(-moz-pre-space, _moz_pre_space) CSS_KEY(-moz-pull-down-menu, _moz_pull_down_menu) CSS_KEY(-moz-right, _moz_right) CSS_KEY(-moz-scrollbars-horizontal, _moz_scrollbars_horizontal) CSS_KEY(-moz-scrollbars-none, _moz_scrollbars_none) CSS_KEY(-moz-scrollbars-vertical, _moz_scrollbars_vertical) CSS_KEY(-moz-stack, _moz_stack) +CSS_KEY(-moz-text, _moz_text) CSS_KEY(-moz-use-system-font, _moz_use_system_font) CSS_KEY(-moz-use-text-color, _moz_use_text_color) CSS_KEY(-moz-visitedhyperlinktext, _moz_visitedhyperlinktext) CSS_KEY(-moz-window, _moz_window) CSS_KEY(-moz-workspace, _moz_workspace) CSS_KEY(-moz-zoom-in, _moz_zoom_in) CSS_KEY(-moz-zoom-out, _moz_zoom_out) CSS_KEY(absolute, absolute)
--- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -1836,16 +1836,17 @@ const KTableValue nsCSSProps::kUserSelec eCSSKeyword_text, NS_STYLE_USER_SELECT_TEXT, eCSSKeyword_element, NS_STYLE_USER_SELECT_ELEMENT, eCSSKeyword_elements, NS_STYLE_USER_SELECT_ELEMENTS, eCSSKeyword_all, NS_STYLE_USER_SELECT_ALL, eCSSKeyword_toggle, NS_STYLE_USER_SELECT_TOGGLE, eCSSKeyword_tri_state, NS_STYLE_USER_SELECT_TRI_STATE, eCSSKeyword__moz_all, NS_STYLE_USER_SELECT_MOZ_ALL, eCSSKeyword__moz_none, NS_STYLE_USER_SELECT_NONE, + eCSSKeyword__moz_text, NS_STYLE_USER_SELECT_MOZ_TEXT, eCSSKeyword_UNKNOWN,-1 }; const KTableValue nsCSSProps::kVerticalAlignKTable[] = { eCSSKeyword_baseline, NS_STYLE_VERTICAL_ALIGN_BASELINE, eCSSKeyword_sub, NS_STYLE_VERTICAL_ALIGN_SUB, eCSSKeyword_super, NS_STYLE_VERTICAL_ALIGN_SUPER, eCSSKeyword_top, NS_STYLE_VERTICAL_ALIGN_TOP,
--- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -100,16 +100,17 @@ static inline mozilla::css::Side operato #define NS_STYLE_USER_SELECT_ELEMENT 2 #define NS_STYLE_USER_SELECT_ELEMENTS 3 #define NS_STYLE_USER_SELECT_ALL 4 #define NS_STYLE_USER_SELECT_TOGGLE 5 #define NS_STYLE_USER_SELECT_TRI_STATE 6 #define NS_STYLE_USER_SELECT_AUTO 7 // internal value - please use nsFrame::IsSelectable() #define NS_STYLE_USER_SELECT_MOZ_ALL 8 // force selection of all children, unless an ancestor has NONE set - bug 48096 #define NS_STYLE_USER_SELECT_MOZ_NONE 9 // Like NONE, but doesn't change selection behavior for descendants whose user-select is not AUTO. +#define NS_STYLE_USER_SELECT_MOZ_TEXT 10 // Like TEXT, except that it won't get overridden by ancestors having ALL. // user-input #define NS_STYLE_USER_INPUT_NONE 0 #define NS_STYLE_USER_INPUT_ENABLED 1 #define NS_STYLE_USER_INPUT_DISABLED 2 #define NS_STYLE_USER_INPUT_AUTO 3 // user-modify
--- a/widget/android/AndroidContentController.h +++ b/widget/android/AndroidContentController.h @@ -2,33 +2,38 @@ * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef AndroidContentController_h__ #define AndroidContentController_h__ #include "mozilla/layers/ChromeProcessController.h" -#include "mozilla/layers/APZEventState.h" #include "mozilla/EventForwards.h" // for Modifiers #include "mozilla/StaticPtr.h" #include "mozilla/TimeStamp.h" #include "GeneratedJNIWrappers.h" #include "nsIDOMWindowUtils.h" #include "nsTArray.h" namespace mozilla { +namespace layers { +class APZEventState; +class APZCTreeManager; +} namespace widget { namespace android { class AndroidContentController final : public mozilla::layers::ChromeProcessController { public: - AndroidContentController(nsIWidget* aWidget, mozilla::layers::APZEventState* aAPZEventState) - : mozilla::layers::ChromeProcessController(aWidget, aAPZEventState) + AndroidContentController(nsIWidget* aWidget, + mozilla::layers::APZEventState* aAPZEventState, + mozilla::layers::APZCTreeManager* aAPZCTreeManager) + : mozilla::layers::ChromeProcessController(aWidget, aAPZEventState, aAPZCTreeManager) {} // ChromeProcessController methods void PostDelayedTask(Task* aTask, int aDelayMs) override; public: static NativePanZoomController::LocalRef SetNativePanZoomController(NativePanZoomController::Param obj); static void NotifyDefaultPrevented(uint64_t aInputBlockId, bool aDefaultPrevented);
--- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -2517,17 +2517,17 @@ void nsWindow::ConfigureAPZControllerThread() { APZThreadUtils::SetControllerThread(nullptr); } already_AddRefed<GeckoContentController> nsWindow::CreateRootContentController() { - nsRefPtr<GeckoContentController> controller = new widget::android::AndroidContentController(this, mAPZEventState); + nsRefPtr<GeckoContentController> controller = new widget::android::AndroidContentController(this, mAPZEventState, mAPZC); return controller.forget(); } uint64_t nsWindow::RootLayerTreeId() { MOZ_ASSERT(sCompositorParent); return sCompositorParent->RootLayerTreeId();
--- a/widget/nsBaseWidget.cpp +++ b/widget/nsBaseWidget.cpp @@ -866,17 +866,17 @@ void nsBaseWidget::CreateCompositor() nsIntRect rect; GetBounds(rect); CreateCompositor(rect.width, rect.height); } already_AddRefed<GeckoContentController> nsBaseWidget::CreateRootContentController() { - nsRefPtr<GeckoContentController> controller = new ChromeProcessController(this, mAPZEventState); + nsRefPtr<GeckoContentController> controller = new ChromeProcessController(this, mAPZEventState, mAPZC); return controller.forget(); } class ChromeProcessSetAllowedTouchBehaviorCallback : public SetAllowedTouchBehaviorCallback { public: explicit ChromeProcessSetAllowedTouchBehaviorCallback(APZCTreeManager* aTreeManager) : mTreeManager(aTreeManager) {}
--- a/xpcom/base/nsTraceRefcnt.cpp +++ b/xpcom/base/nsTraceRefcnt.cpp @@ -35,16 +35,21 @@ #include "mozilla/AutoRestore.h" #include "mozilla/BlockingResourceBase.h" #include "mozilla/PoisonIOInterposer.h" #ifdef HAVE_DLOPEN #include <dlfcn.h> #endif +#ifdef MOZ_DMD +#include "base/process_util.h" +#include "nsMemoryInfoDumper.h" +#endif + //////////////////////////////////////////////////////////////////////////////// #define NS_IMPL_REFCNT_LOGGING #ifdef NS_IMPL_REFCNT_LOGGING #include "plhash.h" #include "prmem.h" @@ -940,16 +945,49 @@ NS_LogInit() } EXPORT_XPCOM_API(void) NS_LogTerm() { mozilla::LogTerm(); } +#ifdef MOZ_DMD +// If MOZ_DMD_SHUTDOWN_LOG is set, dump a DMD report to a file. +// The value of this environment variable is used as the prefix +// of the file name, so you probably want something like "/tmp/". +// By default, this is run in all processes, but you can record a +// log only for a specific process type by setting MOZ_DMD_LOG_PROCESS +// to the process type you want to log, such as "default" or "tab". +// This method can't use the higher level XPCOM file utilities +// because it is run very late in shutdown to avoid recording +// information about refcount logging entries. +static void +LogDMDFile() +{ + const char* dmdFilePrefix = PR_GetEnv("MOZ_DMD_SHUTDOWN_LOG"); + if (!dmdFilePrefix) { + return; + } + + const char* logProcessEnv = PR_GetEnv("MOZ_DMD_LOG_PROCESS"); + if (logProcessEnv && !!strcmp(logProcessEnv, XRE_ChildProcessTypeToString(XRE_GetProcessType()))) { + return; + } + + nsPrintfCString fileName("%sdmd-%d.log.gz", dmdFilePrefix, base::GetCurrentProcId()); + FILE* logFile = fopen(fileName.get(), "w"); + if (NS_WARN_IF(!logFile)) { + return; + } + + nsMemoryInfoDumper::DumpDMDToFile(logFile); +} +#endif + namespace mozilla { void LogTerm() { NS_ASSERTION(gInitCount > 0, "NS_LogTerm without matching NS_LogInit"); if (--gInitCount == 0) { @@ -973,16 +1011,20 @@ LogTerm() nsTraceRefcnt::DumpStatistics(); nsTraceRefcnt::ResetStatistics(); } nsTraceRefcnt::Shutdown(); #ifdef NS_IMPL_REFCNT_LOGGING nsTraceRefcnt::SetActivityIsLegal(false); gActivityTLS = BAD_TLS_INDEX; #endif + +#ifdef MOZ_DMD + LogDMDFile(); +#endif } } } // namespace mozilla EXPORT_XPCOM_API(void) NS_LogAddRef(void* aPtr, nsrefcnt aRefcnt, const char* aClass, uint32_t aClassSize)