diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 3be69fa769b..00e1f2318a2 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -1392,6 +1392,21 @@ Element::GetElementsByClassName(const nsAString& aClassNames, 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, @@ -1464,6 +1479,8 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, 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()) { @@ -1555,6 +1572,8 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, 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; @@ -1571,6 +1590,8 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, child = child->GetNextSibling()) { rv = child->BindToTree(aDocument, this, this, allowScripts); NS_ENSURE_SUCCESS(rv, rv); + + editableDescendantCount += EditableInclusiveDescendantCount(child); } } } @@ -1583,6 +1604,28 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, 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); @@ -1686,6 +1729,20 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent) 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; @@ -1697,6 +1754,10 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent) } ClearInDocument(); + // Editable descendant count only counts descendants that + // are in the uncomposed document. + ResetEditableDescendantCount(); + if (aNullParent || !mParent->IsInShadowTree()) { UnsetFlags(NODE_IS_IN_SHADOW_TREE); diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 05894fd3c38..8d5360cd4bb 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -9007,7 +9007,7 @@ nsDocument::UnblockOnload(bool aFireSync) // 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 asyncDispatcher = new AsyncEventDispatcher(this, NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"), diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index d60ada2ec73..e5d5efab2e1 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -110,7 +110,8 @@ using namespace mozilla; using namespace mozilla::dom; nsINode::nsSlots::nsSlots() - : mWeakReference(nullptr) + : mWeakReference(nullptr), + mEditableDescendantCount(0) { } @@ -1345,6 +1346,38 @@ nsINode::GetOwnerGlobal() const 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 { diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h index 7a18242c48f..039d1600cac 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h @@ -1054,6 +1054,12 @@ public: * 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; }; /** @@ -1089,6 +1095,22 @@ public: 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) { diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index 13eeeda845c..fc471103384 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -525,6 +525,14 @@ nsGenericHTMLElement::IntrinsicState() const 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, @@ -548,6 +556,7 @@ nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, aDocument-> AddToNameTable(this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue()); } + if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue) { nsCOMPtr htmlDocument = do_QueryInterface(aDocument); if (htmlDocument) { @@ -2917,6 +2926,12 @@ nsGenericHTMLElement::ChangeEditableState(int32_t aChange) if (htmlDocument) { htmlDocument->ChangeContentEditableCount(this, aChange); } + + nsIContent* parent = GetParent(); + while (parent) { + parent->ChangeEditableDescendantCount(aChange); + parent = parent->GetParent(); + } } if (document->HasFlag(NODE_IS_EDITABLE)) { diff --git a/dom/html/nsGenericHTMLElement.h b/dom/html/nsGenericHTMLElement.h index c12f999b4a3..abb94fd57d1 100644 --- a/dom/html/nsGenericHTMLElement.h +++ b/dom/html/nsGenericHTMLElement.h @@ -215,6 +215,13 @@ public: } 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) diff --git a/dom/requestsync/RequestSyncService.jsm b/dom/requestsync/RequestSyncService.jsm index 5c4ac2fc8fa..3137a3e7647 100644 --- a/dom/requestsync/RequestSyncService.jsm +++ b/dom/requestsync/RequestSyncService.jsm @@ -47,6 +47,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "secMan", "@mozilla.org/scriptsecuritymanager;1", "nsIScriptSecurityManager"); +XPCOMUtils.defineLazyModuleGetter(this, "AlarmService", + "resource://gre/modules/AlarmService.jsm"); + this.RequestSyncService = { __proto__: IndexedDBHelper.prototype, @@ -863,17 +866,16 @@ this.RequestSyncService = { }, 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) { @@ -882,7 +884,7 @@ this.RequestSyncService = { 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]; } }, diff --git a/dom/requestsync/tests/mochitest.ini b/dom/requestsync/tests/mochitest.ini index e9815915853..ca6ea904965 100644 --- a/dom/requestsync/tests/mochitest.ini +++ b/dom/requestsync/tests/mochitest.ini @@ -9,13 +9,18 @@ support-files = 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" diff --git a/editor/libeditor/tests/mochitest.ini b/editor/libeditor/tests/mochitest.ini index cada7fa72bc..88553dae8a4 100644 --- a/editor/libeditor/tests/mochitest.ini +++ b/editor/libeditor/tests/mochitest.ini @@ -165,3 +165,5 @@ skip-if = toolkit == 'android' [test_bug1109465.html] [test_bug1162952.html] [test_bug1186799.html] +[test_bug1181130-1.html] +[test_bug1181130-2.html] diff --git a/editor/libeditor/tests/test_bug1181130-1.html b/editor/libeditor/tests/test_bug1181130-1.html new file mode 100644 index 00000000000..eb27526a35f --- /dev/null +++ b/editor/libeditor/tests/test_bug1181130-1.html @@ -0,0 +1,50 @@ + + + + + + Test for Bug 1181130 + + + + + +Mozilla Bug 1181130 +

+
+ editable div +
+ non-editable div +
nested editable div
+
+
+ +
+
+ + diff --git a/editor/libeditor/tests/test_bug1181130-2.html b/editor/libeditor/tests/test_bug1181130-2.html new file mode 100644 index 00000000000..edb380e988b --- /dev/null +++ b/editor/libeditor/tests/test_bug1181130-2.html @@ -0,0 +1,44 @@ + + + + + + Test for Bug 1181130 + + + + + +Mozilla Bug 1181130 +

+
+ editable div +
+ non-editable div +
+
+ +
+
+ + diff --git a/gfx/layers/apz/util/APZCCallbackHelper.cpp b/gfx/layers/apz/util/APZCCallbackHelper.cpp index afdd42e0e10..da4377ff25f 100644 --- a/gfx/layers/apz/util/APZCCallbackHelper.cpp +++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp @@ -392,8 +392,8 @@ APZCCallbackHelper::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollI } } -static nsIPresShell* -GetRootContentDocumentPresShellForContent(nsIContent* aContent) +nsIPresShell* +APZCCallbackHelper::GetRootContentDocumentPresShellForContent(nsIContent* aContent) { nsIDocument* doc = aContent->GetComposedDoc(); if (!doc) { diff --git a/gfx/layers/apz/util/APZCCallbackHelper.h b/gfx/layers/apz/util/APZCCallbackHelper.h index e2d9ff607d1..a2d8f49f668 100644 --- a/gfx/layers/apz/util/APZCCallbackHelper.h +++ b/gfx/layers/apz/util/APZCCallbackHelper.h @@ -85,6 +85,9 @@ public: 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 diff --git a/gfx/layers/apz/util/ChromeProcessController.cpp b/gfx/layers/apz/util/ChromeProcessController.cpp index 3fb505bc3e0..b4675b23c45 100644 --- a/gfx/layers/apz/util/ChromeProcessController.cpp +++ b/gfx/layers/apz/util/ChromeProcessController.cpp @@ -11,6 +11,8 @@ #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" @@ -22,19 +24,25 @@ 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() { @@ -98,7 +106,7 @@ ChromeProcessController::GetPresShell() const } nsIDocument* -ChromeProcessController::GetDocument() const +ChromeProcessController::GetRootDocument() const { if (nsIPresShell* presShell = GetPresShell()) { return presShell->GetDocument(); @@ -106,16 +114,50 @@ ChromeProcessController::GetDocument() const return nullptr; } -already_AddRefed -ChromeProcessController::GetDOMWindowUtils() const +nsIDocument* +ChromeProcessController::GetRootContentDocument(const FrameMetrics::ViewID& aScrollId) const { - if (nsIDocument* doc = GetDocument()) { - nsCOMPtr 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 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, @@ -162,7 +204,7 @@ ChromeProcessController::NotifyAPZStateChange(const ScrollableLayerGuid& aGuid, return; } - mAPZEventState->ProcessAPZStateChange(GetDocument(), aGuid.mScrollId, aChange, aArg); + mAPZEventState->ProcessAPZStateChange(GetRootDocument(), aGuid.mScrollId, aChange, aArg); } void diff --git a/gfx/layers/apz/util/ChromeProcessController.h b/gfx/layers/apz/util/ChromeProcessController.h index 6580e1ec5b6..ab77470dc8f 100644 --- a/gfx/layers/apz/util/ChromeProcessController.h +++ b/gfx/layers/apz/util/ChromeProcessController.h @@ -21,6 +21,7 @@ namespace mozilla { namespace layers { +class APZCTreeManager; class APZEventState; // A ChromeProcessController is attached to the root of a compositor's layer @@ -31,7 +32,8 @@ class ChromeProcessController : public mozilla::layers::GeckoContentController 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 @@ -43,7 +45,7 @@ public: 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, @@ -60,12 +62,13 @@ public: private: nsCOMPtr mWidget; nsRefPtr mAPZEventState; + nsRefPtr mAPZCTreeManager; MessageLoop* mUILoop; void InitializeRoot(); nsIPresShell* GetPresShell() const; - nsIDocument* GetDocument() const; - already_AddRefed GetDOMWindowUtils() const; + nsIDocument* GetRootDocument() const; + nsIDocument* GetRootContentDocument(const FrameMetrics::ViewID& aScrollId) const; }; } // namespace layers diff --git a/js/public/TracingAPI.h b/js/public/TracingAPI.h index 280cf18fec7..01ffe16811d 100644 --- a/js/public/TracingAPI.h +++ b/js/public/TracingAPI.h @@ -27,9 +27,21 @@ 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) @@ -38,16 +50,18 @@ class JS_PUBLIC_API(JSTracer) // 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(); @@ -55,13 +69,15 @@ class JS_PUBLIC_API(JSTracer) 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 { diff --git a/js/src/devtools/rootAnalysis/annotations.js b/js/src/devtools/rootAnalysis/annotations.js index 719b3c13880..c7c664c92f4 100644 --- a/js/src/devtools/rootAnalysis/annotations.js +++ b/js/src/devtools/rootAnalysis/annotations.js @@ -239,6 +239,10 @@ function ignoreGCFunction(mangled) if (fun.indexOf("void nsCOMPtr::Assert_NoQueryNeeded()") >= 0) return true; + // These call through an 'op' function pointer. + if (fun.indexOf("js::WeakMap::getDelegate(") >= 0) + return true; + // XXX modify refillFreeList to not need data flow analysis to understand it cannot GC. if (/refillFreeList/.test(fun) && /\(js::AllowGC\)0u/.test(fun)) return true; diff --git a/js/src/ds/OrderedHashTable.h b/js/src/ds/OrderedHashTable.h index 8b12367e554..c9927212976 100644 --- a/js/src/ds/OrderedHashTable.h +++ b/js/src/ds/OrderedHashTable.h @@ -672,7 +672,8 @@ class OrderedHashMap public: Entry() : key(), value() {} - Entry(const Key& k, const Value& v) : key(k), value(v) {} + template + Entry(const Key& k, V&& v) : key(k), value(Forward(v)) {} Entry(Entry&& rhs) : key(Move(rhs.key)), value(Move(rhs.value)) {} const Key key; @@ -707,7 +708,8 @@ class OrderedHashMap 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 + bool put(const Key& key, V&& value) { return impl.put(Entry(key, Forward(value))); } bool remove(const Key& key, bool* foundp) { return impl.remove(key, foundp); } bool clear() { return impl.clear(); } diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index b11b555dcee..7f48bfda44a 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -602,6 +602,74 @@ DispatchToTracer(JSTracer* trc, T* thingp, const char* name) /*** GC Marking Interface *************************************************************************/ +namespace js { + +typedef bool DoNothingMarkingType; + +template +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 call markPotentialEphemeronKey. +template <> +struct LinearlyMarkedEphemeronKeyType { + typedef JSObject* Type; +}; + +template <> +struct LinearlyMarkedEphemeronKeyType { + 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 +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 +void +GCMarker::markPotentialEphemeronKey(T* thing) +{ + markPotentialEphemeronKeyHelper::Type>(thing); +} + +} // namespace js + template static inline bool MustSkipMarking(T thing) @@ -700,7 +768,6 @@ js::GCMarker::markAndTraceChildren(T* thing) 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 @@ -724,18 +791,22 @@ template <> void GCMarker::traverse(Shape* thing) { markAndScan(thing); } // 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 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 { @@ -1274,6 +1345,10 @@ GCMarker::processMarkStackTop(SliceBudget& budget) return reinterpret_cast(addr)->traceChildren(this); } + case ScriptTag: { + return reinterpret_cast(addr)->traceChildren(this); + } + case SavedValueArrayTag: { MOZ_ASSERT(!(addr & CellMask)); JSObject* obj = reinterpret_cast(addr); @@ -1327,6 +1402,7 @@ GCMarker::processMarkStackTop(SliceBudget& budget) return; } + markPotentialEphemeronKey(obj); ObjectGroup* group = obj->groupFromGC(); traverseEdge(obj, group); @@ -1591,11 +1667,13 @@ MarkStack::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const /*** 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), @@ -1608,7 +1686,7 @@ GCMarker::GCMarker(JSRuntime* rt) bool GCMarker::init(JSGCMode gcMode) { - return stack.init(gcMode); + return stack.init(gcMode) && weakKeys.init(); } void @@ -1617,10 +1695,10 @@ GCMarker::start() MOZ_ASSERT(!started); started = true; color = BLACK; + linearWeakMarkingDisabled_ = false; MOZ_ASSERT(!unmarkedArenaStackTop); MOZ_ASSERT(markLaterArenas == 0); - } void @@ -1636,6 +1714,7 @@ GCMarker::stop() /* Free non-ballast stack memory. */ stack.reset(); + weakKeys.clear(); } void @@ -1660,6 +1739,27 @@ GCMarker::reset() 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) { @@ -2351,8 +2451,8 @@ struct AssertNonGrayTracer : public JS::CallbackTracer { 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), diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index c30e77158a8..bb57b1a7574 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -8,12 +8,16 @@ #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" @@ -132,6 +136,35 @@ class MarkStack 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 WeakEntryVector; + +typedef OrderedHashMap WeakKeyTable; + +} /* namespace gc */ + class GCMarker : public JSTracer { public: @@ -173,6 +206,23 @@ class GCMarker : public JSTracer } 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); @@ -195,6 +245,14 @@ class GCMarker : public JSTracer 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); @@ -213,6 +271,7 @@ class GCMarker : public JSTracer GroupTag, SavedValueArrayTag, JitCodeTag, + ScriptTag, LastTag = JitCodeTag }; @@ -230,6 +289,8 @@ class GCMarker : public JSTracer template void markAndTraceChildren(T* thing); template void markAndPush(StackTag tag, T* thing); template void markAndScan(T* thing); + template void markPotentialEphemeronKeyHelper(T oldThing); + template void markPotentialEphemeronKey(T* oldThing); void eagerlyMarkChildren(JSLinearString* str); void eagerlyMarkChildren(JSRope* rope); void eagerlyMarkChildren(JSString* str); @@ -287,6 +348,12 @@ class GCMarker : public JSTracer /* 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 markLaterArenas; diff --git a/js/src/gc/Tracer.h b/js/src/gc/Tracer.h index 4cd01224b8f..465b03a4209 100644 --- a/js/src/gc/Tracer.h +++ b/js/src/gc/Tracer.h @@ -23,11 +23,11 @@ namespace js { // 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 diff --git a/js/src/jit-test/tests/gc/weak-marking-01.js b/js/src/jit-test/tests/gc/weak-marking-01.js new file mode 100644 index 00000000000..7a100f6e102 --- /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(); diff --git a/js/src/jit-test/tests/gc/weak-marking-02.js b/js/src/jit-test/tests/gc/weak-marking-02.js new file mode 100644 index 00000000000..c3d9a051657 --- /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(); diff --git a/js/src/jsapi-tests/testWeakMap.cpp b/js/src/jsapi-tests/testWeakMap.cpp index acb3b9487b5..2b17681c015 100644 --- a/js/src/jsapi-tests/testWeakMap.cpp +++ b/js/src/jsapi-tests/testWeakMap.cpp @@ -70,7 +70,6 @@ BEGIN_TEST(testWeakMap_keyDelegates) { JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); JS_GC(rt); - JS::RootedObject map(cx, JS::NewWeakMapObject(cx)); CHECK(map); @@ -81,16 +80,26 @@ BEGIN_TEST(testWeakMap_keyDelegates) 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. */ @@ -100,7 +109,7 @@ BEGIN_TEST(testWeakMap_keyDelegates) /* 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)); @@ -108,14 +117,14 @@ BEGIN_TEST(testWeakMap_keyDelegates) /* * 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)); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 49efd339e2d..a0f0fe6134a 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -4023,11 +4023,19 @@ GCRuntime::markWeakReferences(gcstats::Phase phase) 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); @@ -4039,6 +4047,8 @@ GCRuntime::markWeakReferences(gcstats::Phase phase) marker.drainMarkStack(unlimited); } MOZ_ASSERT(marker.isDrained()); + + marker.leaveWeakMarkingMode(); } void @@ -4181,12 +4191,23 @@ js::gc::MarkingValidator::nonIncrementalMark() 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; @@ -4249,7 +4270,13 @@ js::gc::MarkingValidator::nonIncrementalMark() 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; } @@ -4754,7 +4781,6 @@ GCRuntime::endMarkingZoneGroup() * black by the action of UnmarkGray. */ MarkIncomingCrossCompartmentPointers(rt, BLACK); - markWeakReferencesInCurrentGroup(gcstats::PHASE_SWEEP_MARK_WEAK); /* @@ -4915,6 +4941,17 @@ GCRuntime::beginSweepingZoneGroup() 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); diff --git a/js/src/jswatchpoint.cpp b/js/src/jswatchpoint.cpp index 37f39225c3f..28ca0f0d3a6 100644 --- a/js/src/jswatchpoint.cpp +++ b/js/src/jswatchpoint.cpp @@ -144,14 +144,6 @@ WatchpointMap::triggerWatchpoint(JSContext* cx, HandleObject obj, HandleId id, M 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) { diff --git a/js/src/jswatchpoint.h b/js/src/jswatchpoint.h index 541bc5c1502..c344ec8e3ab 100644 --- a/js/src/jswatchpoint.h +++ b/js/src/jswatchpoint.h @@ -70,7 +70,6 @@ class WatchpointMap { 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); diff --git a/js/src/jsweakmap.cpp b/js/src/jsweakmap.cpp index a90ae99fa2d..1d8a9fc1c6f 100644 --- a/js/src/jsweakmap.cpp +++ b/js/src/jsweakmap.cpp @@ -50,22 +50,26 @@ 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); } } @@ -80,7 +84,7 @@ WeakMapBase::unmarkCompartment(JSCompartment* c) 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) @@ -158,7 +162,7 @@ WeakMapBase::saveCompartmentMarkedWeakMaps(JSCompartment* c, WeakMapSet& markedW } void -WeakMapBase::restoreCompartmentMarkedWeakMaps(WeakMapSet& markedWeakMaps) +WeakMapBase::restoreMarkedWeakMaps(WeakMapSet& markedWeakMaps) { for (WeakMapSet::Range r = markedWeakMaps.all(); !r.empty(); r.popFront()) { WeakMapBase* map = r.front(); @@ -186,7 +190,7 @@ 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; diff --git a/js/src/jsweakmap.h b/js/src/jsweakmap.h index 501a6e2ccab..fdaf3ff062d 100644 --- a/js/src/jsweakmap.h +++ b/js/src/jsweakmap.h @@ -7,6 +7,8 @@ #ifndef jsweakmap_h #define jsweakmap_h +#include "mozilla/Move.h" + #include "jscompartment.h" #include "jsfriendapi.h" #include "jsobj.h" @@ -39,6 +41,8 @@ typedef HashSet, SystemAllocPolicy> We // 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(); @@ -75,11 +79,17 @@ class WeakMapBase { 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. @@ -106,6 +116,17 @@ class WeakMapBase { bool marked; }; +template +static T extractUnbarriered(BarrieredBase v) +{ + return v.get(); +} +template +static T* extractUnbarriered(T* v) +{ + return v; +} + template > class WeakMap : public HashMap, public WeakMapBase @@ -154,6 +175,77 @@ class WeakMap : public HashMap, publ 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(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(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); } @@ -166,7 +258,7 @@ class WeakMap : public HashMap, publ 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"); @@ -175,29 +267,34 @@ class WeakMap : public HashMap, publ } } - 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. */ @@ -214,17 +311,17 @@ class WeakMap : public HashMap, publ 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()); @@ -240,12 +337,12 @@ class WeakMap : public HashMap, publ 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()); @@ -262,7 +359,7 @@ class WeakMap : public HashMap, publ e.rekeyFront(k); } -protected: + protected: void assertEntriesNotAboutToBeFinalized() { #if DEBUG for (Range r = Base::all(); !r.empty(); r.popFront()) { diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index b18def3b78e..34608c8d10f 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -51,7 +51,7 @@ typedef HashSet class DebuggerWeakMap : private WeakMap, RelocatablePtrObject> diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index f3404865114..3ae86d22411 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -2719,23 +2719,35 @@ nsFrame::IsSelectable(bool* aSelectable, uint8_t* aSelectStyle) const // 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(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) { @@ -2747,19 +2759,28 @@ nsFrame::IsSelectable(bool* aSelectable, uint8_t* aSelectStyle) const } // 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; } @@ -3764,7 +3785,13 @@ static nsIFrame* AdjustFrameForSelectionStyles(nsIFrame* aFrame) { { // 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; } diff --git a/layout/style/contenteditable.css b/layout/style/contenteditable.css index 7b248837fe7..436ecb053d7 100644 --- a/layout/style/contenteditable.css +++ b/layout/style/contenteditable.css @@ -13,6 +13,11 @@ -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; diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index 600ef7f39bd..44509b40bb4 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -115,6 +115,7 @@ 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) diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index ac0145bbb19..9b55b5e8a91 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -1841,6 +1841,7 @@ const KTableValue nsCSSProps::kUserSelectKTable[] = { 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 }; diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index dfe8aa519e6..eec1d429db8 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -105,6 +105,7 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) { #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 diff --git a/widget/android/AndroidContentController.h b/widget/android/AndroidContentController.h index 46fbd0ddb39..fa5b02a4128 100644 --- a/widget/android/AndroidContentController.h +++ b/widget/android/AndroidContentController.h @@ -7,7 +7,6 @@ #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" @@ -16,14 +15,20 @@ #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 diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp index b3df3f21170..1a495079b60 100644 --- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -2522,7 +2522,7 @@ nsWindow::ConfigureAPZControllerThread() already_AddRefed nsWindow::CreateRootContentController() { - nsRefPtr controller = new widget::android::AndroidContentController(this, mAPZEventState); + nsRefPtr controller = new widget::android::AndroidContentController(this, mAPZEventState, mAPZC); return controller.forget(); } diff --git a/widget/nsBaseWidget.cpp b/widget/nsBaseWidget.cpp index 92807e0bc40..a68132a4fb4 100644 --- a/widget/nsBaseWidget.cpp +++ b/widget/nsBaseWidget.cpp @@ -871,7 +871,7 @@ void nsBaseWidget::CreateCompositor() already_AddRefed nsBaseWidget::CreateRootContentController() { - nsRefPtr controller = new ChromeProcessController(this, mAPZEventState); + nsRefPtr controller = new ChromeProcessController(this, mAPZEventState, mAPZC); return controller.forget(); } diff --git a/xpcom/base/nsTraceRefcnt.cpp b/xpcom/base/nsTraceRefcnt.cpp index ca2be147f35..135ab29ddd3 100644 --- a/xpcom/base/nsTraceRefcnt.cpp +++ b/xpcom/base/nsTraceRefcnt.cpp @@ -40,6 +40,11 @@ #include #endif +#ifdef MOZ_DMD +#include "base/process_util.h" +#include "nsMemoryInfoDumper.h" +#endif + //////////////////////////////////////////////////////////////////////////////// #define NS_IMPL_REFCNT_LOGGING @@ -945,6 +950,39 @@ 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() @@ -978,6 +1016,10 @@ LogTerm() nsTraceRefcnt::SetActivityIsLegal(false); gActivityTLS = BAD_TLS_INDEX; #endif + +#ifdef MOZ_DMD + LogDMDFile(); +#endif } }