mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge inbound to m-c a=merge
This commit is contained in:
commit
b5712fe93a
@ -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);
|
||||
|
||||
|
@ -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"),
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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) {
|
||||
|
@ -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)) {
|
||||
|
@ -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)
|
||||
|
@ -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];
|
||||
}
|
||||
},
|
||||
|
@ -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"
|
||||
|
@ -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]
|
||||
|
50
editor/libeditor/tests/test_bug1181130-1.html
Normal file
50
editor/libeditor/tests/test_bug1181130-1.html
Normal 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>
|
44
editor/libeditor/tests/test_bug1181130-2.html
Normal file
44
editor/libeditor/tests/test_bug1181130-2.html
Normal 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>
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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(); }
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
193
js/src/jit-test/tests/gc/weak-marking-01.js
Normal file
193
js/src/jit-test/tests/gc/weak-marking-01.js
Normal 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();
|
128
js/src/jit-test/tests/gc/weak-marking-02.js
Normal file
128
js/src/jit-test/tests/gc/weak-marking-02.js
Normal 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();
|
@ -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));
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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()) {
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user