mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 881120 - Allow clients to specify nodes that shouldn't be released. r=jwalker
This commit is contained in:
parent
6b355d8fa4
commit
3cc23fe0ec
@ -209,6 +209,11 @@ let NodeFront = protocol.FrontClass(NodeActor, {
|
||||
protocol.Front.prototype.initialize.call(this, conn, form, detail, ctx);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy a node front. The node must have been removed from the
|
||||
* ownership tree before this is called, unless the whole walker front
|
||||
* is being destroyed.
|
||||
*/
|
||||
destroy: function() {
|
||||
// If an observer was added on this node, shut it down.
|
||||
if (this.observer) {
|
||||
@ -216,12 +221,6 @@ let NodeFront = protocol.FrontClass(NodeActor, {
|
||||
this._observer = null;
|
||||
}
|
||||
|
||||
// Disconnect this item and from the ownership tree and destroy
|
||||
// all of its children.
|
||||
this.reparent(null);
|
||||
for (let child of this.treeChildren()) {
|
||||
child.destroy();
|
||||
}
|
||||
protocol.Front.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
@ -645,6 +644,11 @@ var WalkerActor = protocol.ActorClass({
|
||||
// this set.
|
||||
this._orphaned = new Set();
|
||||
|
||||
// The client can tell the walker that it is interested in a node
|
||||
// even when it is orphaned with the `retainNode` method. This
|
||||
// list contains orphaned nodes that were so retained.
|
||||
this._retainedOrphans = new Set();
|
||||
|
||||
this.onMutations = this.onMutations.bind(this);
|
||||
this.onFrameLoad = this.onFrameLoad.bind(this);
|
||||
this.onFrameUnload = this.onFrameUnload.bind(this);
|
||||
@ -790,24 +794,76 @@ var WalkerActor = protocol.ActorClass({
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Mark a node as 'retained'.
|
||||
*
|
||||
* A retained node is not released when `releaseNode` is called on its
|
||||
* parent, or when a parent is released with the `cleanup` option to
|
||||
* `getMutations`.
|
||||
*
|
||||
* When a retained node's parent is released, a retained mode is added to
|
||||
* the walker's "retained orphans" list.
|
||||
*
|
||||
* Retained nodes can be deleted by providing the `force` option to
|
||||
* `releaseNode`. They will also be released when their document
|
||||
* has been destroyed.
|
||||
*
|
||||
* Retaining a node makes no promise about its children; They can
|
||||
* still be removed by normal means.
|
||||
*/
|
||||
retainNode: method(function(node) {
|
||||
node.retained = true;
|
||||
}, {
|
||||
request: { node: Arg(0, "domnode") },
|
||||
response: {}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Remove the 'retained' mark from a node. If the node was a
|
||||
* retained orphan, release it.
|
||||
*/
|
||||
unretainNode: method(function(node) {
|
||||
node.retained = false;
|
||||
if (this._retainedOrphans.has(node)) {
|
||||
this._retainedOrphans.delete(node);
|
||||
this.releaseNode(node);
|
||||
}
|
||||
}, {
|
||||
request: { node: Arg(0, "domnode") },
|
||||
response: {},
|
||||
}),
|
||||
|
||||
/**
|
||||
* Release actors for a node and all child nodes.
|
||||
*/
|
||||
releaseNode: method(function(node) {
|
||||
releaseNode: method(function(node, options={}) {
|
||||
if (node.retained && !options.force) {
|
||||
this._retainedOrphans.add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.retained) {
|
||||
// Forcing a retained node to go away.
|
||||
this._retainedOrphans.delete(node);
|
||||
}
|
||||
|
||||
let walker = documentWalker(node.rawNode);
|
||||
|
||||
let child = walker.firstChild();
|
||||
while (child) {
|
||||
let childActor = this._refMap.get(child);
|
||||
if (childActor) {
|
||||
this.releaseNode(childActor);
|
||||
this.releaseNode(childActor, options);
|
||||
}
|
||||
child = walker.nextSibling();
|
||||
}
|
||||
|
||||
node.destroy();
|
||||
}, {
|
||||
request: { node: Arg(0, "domnode") }
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
force: Option(1)
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -1124,6 +1180,8 @@ var WalkerActor = protocol.ActorClass({
|
||||
|
||||
if (options.cleanup) {
|
||||
for (let node of this._orphaned) {
|
||||
// Release the orphaned node. Nodes or children that have been
|
||||
// retained will be moved to this._retainedOrphans.
|
||||
this.releaseNode(node);
|
||||
}
|
||||
this._orphaned = new Set();
|
||||
@ -1139,6 +1197,22 @@ var WalkerActor = protocol.ActorClass({
|
||||
}
|
||||
}),
|
||||
|
||||
queueMutation: function(mutation) {
|
||||
if (!this.actorID) {
|
||||
// We've been destroyed, don't bother queueing this mutation.
|
||||
return;
|
||||
}
|
||||
// We only send the `new-mutations` notification once, until the client
|
||||
// fetches mutations with the `getMutations` packet.
|
||||
let needEvent = this._pendingMutations.length === 0;
|
||||
|
||||
this._pendingMutations.push(mutation);
|
||||
|
||||
if (needEvent) {
|
||||
events.emit(this, "new-mutations");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles mutations from the DOM mutation observer API.
|
||||
*
|
||||
@ -1146,10 +1220,6 @@ var WalkerActor = protocol.ActorClass({
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#MutationRecord
|
||||
*/
|
||||
onMutations: function(mutations) {
|
||||
// We only send the `new-mutations` notification once, until the client
|
||||
// fetches mutations with the `getMutations` packet.
|
||||
let needEvent = this._pendingMutations.length === 0;
|
||||
|
||||
for (let change of mutations) {
|
||||
let targetActor = this._refMap.get(change.target);
|
||||
if (!targetActor) {
|
||||
@ -1206,10 +1276,7 @@ var WalkerActor = protocol.ActorClass({
|
||||
mutation.removed = removedActors;
|
||||
mutation.added = addedActors;
|
||||
}
|
||||
this._pendingMutations.push(mutation);
|
||||
}
|
||||
if (needEvent) {
|
||||
events.emit(this, "new-mutations");
|
||||
this.queueMutation(mutation);
|
||||
}
|
||||
},
|
||||
|
||||
@ -1219,35 +1286,64 @@ var WalkerActor = protocol.ActorClass({
|
||||
if (!frameActor) {
|
||||
return;
|
||||
}
|
||||
let needEvent = this._pendingMutations.length === 0;
|
||||
this._pendingMutations.push({
|
||||
|
||||
this.queueMutation({
|
||||
type: "frameLoad",
|
||||
target: frameActor.actorID,
|
||||
added: [],
|
||||
removed: []
|
||||
});
|
||||
},
|
||||
|
||||
if (needEvent) {
|
||||
events.emit(this, "new-mutations");
|
||||
// Returns true if domNode is in window or a subframe.
|
||||
_childOfWindow: function(window, domNode) {
|
||||
let win = nodeDocument(domNode).defaultView;
|
||||
while (win) {
|
||||
if (win === window) {
|
||||
return true;
|
||||
}
|
||||
win = win.frameElement;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
onFrameUnload: function(window) {
|
||||
// Any retained orphans that belong to this document
|
||||
// or its children need to be released, and a mutation sent
|
||||
// to notify of that.
|
||||
let releasedOrphans = [];
|
||||
|
||||
for (let retained of this._retainedOrphans) {
|
||||
if (Cu.isDeadWrapper(retained.rawNode) ||
|
||||
this._childOfWindow(window, retained.rawNode)) {
|
||||
this._retainedOrphans.delete(retained);
|
||||
releasedOrphans.push(retained.actorID);
|
||||
this.releaseNode(retained, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (releasedOrphans.length > 0) {
|
||||
this.queueMutation({
|
||||
target: this.rootNode.actorID,
|
||||
type: "unretained",
|
||||
nodes: releasedOrphans
|
||||
});
|
||||
}
|
||||
|
||||
let doc = window.document;
|
||||
let documentActor = this._refMap.get(doc);
|
||||
if (!documentActor) {
|
||||
return;
|
||||
}
|
||||
|
||||
let needEvent = this._pendingMutations.length === 0;
|
||||
this._pendingMutations.push({
|
||||
this.queueMutation({
|
||||
type: "documentUnload",
|
||||
target: documentActor.actorID
|
||||
});
|
||||
this.releaseNode(documentActor);
|
||||
if (needEvent) {
|
||||
events.emit(this, "new-mutations");
|
||||
}
|
||||
|
||||
// Need to force a release of this node, because those nodes can't
|
||||
// be accessed anymore.
|
||||
this.releaseNode(documentActor, { force: true });
|
||||
}
|
||||
});
|
||||
|
||||
@ -1261,6 +1357,7 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
|
||||
initialize: function(client, form) {
|
||||
protocol.Front.prototype.initialize.call(this, client, form);
|
||||
this._orphaned = new Set();
|
||||
this._retainedOrphans = new Set();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
@ -1290,11 +1387,51 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
|
||||
return types.getType("domnode").read({ actor: id }, this, "standin");
|
||||
},
|
||||
|
||||
releaseNode: protocol.custom(function(node) {
|
||||
/**
|
||||
* See the documentation for WalkerActor.prototype.retainNode for
|
||||
* information on retained nodes.
|
||||
*
|
||||
* From the client's perspective, `retainNode` can fail if the node in
|
||||
* question is removed from the ownership tree before the `retainNode`
|
||||
* request reaches the server. This can only happen if the client has
|
||||
* asked the server to release nodes but hasn't gotten a response
|
||||
* yet: Either a `releaseNode` request or a `getMutations` with `cleanup`
|
||||
* set is outstanding.
|
||||
*
|
||||
* If either of those requests is outstanding AND releases the retained
|
||||
* node, this request will fail with noSuchActor, but the ownership tree
|
||||
* will stay in a consistent state.
|
||||
*
|
||||
* Because the protocol guarantees that requests will be processed and
|
||||
* responses received in the order they were sent, we get the right
|
||||
* semantics by setting our local retained flag on the node only AFTER
|
||||
* a SUCCESSFUL retainNode call.
|
||||
*/
|
||||
retainNode: protocol.custom(function(node) {
|
||||
return this._retainNode(node).then(() => {
|
||||
node.retained = true;
|
||||
});
|
||||
}, {
|
||||
impl: "_retainNode",
|
||||
}),
|
||||
|
||||
unretainNode: protocol.custom(function(node) {
|
||||
return this._unretainNode(node).then(() => {
|
||||
node.retained = false;
|
||||
if (this._retainedOrphans.has(node)) {
|
||||
this._retainedOrphans.delete(node);
|
||||
this._releaseFront(node);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
impl: "_unretainNode"
|
||||
}),
|
||||
|
||||
releaseNode: protocol.custom(function(node, options={}) {
|
||||
// NodeFront.destroy will destroy children in the ownership tree too,
|
||||
// mimicking what the server will do here.
|
||||
let actorID = node.actorID;
|
||||
node.destroy();
|
||||
this._releaseFront(node, !!options.force);
|
||||
return this._releaseNode({ actorID: actorID });
|
||||
}, {
|
||||
impl: "_releaseNode"
|
||||
@ -1308,6 +1445,28 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
|
||||
impl: "_querySelector"
|
||||
}),
|
||||
|
||||
_releaseFront: function(node, force) {
|
||||
if (node.retained && !force) {
|
||||
node.reparent(null);
|
||||
this._retainedOrphans.add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.retained) {
|
||||
// Forcing a removal.
|
||||
this._retainedOrphans.delete(node);
|
||||
}
|
||||
|
||||
// Release any children
|
||||
for (let child of node.treeChildren()) {
|
||||
this._releaseFront(child, force);
|
||||
}
|
||||
|
||||
// All children will have been removed from the node by this point.
|
||||
node.reparent(null);
|
||||
node.destroy();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get any unprocessed mutation records and process them.
|
||||
*/
|
||||
@ -1374,7 +1533,17 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
|
||||
// We try to give fronts instead of actorIDs, but these fronts need
|
||||
// to be destroyed now.
|
||||
emittedMutation.target = targetFront.actorID;
|
||||
targetFront.destroy();
|
||||
|
||||
// Release the document node and all of its children, even retained.
|
||||
this._releaseFront(targetFront, true);
|
||||
} else if (change.type === "unretained") {
|
||||
// Retained orphans were force-released without the intervention of
|
||||
// client (probably a navigated frame).
|
||||
for (let released of change.nodes) {
|
||||
let releasedFront = this.get(released);
|
||||
this._retainedOrphans.delete(released);
|
||||
this._releaseFront(releasedFront, true);
|
||||
}
|
||||
} else {
|
||||
targetFront.updateMutation(change);
|
||||
}
|
||||
@ -1384,7 +1553,8 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
|
||||
|
||||
if (options.cleanup) {
|
||||
for (let node of this._orphaned) {
|
||||
node.destroy();
|
||||
// This will move retained nodes to this._retainedOrphans.
|
||||
this._releaseFront(node);
|
||||
}
|
||||
this._orphaned = new Set();
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ MOCHITEST_CHROME_FILES = \
|
||||
test_inspector-mutations-frameload.html \
|
||||
test_inspector-mutations-value.html \
|
||||
test_inspector-release.html \
|
||||
test_inspector-retain.html \
|
||||
test_inspector-traversal.html \
|
||||
test_unsafeDereference.html \
|
||||
nonchrome_unsafeDereference.html \
|
||||
|
@ -110,7 +110,8 @@ function serverOwnershipTree(walker) {
|
||||
|
||||
return {
|
||||
root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc ),
|
||||
orphaned: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._orphaned)]
|
||||
orphaned: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._orphaned)],
|
||||
retained: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._retainedOrphans)]
|
||||
};
|
||||
}
|
||||
|
||||
@ -124,7 +125,8 @@ function clientOwnershipSubtree(node) {
|
||||
function clientOwnershipTree(walker) {
|
||||
return {
|
||||
root: clientOwnershipSubtree(walker.rootNode),
|
||||
orphaned: [clientOwnershipSubtree(o) for (o of walker._orphaned)]
|
||||
orphaned: [clientOwnershipSubtree(o) for (o of walker._orphaned)],
|
||||
retained: [clientOwnershipSubtree(o) for (o of walker._retainedOrphans)]
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,6 +163,23 @@ function checkMissing(client, actorID) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// Verify that an actorID is accessible both from the client library and the server.
|
||||
function checkAvailable(client, actorID) {
|
||||
let deferred = Promise.defer();
|
||||
let front = client.getActor(actorID);
|
||||
ok(front, "Front should be accessible from the client for actorID: " + actorID);
|
||||
|
||||
let deferred = Promise.defer();
|
||||
client.request({
|
||||
to: actorID,
|
||||
type: "garbageAvailableTest",
|
||||
}, response => {
|
||||
is(response.error, "unrecognizedPacketType", "node list actor should be contactable.");
|
||||
deferred.resolve(undefined);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function promiseDone(promise) {
|
||||
promise.then(null, err => {
|
||||
ok(false, "Promise failed: " + err);
|
||||
@ -171,6 +190,77 @@ function promiseDone(promise) {
|
||||
});
|
||||
}
|
||||
|
||||
// Mutation list testing
|
||||
|
||||
function isSrcChange(change) {
|
||||
return (change.type === "attributes" && change.attributeName === "src");
|
||||
}
|
||||
|
||||
function assertAndStrip(mutations, message, test) {
|
||||
let size = mutations.length;
|
||||
mutations = mutations.filter(test);
|
||||
ok((mutations.size != size), message);
|
||||
return mutations;
|
||||
}
|
||||
|
||||
function isSrcChange(change) {
|
||||
return change.type === "attributes" && change.attributeName === "src";
|
||||
}
|
||||
|
||||
function isUnload(change) {
|
||||
return change.type === "documentUnload";
|
||||
}
|
||||
|
||||
function isFrameLoad(change) {
|
||||
return change.type === "frameLoad";
|
||||
}
|
||||
|
||||
function isUnretained(change) {
|
||||
return change.type === "unretained";
|
||||
}
|
||||
|
||||
function isChildList(change) {
|
||||
return change.type === "childList";
|
||||
}
|
||||
|
||||
// Make sure an iframe's src attribute changed and then
|
||||
// strip that mutation out of the list.
|
||||
function assertSrcChange(mutations) {
|
||||
return assertAndStrip(mutations, "Should have had an iframe source change.", isSrcChange);
|
||||
}
|
||||
|
||||
// Make sure there's an unload in the mutation list and strip
|
||||
// that mutation out of the list
|
||||
function assertUnload(mutations) {
|
||||
return assertAndStrip(mutations, "Should have had a document unload change.", isUnload);
|
||||
}
|
||||
|
||||
// Make sure there's a frame load in the mutation list and strip
|
||||
// that mutation out of the list
|
||||
function assertFrameLoad(mutations) {
|
||||
return assertAndStrip(mutations, "Should have had a frame load change.", isFrameLoad);
|
||||
}
|
||||
|
||||
// Load mutations aren't predictable, so keep accumulating mutations until
|
||||
// the one we're looking for shows up.
|
||||
function waitForMutation(walker, test, mutations=[]) {
|
||||
let deferred = Promise.defer();
|
||||
for (let change of mutations) {
|
||||
if (test(change)) {
|
||||
deferred.resolve(mutations);
|
||||
}
|
||||
}
|
||||
|
||||
walker.once("mutations", newMutations => {
|
||||
waitForMutation(walker, test, mutations.concat(newMutations)).then(finalMutations => {
|
||||
deferred.resolve(finalMutations);
|
||||
})
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
|
||||
var _tests = [];
|
||||
function addTest(test) {
|
||||
_tests.push(test);
|
||||
|
@ -67,67 +67,6 @@ function loadChildSelector(selector) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function isSrcChange(change) {
|
||||
return (change.type === "attributes" && change.attributeName === "src");
|
||||
}
|
||||
|
||||
function assertAndStrip(mutations, message, test) {
|
||||
let size = mutations.length;
|
||||
mutations = mutations.filter(test);
|
||||
ok((mutations.size != size), message);
|
||||
return mutations;
|
||||
}
|
||||
|
||||
function isSrcChange(change) {
|
||||
return change.type === "attributes" && change.attributeName === "src";
|
||||
}
|
||||
|
||||
function isUnload(change) {
|
||||
return change.type === "documentUnload";
|
||||
}
|
||||
|
||||
function isFrameLoad(change) {
|
||||
return change.type === "frameLoad";
|
||||
}
|
||||
|
||||
// Make sure an iframe's src attribute changed and then
|
||||
// strip that mutation out of the list.
|
||||
function assertSrcChange(mutations) {
|
||||
return assertAndStrip(mutations, "Should have had an iframe source change.", isSrcChange);
|
||||
}
|
||||
|
||||
// Make sure there's an unload in the mutation list and strip
|
||||
// that mutation out of the list
|
||||
function assertUnload(mutations) {
|
||||
return assertAndStrip(mutations, "Should have had a document unload change.", isUnload);
|
||||
}
|
||||
|
||||
// Make sure there's a frame load in the mutation list and strip
|
||||
// that mutation out of the list
|
||||
function assertFrameLoad(mutations) {
|
||||
return assertAndStrip(mutations, "Should have had a frame load change.", isFrameLoad);
|
||||
}
|
||||
|
||||
// Load mutations aren't predictable, so keep accumulating mutations until
|
||||
// the one we're looking for shows up.
|
||||
function waitForMutation(walker, test, mutations=[]) {
|
||||
let deferred = Promise.defer();
|
||||
for (let change of mutations) {
|
||||
if (test(change)) {
|
||||
deferred.resolve(mutations);
|
||||
}
|
||||
}
|
||||
|
||||
walker.once("mutations", newMutations => {
|
||||
waitForMutation(walker, test, mutations.concat(newMutations)).then(finalMutations => {
|
||||
deferred.resolve(finalMutations);
|
||||
})
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getUnloadedDoc(mutations) {
|
||||
for (let change of mutations) {
|
||||
if (isUnload(change)) {
|
||||
|
@ -0,0 +1,183 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug </title>
|
||||
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
|
||||
const Promise = devtools.require("sdk/core/promise");
|
||||
const inspector = devtools.require("devtools/server/actors/inspector");
|
||||
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
var gWalker = null;
|
||||
var gClient = null;
|
||||
var gInspectee = null;
|
||||
|
||||
function assertOwnership() {
|
||||
return assertOwnershipTrees(gWalker);
|
||||
}
|
||||
|
||||
addTest(function setup() {
|
||||
let url = document.getElementById("inspectorContent").href;
|
||||
attachURL(url, function(err, client, tab, doc) {
|
||||
gInspectee = doc;
|
||||
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
|
||||
let inspector = InspectorFront(client, tab);
|
||||
promiseDone(inspector.getWalker().then(walker => {
|
||||
ok(walker, "getWalker() should return an actor.");
|
||||
gClient = client;
|
||||
gWalker = walker;
|
||||
}).then(runNextTest));
|
||||
});
|
||||
});
|
||||
|
||||
// Retain a node, and a second-order child (in another document, for kicks)
|
||||
// Release the parent of the top item, which should cause one retained orphan.
|
||||
|
||||
// Then unretain the top node, which should retain the orphan.
|
||||
|
||||
// Then change the source of the iframe, which should kill that orphan.
|
||||
|
||||
addTest(function testRetain() {
|
||||
let originalOwnershipSize = 0;
|
||||
let bodyFront = null;
|
||||
let frameFront = null;
|
||||
let childListFront = null;
|
||||
// Get the toplevel body element and retain it.
|
||||
promiseDone(gWalker.querySelector(gWalker.rootNode, "body").then(front => {
|
||||
bodyFront = front;
|
||||
return gWalker.retainNode(bodyFront);
|
||||
}).then(() => {
|
||||
// Get an element in the child frame and retain it.
|
||||
return gWalker.querySelector(gWalker.rootNode, "#childFrame");
|
||||
}).then(frame => {
|
||||
frameFront = frame;
|
||||
return gWalker.children(frame, { maxNodes: 1 }).then(children => {
|
||||
return children.nodes[0];
|
||||
});
|
||||
}).then(childDoc => {
|
||||
return gWalker.querySelector(childDoc, "#longlist");
|
||||
}).then(list => {
|
||||
childListFront = list;
|
||||
originalOwnershipSize = assertOwnership();
|
||||
// and rtain it.
|
||||
return gWalker.retainNode(childListFront);
|
||||
}).then(() => {
|
||||
// OK, try releasing the parent of the first retained.
|
||||
return gWalker.releaseNode(bodyFront.parentNode());
|
||||
}).then(() => {
|
||||
let size = assertOwnership();
|
||||
let clientTree = clientOwnershipTree(gWalker);
|
||||
|
||||
// That request should have freed the parent of the first retained
|
||||
// but moved the rest into the retained orphaned tree.
|
||||
is(ownershipTreeSize(clientTree.root) + ownershipTreeSize(clientTree.retained[0]) + 1,
|
||||
originalOwnershipSize,
|
||||
"Should have only lost one item overall.");
|
||||
is(gWalker._retainedOrphans.size, 1, "Should have retained one orphan");
|
||||
ok(gWalker._retainedOrphans.has(bodyFront), "Should have retained the expected node.");
|
||||
}).then(() => {
|
||||
// Unretain the body, which should promote the childListFront to a retained orphan.
|
||||
return gWalker.unretainNode(bodyFront);
|
||||
}).then(() => {
|
||||
assertOwnership();
|
||||
let clientTree = clientOwnershipTree(gWalker);
|
||||
|
||||
is(gWalker._retainedOrphans.size, 1, "Should still only have one retained orphan.");
|
||||
ok(!gWalker._retainedOrphans.has(bodyFront), "Should have dropped the body node.")
|
||||
ok(gWalker._retainedOrphans.has(childListFront), "Should have retained the child node.")
|
||||
}).then(() => {
|
||||
// Change the source of the iframe, which should kill the retained orphan.
|
||||
gInspectee.querySelector("#childFrame").src = "data:text/html,<html>new child</html>";
|
||||
return waitForMutation(gWalker, isUnretained);
|
||||
}).then(mutations => {
|
||||
assertOwnership();
|
||||
let clientTree = clientOwnershipTree(gWalker);
|
||||
is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
|
||||
|
||||
}).then(runNextTest));
|
||||
});
|
||||
|
||||
// Get a hold of a node, remove it from the doc and retain it at the same time.
|
||||
// We should always win that race (even though the mutation happens before the
|
||||
// retain request), because we haven't issued `getMutations` yet.
|
||||
addTest(function testWinRace() {
|
||||
let front = null;
|
||||
promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(node => {
|
||||
front = node;
|
||||
let contentNode = gInspectee.querySelector("#a");
|
||||
contentNode.parentNode.removeChild(contentNode);
|
||||
// Now wait for that mutation and retain response to come in.
|
||||
return Promise.all([
|
||||
gWalker.retainNode(front),
|
||||
waitForMutation(gWalker, isChildList)
|
||||
]);
|
||||
}).then(() => {
|
||||
assertOwnership();
|
||||
let clientTree = clientOwnershipTree(gWalker);
|
||||
is(gWalker._retainedOrphans.size, 1, "Should have a retained orphan.");
|
||||
ok(gWalker._retainedOrphans.has(front), "Should have retained our expected node.");
|
||||
return gWalker.unretainNode(front);
|
||||
}).then(() => {
|
||||
// Make sure we're clear for the next test.
|
||||
assertOwnership();
|
||||
let clientTree = clientOwnershipTree(gWalker);
|
||||
is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
|
||||
}).then(runNextTest));
|
||||
});
|
||||
|
||||
// Same as above, but issue the request right after the 'new-mutations' event, so that
|
||||
// we *lose* the race.
|
||||
addTest(function testLoseRace() {
|
||||
let front = null;
|
||||
promiseDone(gWalker.querySelector(gWalker.rootNode, "#z").then(node => {
|
||||
front = node;
|
||||
gInspectee.querySelector("#z").parentNode = null;
|
||||
let contentNode = gInspectee.querySelector("#a");
|
||||
contentNode.parentNode.removeChild(contentNode);
|
||||
return promiseOnce(gWalker, "new-mutations");
|
||||
}).then(() => {
|
||||
// Verify that we have an outstanding request (no good way to tell that it's a
|
||||
// getMutations request, but there's nothing else it would be).
|
||||
is(gWalker._requests.length, 1, "Should have an outstanding request.");
|
||||
return gWalker.retainNode(front)
|
||||
}).then(() => { ok(false, "Request should not have succeeded!"); },
|
||||
(err) => {
|
||||
ok(err, "noSuchActor", "Should have lost the race.");
|
||||
let clientTree = clientOwnershipTree(gWalker);
|
||||
is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
|
||||
// Don't re-throw the error.
|
||||
}).then(runNextTest));
|
||||
});
|
||||
|
||||
addTest(function cleanup() {
|
||||
delete gWalker;
|
||||
delete gClient;
|
||||
runNextTest();
|
||||
});
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user