Merge inbound to m-c a=merge

This commit is contained in:
Wes Kocher 2015-08-14 16:31:17 -07:00
commit b5712fe93a
39 changed files with 1140 additions and 116 deletions

View File

@ -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);

View File

@ -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<AsyncEventDispatcher> asyncDispatcher =
new AsyncEventDispatcher(this,
NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"),

View File

@ -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
{

View File

@ -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) {

View File

@ -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<nsIHTMLDocument> 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)) {

View File

@ -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)

View File

@ -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];
}
},

View File

@ -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"

View File

@ -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]

View File

@ -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>

View File

@ -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>

View File

@ -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) {

View File

@ -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

View File

@ -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<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,
@ -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

View File

@ -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<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

View File

@ -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 {

View File

@ -239,6 +239,10 @@ function ignoreGCFunction(mangled)
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;

View File

@ -672,7 +672,8 @@ class OrderedHashMap
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;
@ -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 <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(); }

View File

@ -602,6 +602,74 @@ DispatchToTracer(JSTracer* trc, T* thingp, const char* 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)
@ -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 <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 {
@ -1274,6 +1345,10 @@ GCMarker::processMarkStackTop(SliceBudget& budget)
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);
@ -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),

View File

@ -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<WeakMarkable, 2, js::SystemAllocPolicy> WeakEntryVector;
typedef OrderedHashMap<JS::GCCellPtr,
WeakEntryVector,
WeakKeyTableHashPolicy,
js::SystemAllocPolicy> 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 <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);
@ -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<size_t> markLaterArenas;

View File

@ -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

View File

@ -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();

View File

@ -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();

View File

@ -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));

View File

@ -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);

View File

@ -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)
{

View File

@ -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);

View File

@ -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;

View File

@ -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<WeakMapBase*, DefaultHasher<WeakMapBase*>, 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 <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
@ -154,6 +175,77 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>, 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<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); }
@ -166,7 +258,7 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>, 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<Key, Value, HashPolicy, RuntimeAllocPolicy>, 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<Key, Value, HashPolicy, RuntimeAllocPolicy>, 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<Key, Value, HashPolicy, RuntimeAllocPolicy>, 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<Key, Value, HashPolicy, RuntimeAllocPolicy>, publ
e.rekeyFront(k);
}
protected:
protected:
void assertEntriesNotAboutToBeFinalized() {
#if DEBUG
for (Range r = Base::all(); !r.empty(); r.popFront()) {

View File

@ -51,7 +51,7 @@ typedef HashSet<ReadBarrieredGlobalObject,
* 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.
*
@ -63,6 +63,11 @@ typedef HashSet<ReadBarrieredGlobalObject,
* 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>

View File

@ -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<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) {
@ -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;
}

View File

@ -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;

View File

@ -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)

View File

@ -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
};

View File

@ -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

View File

@ -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

View File

@ -2522,7 +2522,7 @@ nsWindow::ConfigureAPZControllerThread()
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();
}

View File

@ -871,7 +871,7 @@ void nsBaseWidget::CreateCompositor()
already_AddRefed<GeckoContentController>
nsBaseWidget::CreateRootContentController()
{
nsRefPtr<GeckoContentController> controller = new ChromeProcessController(this, mAPZEventState);
nsRefPtr<GeckoContentController> controller = new ChromeProcessController(this, mAPZEventState, mAPZC);
return controller.forget();
}

View File

@ -40,6 +40,11 @@
#include <dlfcn.h>
#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
}
}