Bug 1520782 - Convert inspector tests using chrome documents and using server references directly to browser mochitests. r=pbro

These tests were using chrome mochitest which forces the test page to be running in chrome and in parent process.
This doesn't reflect typical setup where the page runs unprivileged in content process.
Also, with the current bug, the pages running in system principal will be debugged with a special setup.
Actors will be run with modules loaded in a distinct loader in order to be executed
in a distinct compartment, distinct from the shared system principal compartment.
That a prerequisite for the Debugger API. It has to run in a distinct compartment than its debuggee.

Depends on D16825

Differential Revision: https://phabricator.services.mozilla.com/D16826

--HG--
rename : devtools/server/tests/mochitest/animation-data.html => devtools/server/tests/browser/animation-data.html
rename : devtools/server/tests/mochitest/test_inspector-mutations-childlist.html => devtools/server/tests/browser/browser_inspector-mutations-childlist.js
rename : devtools/server/tests/mochitest/inspector-helpers.js => devtools/server/tests/browser/inspector-helpers.js
extra : moz-landing-system : lando
This commit is contained in:
Alexandre Poirot 2019-01-23 08:53:03 +00:00
parent a95993b1d7
commit 189029e823
26 changed files with 1719 additions and 1919 deletions

View File

@ -100,13 +100,6 @@
}
}
</style>
<script type="text/javascript">
"use strict";
window.onload = function() {
window.opener.postMessage("ready", "*");
};
</script>
</head>
</body>
<div class="ball still"></div>

View File

@ -4,6 +4,7 @@ subsuite = devtools
support-files =
head.js
animation.html
animation-data.html
doc_accessibility_infobar.html
doc_accessibility.html
doc_allocations.html
@ -17,6 +18,8 @@ support-files =
error-actor.js
grid.html
inspectedwindow-reload-target.sjs
inspector-search-data.html
inspector-traversal-data.html
inspector-shadow.html
navigate-first.html
navigate-second.html
@ -28,6 +31,7 @@ support-files =
storage-secured-iframe.html
stylesheets-nested-iframes.html
test-spawn-actor-in-parent.js
inspector-helpers.js
storage-helpers.js
!/devtools/client/shared/test/shared-head.js
!/devtools/client/shared/test/telemetry-test-helpers.js
@ -40,6 +44,7 @@ support-files =
[browser_accessibility_simple.js]
[browser_accessibility_walker.js]
[browser_actor_error.js]
[browser_animation_actor-lifetime.js]
[browser_animation_emitMutations.js]
[browser_animation_getProperties.js]
[browser_animation_getMultipleStates.js]
@ -67,7 +72,16 @@ skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still di
skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
[browser_canvasframe_helper_06.js]
skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
[browser_inspector-anonymous.js]
[browser_inspector-insert.js]
[browser_inspector-mutations-childlist.js]
[browser_inspector-mutations-frameload.js]
[browser_inspector-release.js]
[browser_inspector-remove.js]
[browser_inspector-retain.js]
[browser_inspector-search.js]
[browser_inspector-shadow.js]
[browser_inspector-traversal.js]
[browser_layout_getGrids.js]
[browser_layout_simple.js]
[browser_markers-cycle-collection.js]

View File

@ -0,0 +1,65 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for Bug 1247243
add_task(async function setup() {
info("Setting up inspector and animation actors.");
const { animations, walker } =
await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation-data.html");
info("Testing animated node actor");
const animatedNodeActor = await walker.querySelector(walker.rootNode,
".animated");
await animations.getAnimationPlayersForNode(animatedNodeActor);
await assertNumberOfAnimationActors(1, "AnimationActor have 1 AnimationPlayerActors");
info("Testing AnimationPlayerActors release");
const stillNodeActor = await walker.querySelector(walker.rootNode,
".still");
await animations.getAnimationPlayersForNode(stillNodeActor);
await assertNumberOfAnimationActors(0,
"AnimationActor does not have any AnimationPlayerActors anymore");
info("Testing multi animated node actor");
const multiNodeActor = await walker.querySelector(walker.rootNode,
".multi");
await animations.getAnimationPlayersForNode(multiNodeActor);
await assertNumberOfAnimationActors(2,
"AnimationActor has now 2 AnimationPlayerActors");
info("Testing single animated node actor");
await animations.getAnimationPlayersForNode(animatedNodeActor);
await assertNumberOfAnimationActors(1,
"AnimationActor has only one AnimationPlayerActors");
info("Testing AnimationPlayerActors release again");
await animations.getAnimationPlayersForNode(stillNodeActor);
await assertNumberOfAnimationActors(0,
"AnimationActor does not have any AnimationPlayerActors anymore");
async function assertNumberOfAnimationActors(expected, message) {
const actors = await ContentTask.spawn(
gBrowser.selectedBrowser,
[animations.actorID],
function(actorID) {
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const { DebuggerServer } = require("devtools/server/main");
// Convert actorID to current compartment string otherwise
// searchAllConnectionsForActor is confused and won't find the actor.
actorID = String(actorID);
const animationActors = DebuggerServer
.searchAllConnectionsForActor(actorID);
if (!animationActors) {
return 0;
}
return animationActors.actors.length;
}
);
is(actors, expected, message);
}
});

View File

@ -0,0 +1,174 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for Bug 777674
add_task(async function() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
await testXBLAnonymousInHTMLDocument(walker);
await testNativeAnonymous(walker);
await testNativeAnonymousStartingNode(walker);
await testPseudoElements(walker);
await testEmptyWithPseudo(walker);
await testShadowAnonymous(walker);
});
async function testXBLAnonymousInHTMLDocument(walker) {
info("Testing XBL anonymous in an HTML document.");
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const rawToolbarbutton = content.document.createElementNS(XUL_NS, "toolbarbutton");
content.document.documentElement.appendChild(rawToolbarbutton);
});
const toolbarbutton = await walker.querySelector(walker.rootNode, "toolbarbutton");
const children = await walker.children(toolbarbutton);
is(toolbarbutton.numChildren, 0, "XBL content is not visible in HTML doc");
is(children.nodes.length, 0, "XBL content is not returned in HTML doc");
}
async function testNativeAnonymous(walker) {
info("Testing native anonymous content with walker.");
const select = await walker.querySelector(walker.rootNode, "select");
const children = await walker.children(select);
is(select.numChildren, 2, "No native anon content for form control");
is(children.nodes.length, 2, "No native anon content for form control");
}
async function testNativeAnonymousStartingNode(walker) {
info("Tests attaching an element that a walker can't see.");
await ContentTask.spawn(gBrowser.selectedBrowser, [walker.actorID],
async function(actorID) {
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const { DebuggerServer } = require("devtools/server/main");
const {DocumentWalker} =
require("devtools/server/actors/inspector/document-walker");
const nodeFilterConstants =
require("devtools/shared/dom-node-filter-constants");
const docwalker = new DocumentWalker(
content.document.querySelector("select"),
content,
{
whatToShow: nodeFilterConstants.SHOW_ALL,
filter: () => {
return nodeFilterConstants.FILTER_ACCEPT;
},
}
);
const scrollbar = docwalker.lastChild();
is(scrollbar.tagName, "scrollbar", "An anonymous child has been fetched");
// Convert actorID to current compartment string otherwise
// searchAllConnectionsForActor is confused and won't find the actor.
actorID = String(actorID);
const serverWalker = DebuggerServer.searchAllConnectionsForActor(actorID);
const node = await serverWalker.attachElement(scrollbar);
ok(node, "A response has arrived");
ok(node.node, "A node is in the response");
is(node.node.rawNode.tagName, "SELECT",
"The node has changed to a parent that the walker recognizes");
});
}
async function testPseudoElements(walker) {
info("Testing pseudo elements with walker.");
// Markup looks like: <div><::before /><span /><::after /></div>
const pseudo = await walker.querySelector(walker.rootNode, "#pseudo");
const children = await walker.children(pseudo);
is(pseudo.numChildren, 1, "::before/::after are not counted if there is a child");
is(children.nodes.length, 3, "Correct number of children");
const before = children.nodes[0];
ok(before.isAnonymous, "Child is anonymous");
ok(!before._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
ok(before._form.isNativeAnonymous, "Child is native anonymous");
const span = children.nodes[1];
ok(!span.isAnonymous, "Child is not anonymous");
const after = children.nodes[2];
ok(after.isAnonymous, "Child is anonymous");
ok(!after._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(!after._form.isShadowAnonymous, "Child is not shadow anonymous");
ok(after._form.isNativeAnonymous, "Child is native anonymous");
}
async function testEmptyWithPseudo(walker) {
info("Testing elements with no childrent, except for pseudos.");
info("Checking an element whose only child is a pseudo element");
const pseudo = await walker.querySelector(walker.rootNode, "#pseudo-empty");
const children = await walker.children(pseudo);
is(pseudo.numChildren, 1,
"::before/::after are is counted if there are no other children");
is(children.nodes.length, 1, "Correct number of children");
const before = children.nodes[0];
ok(before.isAnonymous, "Child is anonymous");
ok(!before._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
ok(before._form.isNativeAnonymous, "Child is native anonymous");
}
async function testShadowAnonymous(walker) {
if (true) {
// FIXME(bug 1465114)
return;
}
info("Testing shadow DOM content.");
const shadow = await walker.querySelector(walker.rootNode, "#shadow");
const children = await walker.children(shadow);
is(shadow.numChildren, 3, "Children of the shadow root are counted");
is(children.nodes.length, 3, "Children returned from walker");
const before = children.nodes[0];
ok(before.isAnonymous, "Child is anonymous");
ok(!before._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
ok(before._form.isNativeAnonymous, "Child is native anonymous");
// <h3>Shadow <em>DOM</em></h3>
const shadowChild1 = children.nodes[1];
ok(shadowChild1.isAnonymous, "Child is anonymous");
ok(!shadowChild1._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(shadowChild1._form.isShadowAnonymous, "Child is shadow anonymous");
ok(!shadowChild1._form.isNativeAnonymous, "Child is not native anonymous");
const shadowSubChildren = await walker.children(children.nodes[1]);
is(shadowChild1.numChildren, 2, "Subchildren of the shadow root are counted");
is(shadowSubChildren.nodes.length, 2, "Subchildren are returned from walker");
// <em>DOM</em>
const shadowSubChild = children.nodes[1];
ok(shadowSubChild.isAnonymous, "Child is anonymous");
ok(!shadowSubChild._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(shadowSubChild._form.isShadowAnonymous, "Child is shadow anonymous");
ok(!shadowSubChild._form.isNativeAnonymous, "Child is not native anonymous");
// <select multiple></select>
const shadowChild2 = children.nodes[2];
ok(shadowChild2.isAnonymous, "Child is anonymous");
ok(!shadowChild2._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(shadowChild2._form.isShadowAnonymous, "Child is shadow anonymous");
ok(!shadowChild2._form.isNativeAnonymous, "Child is not native anonymous");
}

View File

@ -0,0 +1,119 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function setup() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
await testRearrange(walker);
await testInsertInvalidInput(walker);
});
async function testRearrange(walker) {
const longlist = await walker.querySelector(walker.rootNode, "#longlist");
let children = await walker.children(longlist);
const nodeA = children.nodes[0];
is(nodeA.id, "a", "Got the expected node.");
// Move nodeA to the end of the list.
await walker.insertBefore(nodeA, longlist, null);
await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
ok(!content.document.querySelector("#a").nextSibling,
"a should now be at the end of the list.");
});
children = await walker.children(longlist);
is(nodeA, children.nodes[children.nodes.length - 1],
"a should now be the last returned child.");
// Now move it to the middle of the list.
const nextNode = children.nodes[13];
await walker.insertBefore(nodeA, longlist, nextNode);
await ContentTask.spawn(gBrowser.selectedBrowser, [nextNode.actorID],
async function(actorID) {
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const { DebuggerServer } = require("devtools/server/main");
const {DocumentWalker} = require("devtools/server/actors/inspector/document-walker");
const sibling =
new DocumentWalker(content.document.querySelector("#a"), content).nextSibling();
// Convert actorID to current compartment string otherwise
// searchAllConnectionsForActor is confused and won't find the actor.
actorID = String(actorID);
const nodeActor = DebuggerServer.searchAllConnectionsForActor(actorID);
is(sibling, nodeActor.rawNode, "Node should match the expected next node.");
});
children = await walker.children(longlist);
is(nodeA, children.nodes[13], "a should be where we expect it.");
is(nextNode, children.nodes[14], "next node should be where we expect it.");
}
async function testInsertInvalidInput(walker) {
const longlist = await walker.querySelector(walker.rootNode, "#longlist");
const children = await walker.children(longlist);
const nodeA = children.nodes[0];
const nextSibling = children.nodes[1];
// Now move it to the original location and make sure no mutation happens.
await ContentTask.spawn(gBrowser.selectedBrowser, [longlist.actorID],
async function(actorID) {
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const { DebuggerServer } = require("devtools/server/main");
// Convert actorID to current compartment string otherwise
// searchAllConnectionsForActor is confused and won't find the actor.
actorID = String(actorID);
const nodeActor = DebuggerServer.searchAllConnectionsForActor(actorID);
content.hasMutated = false;
content.observer = new content.MutationObserver(() => {
content.hasMutated = true;
});
content.observer.observe(nodeActor.rawNode, {
childList: true,
});
});
await walker.insertBefore(nodeA, longlist, nodeA);
let hasMutated = await ContentTask.spawn(gBrowser.selectedBrowser, null,
async function() {
const state = content.hasMutated;
content.hasMutated = false;
return state;
});
ok(!hasMutated, "hasn't mutated");
await walker.insertBefore(nodeA, longlist, nextSibling);
hasMutated = await ContentTask.spawn(gBrowser.selectedBrowser, null,
async function() {
const state = content.hasMutated;
content.hasMutated = false;
return state;
});
ok(!hasMutated, "still hasn't mutated after inserting before nextSibling");
await walker.insertBefore(nodeA, longlist);
hasMutated = await ContentTask.spawn(gBrowser.selectedBrowser, null,
async function() {
const state = content.hasMutated;
content.hasMutated = false;
return state;
});
ok(hasMutated, "has mutated after inserting with null sibling");
await walker.insertBefore(nodeA, longlist);
hasMutated = await ContentTask.spawn(gBrowser.selectedBrowser, null,
async function() {
const state = content.hasMutated;
content.hasMutated = false;
return state;
});
ok(!hasMutated, "hasn't mutated after inserting with null sibling again");
await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
content.observer.disconnect();
});
}

View File

@ -1,68 +1,37 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=
-->
<head>
<meta charset="utf-8">
<title>Test for Bug </title>
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
<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" src="inspector-helpers.js"></script>
<script type="application/javascript">
"use strict";
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
};
/* import-globals-from inspector-helpers.js */
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
let gInspectee = null;
let gWalker = null;
async function setup(callback) {
const url = document.getElementById("inspectorContent").href;
const { target, doc } = await attachURL(url);
gInspectee = doc;
const inspector = await target.getInspector();
gWalker = inspector.walker;
callback();
}
function teardown() {
gWalker = null;
gInspectee = null;
}
function assertOwnership() {
return assertOwnershipTrees(gWalker);
}
function setParent(nodeSelector, newParentSelector) {
const node = gInspectee.querySelector(nodeSelector);
if (newParentSelector) {
const newParent = gInspectee.querySelector(newParentSelector);
newParent.appendChild(node);
} else {
node.remove();
}
}
function loadSelector(selector) {
return gWalker.querySelectorAll(gWalker.rootNode, selector).then(nodeList => {
function loadSelector(walker, selector) {
return walker.querySelectorAll(walker.rootNode, selector).then(nodeList => {
return nodeList.items();
});
}
function loadSelectors(selectors) {
return Promise.all(Array.from(selectors, (sel) => loadSelector(sel)));
function loadSelectors(walker, selectors) {
return Promise.all(Array.from(selectors, (sel) => loadSelector(walker, sel)));
}
function doMoves(moves) {
for (const move of moves) {
setParent(move[0], move[1]);
}
function doMoves(movesArg) {
return ContentTask.spawn(gBrowser.selectedBrowser, movesArg, function(moves) {
function setParent(nodeSelector, newParentSelector) {
const node = content.document.querySelector(nodeSelector);
if (newParentSelector) {
const newParent = content.document.querySelector(newParentSelector);
newParent.appendChild(node);
} else {
node.remove();
}
}
for (const move of moves) {
setParent(move[0], move[1]);
}
});
}
/**
@ -72,59 +41,60 @@ function doMoves(moves) {
var gDummySerial = 0;
function mutationTest(testSpec) {
return function() {
setup(() => {
promiseDone(loadSelectors(testSpec.load || ["html"]).then(() => {
gWalker.autoCleanup = !!testSpec.autoCleanup;
if (testSpec.preCheck) {
testSpec.preCheck();
}
doMoves(testSpec.moves || []);
return async function() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
await loadSelectors(walker, testSpec.load || ["html"]);
walker.autoCleanup = !!testSpec.autoCleanup;
if (testSpec.preCheck) {
testSpec.preCheck();
}
const onMutations = walker.once("mutations");
// Some of these moves will trigger no mutation events,
// so do a dummy change to the root node to trigger
// a mutation event anyway.
gInspectee.documentElement.setAttribute("data-dummy", gDummySerial++);
await doMoves(testSpec.moves || []);
gWalker.once("mutations", (mutations) => {
// Filter out our dummy mutation.
// eslint-disable-next-line max-nested-callbacks
mutations = mutations.filter(change => {
if (change.type == "attributes" &&
change.attributeName == "data-dummy") {
return false;
}
return true;
});
assertOwnership();
if (testSpec.postCheck) {
testSpec.postCheck(mutations);
}
teardown();
runNextTest();
});
}));
// Some of these moves will trigger no mutation events,
// so do a dummy change to the root node to trigger
// a mutation event anyway.
await ContentTask.spawn(gBrowser.selectedBrowser, [gDummySerial++],
function(serial) {
content.document.documentElement.setAttribute("data-dummy", serial);
});
let mutations = await onMutations;
// Filter out our dummy mutation.
mutations = mutations.filter(change => {
if (change.type == "attributes" &&
change.attributeName == "data-dummy") {
return false;
}
return true;
});
await assertOwnershipTrees(walker);
if (testSpec.postCheck) {
testSpec.postCheck(walker, mutations);
}
};
}
// Verify that our dummy mutation works.
addTest(mutationTest({
add_task(mutationTest({
autoCleanup: false,
postCheck: function(mutations) {
postCheck: function(walker, mutations) {
is(mutations.length, 0, "Dummy mutation is filtered out.");
},
}));
// Test a simple move to a different location in the sibling list for the same
// parent.
addTest(mutationTest({
add_task(mutationTest({
autoCleanup: false,
load: ["#longlist div"],
moves: [
["#a", "#longlist"],
],
postCheck: function(mutations) {
postCheck: function(walker, mutations) {
const remove = mutations[0];
is(remove.type, "childList", "First mutation should be a childList.");
ok(remove.removed.length > 0, "First mutation should be a removal.");
@ -140,13 +110,13 @@ addTest(mutationTest({
}));
// Test a move to another location that is within our ownership tree.
addTest(mutationTest({
add_task(mutationTest({
autoCleanup: false,
load: ["#longlist div", "#longlist-sibling"],
moves: [
["#a", "#longlist-sibling"],
],
postCheck: function(mutations) {
postCheck: function(walker, mutations) {
const remove = mutations[0];
is(remove.type, "childList", "First mutation should be a childList.");
ok(remove.removed.length > 0, "First mutation should be a removal.");
@ -162,13 +132,13 @@ addTest(mutationTest({
// Move an unseen node with a seen parent into our ownership tree - should generate a
// childList pair with no adds or removes.
addTest(mutationTest({
add_task(mutationTest({
autoCleanup: false,
load: ["#longlist"],
moves: [
["#longlist-sibling", "#longlist"],
],
postCheck: function(mutations) {
postCheck: function(walker, mutations) {
is(mutations.length, 2, "Should generate two mutations");
is(mutations[0].type, "childList", "Should be childList mutations.");
is(mutations[0].added.length, 0, "Should have no adds.");
@ -181,13 +151,13 @@ addTest(mutationTest({
// Move an unseen node with an unseen parent into our ownership tree. Should only
// generate one childList mutation with no adds or removes.
addTest(mutationTest({
add_task(mutationTest({
autoCleanup: false,
load: ["#longlist div"],
moves: [
["#longlist-sibling-firstchild", "#longlist"],
],
postCheck: function(mutations) {
postCheck: function(walker, mutations) {
is(mutations.length, 1, "Should generate two mutations");
is(mutations[0].type, "childList", "Should be childList mutations.");
is(mutations[0].added.length, 0, "Should have no adds.");
@ -196,30 +166,30 @@ addTest(mutationTest({
}));
// Move a node between unseen nodes, should generate no mutations.
addTest(mutationTest({
add_task(mutationTest({
autoCleanup: false,
load: ["html"],
moves: [
["#longlist-sibling", "#longlist"],
],
postCheck: function(mutations) {
postCheck: function(walker, mutations) {
is(mutations.length, 0, "Should generate no mutations.");
},
}));
// Orphan a node and don't clean it up
addTest(mutationTest({
add_task(mutationTest({
autoCleanup: false,
load: ["#longlist div"],
moves: [
["#longlist", null],
],
postCheck: function(mutations) {
postCheck: function(walker, mutations) {
is(mutations.length, 1, "Should generate one mutation.");
const change = mutations[0];
is(change.type, "childList", "Should be a childList.");
is(change.removed.length, 1, "Should have removed a child.");
const ownership = clientOwnershipTree(gWalker);
const ownership = clientOwnershipTree(walker);
is(ownership.orphaned.length, 1, "Should have one orphaned subtree.");
is(ownershipTreeSize(ownership.orphaned[0]), 1 + 26 + 26,
"Should have orphaned longlist, and 26 children, and 26 singleTextChilds");
@ -227,35 +197,35 @@ addTest(mutationTest({
}));
// Orphan a node, and do clean it up.
addTest(mutationTest({
add_task(mutationTest({
autoCleanup: true,
load: ["#longlist div"],
moves: [
["#longlist", null],
],
postCheck: function(mutations) {
postCheck: function(walker, mutations) {
is(mutations.length, 1, "Should generate one mutation.");
const change = mutations[0];
is(change.type, "childList", "Should be a childList.");
is(change.removed.length, 1, "Should have removed a child.");
const ownership = clientOwnershipTree(gWalker);
const ownership = clientOwnershipTree(walker);
is(ownership.orphaned.length, 0, "Should have no orphaned subtrees.");
},
}));
// Orphan a node by moving it into the tree but out of our visible subtree.
addTest(mutationTest({
add_task(mutationTest({
autoCleanup: false,
load: ["#longlist div"],
moves: [
["#longlist", "#longlist-sibling"],
],
postCheck: function(mutations) {
postCheck: function(walker, mutations) {
is(mutations.length, 1, "Should generate one mutation.");
const change = mutations[0];
is(change.type, "childList", "Should be a childList.");
is(change.removed.length, 1, "Should have removed a child.");
const ownership = clientOwnershipTree(gWalker);
const ownership = clientOwnershipTree(walker);
is(ownership.orphaned.length, 1, "Should have one orphaned subtree.");
is(ownershipTreeSize(ownership.orphaned[0]), 1 + 26 + 26,
"Should have orphaned longlist, 26 children, and 26 singleTextChilds.");
@ -264,37 +234,18 @@ addTest(mutationTest({
// Orphan a node by moving it into the tree but out of our visible subtree,
// and clean it up.
addTest(mutationTest({
add_task(mutationTest({
autoCleanup: true,
load: ["#longlist div"],
moves: [
["#longlist", "#longlist-sibling"],
],
postCheck: function(mutations) {
postCheck: function(walker, mutations) {
is(mutations.length, 1, "Should generate one mutation.");
const change = mutations[0];
is(change.type, "childList", "Should be a childList.");
is(change.removed.length, 1, "Should have removed a child.");
const ownership = clientOwnershipTree(gWalker);
const ownership = clientOwnershipTree(walker);
is(ownership.orphaned.length, 0, "Should have no orphaned subtrees.");
},
}));
addTest(function cleanup() {
gInspectee = null;
gWalker = null;
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>

View File

@ -0,0 +1,151 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* import-globals-from inspector-helpers.js */
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
async function loadChildSelector(walker, selector) {
const frame = await walker.querySelector(walker.rootNode, "#childFrame");
ok(frame.numChildren > 0,
"Child frame should consider its loaded document as a child.");
const children = await walker.children(frame);
const nodeList = await walker.querySelectorAll(children.nodes[0], selector);
return nodeList.items();
}
function getUnloadedDoc(mutations) {
for (const change of mutations) {
if (isUnload(change)) {
return change.target;
}
}
return null;
}
add_task(async function loadNewChild() {
const { target, walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
// Load a bunch of fronts for actors inside the child frame.
await loadChildSelector(walker, "#longlist div");
const onMutations = waitForMutation(walker, isChildList);
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
const childFrame = content.document.querySelector("#childFrame");
childFrame.src = "data:text/html,<html>new child</html>";
});
let mutations = await onMutations;
const unloaded = getUnloadedDoc(mutations);
mutations = assertSrcChange(mutations);
mutations = assertUnload(mutations);
mutations = assertFrameLoad(mutations);
mutations = assertChildList(mutations);
is(mutations.length, 0, "Got the expected mutations.");
assertOwnershipTrees(walker);
return checkMissing(target, unloaded);
});
add_task(async function loadNewChild() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
// Load a bunch of fronts for actors inside the child frame.
await loadChildSelector(walker, "#longlist div");
let onMutations = waitForMutation(walker, isChildList);
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
const childFrame = content.document.querySelector("#childFrame");
childFrame.src = "data:text/html,<html>new child</html>";
});
await onMutations;
onMutations = waitForMutation(walker, isChildList);
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
// The first load went through as expected (as tested in loadNewChild)
// Now change the source again, but this time we *don't* expect
// an unload, because we haven't seen the new child document yet.
const childFrame = content.document.querySelector("#childFrame");
childFrame.src = "data:text/html,<html>second new child</html>";
});
let mutations = await onMutations;
mutations = assertSrcChange(mutations);
mutations = assertFrameLoad(mutations);
mutations = assertChildList(mutations);
ok(!getUnloadedDoc(mutations), "Should not have gotten an unload.");
is(mutations.length, 0, "Got the expected mutations.");
assertOwnershipTrees(walker);
});
add_task(async function loadNewChildTwiceAndCareAboutIt() {
const { target, walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
// Load a bunch of fronts for actors inside the child frame.
await loadChildSelector(walker, "#longlist div");
let onMutations = waitForMutation(walker, isChildList);
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
const childFrame = content.document.querySelector("#childFrame");
childFrame.src = "data:text/html,<html>new child</html>";
});
await onMutations;
// Read the new child
await loadChildSelector(walker, "#longlist div");
onMutations = waitForMutation(walker, isChildList);
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
// Now change the source again, and expect the same results as loadNewChild.
const childFrame = content.document.querySelector("#childFrame");
childFrame.src = "data:text/html,<html>second new child</html>";
});
let mutations = await onMutations;
const unloaded = getUnloadedDoc(mutations);
mutations = assertSrcChange(mutations);
mutations = assertUnload(mutations);
mutations = assertFrameLoad(mutations);
mutations = assertChildList(mutations);
is(mutations.length, 0, "Got the expected mutations.");
assertOwnershipTrees(walker);
return checkMissing(target, unloaded);
});
add_task(async function testBack() {
const { target, walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
// Load a bunch of fronts for actors inside the child frame.
await loadChildSelector(walker, "#longlist div");
let onMutations = waitForMutation(walker, isChildList);
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
const childFrame = content.document.querySelector("#childFrame");
childFrame.src = "data:text/html,<html>new child</html>";
});
await onMutations;
// Read the new child
await loadChildSelector(walker, "#longlist div");
onMutations = waitForMutation(walker, isChildList);
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
// Now use history.back to change the source,
// and expect the same results as loadNewChild.
const childFrame = content.document.querySelector("#childFrame");
childFrame.contentWindow.history.back();
});
let mutations = await onMutations;
const unloaded = getUnloadedDoc(mutations);
mutations = assertSrcChange(mutations);
mutations = assertUnload(mutations);
mutations = assertFrameLoad(mutations);
mutations = assertChildList(mutations);
is(mutations.length, 0, "Got the expected mutations.");
assertOwnershipTrees(walker);
return checkMissing(target, unloaded);
});

View File

@ -0,0 +1,49 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* import-globals-from inspector-helpers.js */
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
add_task(async function loadNewChild() {
const { target, walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
let originalOwnershipSize = 0;
let longlist = null;
let firstChild = null;
const list = await walker.querySelectorAll(walker.rootNode, "#longlist div");
// Make sure we have the 26 children of longlist in our ownership tree.
is(list.length, 26, "Expect 26 div children.");
// Make sure we've read in all those children and incorporated them
// in our ownership tree.
const items = await list.items();
originalOwnershipSize = await assertOwnershipTrees(walker);
// Here is how the ownership tree is summed up:
// #document 1
// <html> 1
// <body> 1
// <div id=longlist> 1
// <div id=a>a</div> 26*2 (each child plus it's singleTextChild)
// ...
// <div id=z>z</div>
// -----
// 56
is(originalOwnershipSize, 56, "Correct number of items in ownership tree");
firstChild = items[0].actorID;
// Now get the longlist and release it from the ownership tree.
const node = await walker.querySelector(walker.rootNode, "#longlist");
longlist = node.actorID;
await walker.releaseNode(node);
// Our ownership size should now be 53 fewer
// (we forgot about #longlist + 26 children + 26 singleTextChild nodes)
const newOwnershipSize = await assertOwnershipTrees(walker);
is(newOwnershipSize, originalOwnershipSize - 53,
"Ownership tree should be lower");
// Now verify that some nodes have gone away
await checkMissing(target, longlist);
await checkMissing(target, firstChild);
});

View File

@ -0,0 +1,80 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* import-globals-from inspector-helpers.js */
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
add_task(async function testRemoveSubtree() {
const { target, walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
function ignoreNode(node) {
// Duplicate the walker logic to skip blank nodes...
return node.nodeType === Node.TEXT_NODE &&
!/[^\s]/.test(node.nodeValue);
}
let nextSibling = content.document.querySelector("#longlist").nextSibling;
while (nextSibling && ignoreNode(nextSibling)) {
nextSibling = nextSibling.nextSibling;
}
let previousSibling = content.document.querySelector("#longlist").previousSibling;
while (previousSibling && ignoreNode(previousSibling)) {
previousSibling = previousSibling.previousSibling;
}
content.nextSibling = nextSibling;
content.previousSibling = previousSibling;
});
let originalOwnershipSize = 0;
const longlist = await walker.querySelector(walker.rootNode, "#longlist");
const longlistID = longlist.actorID;
await walker.children(longlist);
originalOwnershipSize = await assertOwnershipTrees(walker);
// Here is how the ownership tree is summed up:
// #document 1
// <html> 1
// <body> 1
// <div id=longlist> 1
// <div id=a>a</div> 26*2 (each child plus it's singleTextChild)
// ...
// <div id=z>z</div>
// -----
// 56
is(originalOwnershipSize, 56, "Correct number of items in ownership tree");
const onMutation = waitForMutation(walker, isChildList);
const siblings = await walker.removeNode(longlist);
await ContentTask.spawn(gBrowser.selectedBrowser,
[siblings.previousSibling.actorID, siblings.nextSibling.actorID],
function([previousActorID, nextActorID]) {
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const { DebuggerServer } = require("devtools/server/main");
// Convert actorID to current compartment string otherwise
// searchAllConnectionsForActor is confused and won't find the actor.
previousActorID = String(previousActorID);
nextActorID = String(nextActorID);
const previous = DebuggerServer.searchAllConnectionsForActor(previousActorID);
const next = DebuggerServer.searchAllConnectionsForActor(nextActorID);
is(previous.rawNode, content.previousSibling,
"Should have returned the previous sibling.");
is(next.rawNode, content.nextSibling, "Should have returned the next sibling.");
});
await onMutation;
// Our ownership size should now be 51 fewer (we forgot about #longlist + 26
// children + 26 singleTextChild nodes, but learned about #longlist's
// prev/next sibling)
const newOwnershipSize = await assertOwnershipTrees(walker);
is(newOwnershipSize, originalOwnershipSize - 51,
"Ownership tree should be lower");
// Now verify that some nodes have gone away
return checkMissing(target, longlistID);
});

View File

@ -0,0 +1,125 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* import-globals-from inspector-helpers.js */
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
// 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.
add_task(async function testRetain() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
// Get the toplevel body element and retain it.
const bodyFront = await walker.querySelector(walker.rootNode, "body");
await walker.retainNode(bodyFront);
// Get an element in the child frame and retain it.
const frame = await walker.querySelector(walker.rootNode, "#childFrame");
const children = await walker.children(frame, { maxNodes: 1 });
const childDoc = children.nodes[0];
const childListFront = await walker.querySelector(childDoc, "#longlist");
const originalOwnershipSize = await assertOwnershipTrees(walker);
// and retain it.
await walker.retainNode(childListFront);
// OK, try releasing the parent of the first retained.
await walker.releaseNode(bodyFront.parentNode());
const clientTree = clientOwnershipTree(walker);
// 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(walker._retainedOrphans.size, 1, "Should have retained one orphan");
ok(walker._retainedOrphans.has(bodyFront),
"Should have retained the expected node.");
// Unretain the body, which should promote the childListFront to a retained orphan.
await walker.unretainNode(bodyFront);
await assertOwnershipTrees(walker);
is(walker._retainedOrphans.size, 1, "Should still only have one retained orphan.");
ok(!walker._retainedOrphans.has(bodyFront), "Should have dropped the body node.");
ok(walker._retainedOrphans.has(childListFront),
"Should have retained the child node.");
// Change the source of the iframe, which should kill the retained orphan.
const onMutations = waitForMutation(walker, isUnretained);
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
content.document.querySelector("#childFrame").src =
"data:text/html,<html>new child</html>";
});
await onMutations;
await assertOwnershipTrees(walker);
is(walker._retainedOrphans.size, 0, "Should have no more retained orphans.");
});
// 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.
add_task(async function testWinRace() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const front = await walker.querySelector(walker.rootNode, "#a");
const onMutation = waitForMutation(walker, isChildList);
ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
const contentNode = content.document.querySelector("#a");
contentNode.remove();
});
// Now wait for that mutation and retain response to come in.
await walker.retainNode(front);
await onMutation;
await assertOwnershipTrees(walker);
is(walker._retainedOrphans.size, 1, "Should have a retained orphan.");
ok(walker._retainedOrphans.has(front), "Should have retained our expected node.");
await walker.unretainNode(front);
// Make sure we're clear for the next test.
await assertOwnershipTrees(walker);
is(walker._retainedOrphans.size, 0, "Should have no more retained orphans.");
});
// Same as above, but issue the request right after the 'new-mutations' event, so that
// we *lose* the race.
add_task(async function testLoseRace() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const front = await walker.querySelector(walker.rootNode, "#z");
const onMutation = walker.once("new-mutations");
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
const contentNode = content.document.querySelector("#z");
contentNode.remove();
});
await onMutation;
// 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(walker._requests.length, 1, "Should have an outstanding request.");
try {
await walker.retainNode(front);
ok(false, "Request should not have succeeded!");
} catch (err) {
// XXX: Switched to from ok() to todo_is() in Bug 1467712. Follow up in
// 1500960
// This is throwing because of
// `gInspectee.querySelector("#z").parentNode = null;` two blocks above...
// Even if you fix that, the test is still failing because "#a" was removed
// by the previous test. I am switching this to "#z" because I think that
// was the original intent. Still not failing with the expected error message
// Needs more work.
// ok(err, "noSuchActor", "Should have lost the race.");
is(walker._retainedOrphans.size, 0, "Should have no more retained orphans.");
// Don't re-throw the error.
}
});

View File

@ -0,0 +1,242 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
// Test for Bug 835896
// WalkerSearch specific tests. This is to make sure search results are
// coming back as expected.
// See also test_inspector-search-front.html.
add_task(async function() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-search-data.html");
await ContentTask.spawn(gBrowser.selectedBrowser,
[walker.actorID],
async function(actorID) {
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const { DebuggerServer } = require("devtools/server/main");
const {DocumentWalker: _documentWalker} = require("devtools/server/actors/inspector/document-walker");
// Convert actorID to current compartment string otherwise
// searchAllConnectionsForActor is confused and won't find the actor.
actorID = String(actorID);
const walkerActor = DebuggerServer.searchAllConnectionsForActor(actorID);
const walkerSearch = walkerActor.walkerSearch;
const {WalkerSearch, WalkerIndex} =
require("devtools/server/actors/utils/walker-search");
info("Testing basic index APIs exist.");
const index = new WalkerIndex(walkerActor);
ok(index.data.size > 0, "public index is filled after getting");
index.clearIndex();
ok(!index._data, "private index is empty after clearing");
ok(index.data.size > 0, "public index is filled after getting");
index.destroy();
info("Testing basic search APIs exist.");
ok(walkerSearch, "walker search exists on the WalkerActor");
ok(walkerSearch.search, "walker search has `search` method");
ok(walkerSearch.index, "walker search has `index` property");
is(walkerSearch.walker, walkerActor, "referencing the correct WalkerActor");
const walkerSearch2 = new WalkerSearch(walkerActor);
ok(walkerSearch2, "a new search instance can be created");
ok(walkerSearch2.search, "new search instance has `search` method");
ok(walkerSearch2.index, "new search instance has `index` property");
isnot(walkerSearch2, walkerSearch,
"new search instance differs from the WalkerActor's");
walkerSearch2.destroy();
info("Testing search with an empty query.");
let results = walkerSearch.search("");
is(results.length, 0, "No results when searching for ''");
results = walkerSearch.search(null);
is(results.length, 0, "No results when searching for null");
results = walkerSearch.search(undefined);
is(results.length, 0, "No results when searching for undefined");
results = walkerSearch.search(10);
is(results.length, 0, "No results when searching for 10");
const inspectee = content.document;
const testData = [
{
desc: "Search for tag with one result.",
search: "body",
expected: [
{node: inspectee.body, type: "tag"},
],
},
{
desc: "Search for tag with multiple results",
search: "h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "tag"},
{node: inspectee.querySelectorAll("h2")[1], type: "tag"},
{node: inspectee.querySelectorAll("h2")[2], type: "tag"},
],
},
{
desc: "Search for selector with multiple results",
search: "body > h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "selector"},
{node: inspectee.querySelectorAll("h2")[1], type: "selector"},
{node: inspectee.querySelectorAll("h2")[2], type: "selector"},
],
},
{
desc: "Search for selector with multiple results",
search: ":root h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "selector"},
{node: inspectee.querySelectorAll("h2")[1], type: "selector"},
{node: inspectee.querySelectorAll("h2")[2], type: "selector"},
],
},
{
desc: "Search for selector with multiple results",
search: "* h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "selector"},
{node: inspectee.querySelectorAll("h2")[1], type: "selector"},
{node: inspectee.querySelectorAll("h2")[2], type: "selector"},
],
},
{
desc: "Search with multiple matches in a single tag expecting a single result",
search: "💩",
expected: [
{node: inspectee.getElementById("💩"), type: "attributeValue"},
],
},
{
desc: "Search that has tag and text results",
search: "h1",
expected: [
{node: inspectee.querySelector("h1"), type: "tag"},
{node: inspectee.querySelector("h1 + p").childNodes[0], type: "text"},
{node: inspectee.querySelector("h1 + p > strong").childNodes[0], type: "text"},
],
},
];
const isDeeply = (a, b, msg) => {
return is(JSON.stringify(a), JSON.stringify(b), msg);
};
for (const {desc, search, expected} of testData) {
info("Running test: " + desc);
results = walkerSearch.search(search);
isDeeply(results, expected,
"Search returns correct results with '" + search + "'");
}
info("Testing ::before and ::after element matching");
const beforeElt = new _documentWalker(inspectee.querySelector("#pseudo"),
inspectee.defaultView).firstChild();
const afterElt = new _documentWalker(inspectee.querySelector("#pseudo"),
inspectee.defaultView).lastChild();
const styleText = inspectee.querySelector("style").childNodes[0];
// ::before
results = walkerSearch.search("::before");
isDeeply(results, [ {node: beforeElt, type: "tag"} ],
"Tag search works for pseudo element");
results = walkerSearch.search("_moz_generated_content_before");
is(results.length, 0, "No results for anon tag name");
results = walkerSearch.search("before element");
isDeeply(results, [
{node: styleText, type: "text"},
{node: beforeElt, type: "text"},
], "Text search works for pseudo element");
// ::after
results = walkerSearch.search("::after");
isDeeply(results, [ {node: afterElt, type: "tag"} ],
"Tag search works for pseudo element");
results = walkerSearch.search("_moz_generated_content_after");
is(results.length, 0, "No results for anon tag name");
results = walkerSearch.search("after element");
isDeeply(results, [
{node: styleText, type: "text"},
{node: afterElt, type: "text"},
], "Text search works for pseudo element");
info("Testing search before and after a mutation.");
const expected = [
{node: inspectee.querySelectorAll("h3")[0], type: "tag"},
{node: inspectee.querySelectorAll("h3")[1], type: "tag"},
{node: inspectee.querySelectorAll("h3")[2], type: "tag"},
];
results = walkerSearch.search("h3");
isDeeply(results, expected, "Search works with tag results");
function mutateDocumentAndWaitForMutation(mutationFn) {
// eslint-disable-next-line new-cap
return new Promise(resolve => {
info("Listening to markup mutation on the inspectee");
const observer = new inspectee.defaultView.MutationObserver(resolve);
observer.observe(inspectee, {childList: true, subtree: true});
mutationFn();
});
}
await mutateDocumentAndWaitForMutation(() => {
expected[0].node.remove();
});
results = walkerSearch.search("h3");
isDeeply(results, [
expected[1],
expected[2],
], "Results are updated after removal");
// eslint-disable-next-line new-cap
await new Promise(resolve => {
info("Waiting for a mutation to happen");
const observer = new inspectee.defaultView.MutationObserver(() => {
resolve();
});
observer.observe(inspectee, {attributes: true, subtree: true});
inspectee.body.setAttribute("h3", "true");
});
results = walkerSearch.search("h3");
isDeeply(results, [
{node: inspectee.body, type: "attributeName"},
expected[1],
expected[2],
], "Results are updated after addition");
// eslint-disable-next-line new-cap
await new Promise(resolve => {
info("Waiting for a mutation to happen");
const observer = new inspectee.defaultView.MutationObserver(() => {
resolve();
});
observer.observe(inspectee, {attributes: true, childList: true, subtree: true});
inspectee.body.removeAttribute("h3");
expected[1].node.remove();
expected[2].node.remove();
});
results = walkerSearch.search("h3");
is(results.length, 0, "Results are updated after removal");
});
});

View File

@ -0,0 +1,274 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* import-globals-from inspector-helpers.js */
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
const checkActorIDs = [];
add_task(async function loadNewChild() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
// Make sure that refetching the root document of the walker returns the same
// actor as the getWalker returned.
const root = await walker.document();
ok(root === walker.rootNode,
"Re-fetching the document node should match the root document node.");
checkActorIDs.push(root.actorID);
await assertOwnershipTrees(walker);
});
add_task(async function testInnerHTML() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const docElement = await walker.documentElement();
const longstring = await walker.innerHTML(docElement);
const innerHTML = await longstring.string();
const actualInnerHTML = await ContentTask.spawn(gBrowser.selectedBrowser, null,
function() {
return content.document.documentElement.innerHTML;
});
ok(innerHTML === actualInnerHTML, "innerHTML should match");
});
add_task(async function testOuterHTML() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const docElement = await walker.documentElement();
const longstring = await walker.outerHTML(docElement);
const outerHTML = await longstring.string();
const actualOuterHTML = await ContentTask.spawn(gBrowser.selectedBrowser, null,
function() {
return content.document.documentElement.outerHTML;
});
ok(outerHTML === actualOuterHTML, "outerHTML should match");
});
add_task(async function testSetOuterHTMLNode() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const newHTML = "<p id=\"edit-html-done\">after edit</p>";
let node = await walker.querySelector(walker.rootNode, "#edit-html");
await walker.setOuterHTML(node, newHTML);
node = await walker.querySelector(walker.rootNode, "#edit-html-done");
const longstring = await walker.outerHTML(node);
const outerHTML = await longstring.string();
is(outerHTML, newHTML, "outerHTML has been updated");
node = await walker.querySelector(walker.rootNode, "#edit-html");
ok(!node, "The node with the old ID cannot be selected anymore");
});
add_task(async function testQuerySelector() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
let node = await walker.querySelector(walker.rootNode, "#longlist");
is(node.getAttribute("data-test"), "exists", "should have found the right node");
await assertOwnershipTrees(walker);
node = await walker.querySelector(walker.rootNode, "unknownqueryselector");
ok(!node, "Should not find a node here.");
await assertOwnershipTrees(walker);
});
add_task(async function testQuerySelectors() {
const { target, walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const nodeList = await walker.querySelectorAll(walker.rootNode, "#longlist div");
is(nodeList.length, 26, "Expect 26 div children.");
await assertOwnershipTrees(walker);
const firstNode = await nodeList.item(0);
checkActorIDs.push(firstNode.actorID);
is(firstNode.id, "a", "First child should be a");
await assertOwnershipTrees(walker);
let nodes = await nodeList.items();
is(nodes.length, 26, "Expect 26 nodes");
is(nodes[0], firstNode, "First node should be reused.");
ok(nodes[0]._parent, "Parent node should be set.");
ok(nodes[0]._next || nodes[0]._prev, "Siblings should be set.");
ok(nodes[25]._next || nodes[25]._prev,
"Siblings of " + nodes[25] + " should be set.");
await assertOwnershipTrees(walker);
nodes = await nodeList.items(-1);
is(nodes.length, 1, "Expect 1 node");
is(nodes[0].id, "z", "Expect it to be the last node.");
checkActorIDs.push(nodes[0].actorID);
// Save the node list ID so we can ensure it was destroyed.
const nodeListID = nodeList.actorID;
await assertOwnershipTrees(walker);
await nodeList.release();
ok(!nodeList.actorID, "Actor should have been destroyed.");
await assertOwnershipTrees(walker);
await checkMissing(target, nodeListID);
});
// Helper to check the response of requests that return hasFirst/hasLast/nodes
// node lists (like `children` and `siblings`)
async function checkArray(walker, children, first, last, ids) {
is(children.hasFirst, first,
"Should " + (first ? "" : "not ") + " have the first node.");
is(children.hasLast, last, "Should " + (last ? "" : "not ") + " have the last node.");
is(children.nodes.length, ids.length,
"Should have " + ids.length + " children listed.");
let responseIds = "";
for (const node of children.nodes) {
responseIds += node.id;
}
is(responseIds, ids, "Correct nodes were returned.");
await assertOwnershipTrees(walker);
}
add_task(async function testNoChildren() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const empty = await walker.querySelector(walker.rootNode, "#empty");
await assertOwnershipTrees(walker);
const children = await walker.children(empty);
await checkArray(walker, children, true, true, "");
});
add_task(async function testLongListTraversal() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const longList = await walker.querySelector(walker.rootNode, "#longlist");
// First call with no options, expect all children.
await assertOwnershipTrees(walker);
let children = await walker.children(longList);
await checkArray(walker, children, true, true, "abcdefghijklmnopqrstuvwxyz");
const allChildren = children.nodes;
await assertOwnershipTrees(walker);
// maxNodes should limit us to the first 5 nodes.
await assertOwnershipTrees(walker);
children = await walker.children(longList, { maxNodes: 5 });
await checkArray(walker, children, true, false, "abcde");
await assertOwnershipTrees(walker);
// maxNodes with the second item centered should still give us the first 5 nodes.
children = await walker.children(longList, { maxNodes: 5, center: allChildren[1] });
await checkArray(walker, children, true, false, "abcde");
// maxNodes with a center in the middle of the list should put that item in the middle
const center = allChildren[13];
is(center.id, "n", "Make sure I know how to count letters.");
children = await walker.children(longList, { maxNodes: 5, center: center });
await checkArray(walker, children, false, false, "lmnop");
// maxNodes with the second-to-last item centered should give us the last 5 nodes.
children = await walker.children(longList, { maxNodes: 5, center: allChildren[24] });
await checkArray(walker, children, false, true, "vwxyz");
// maxNodes with a start in the middle should start at that node and fetch 5
const start = allChildren[13];
is(start.id, "n", "Make sure I know how to count letters.");
children = await walker.children(longList, { maxNodes: 5, start: start });
await checkArray(walker, children, false, false, "nopqr");
// maxNodes near the end should only return what's left
children = await walker.children(longList, { maxNodes: 5, start: allChildren[24] });
await checkArray(walker, children, false, true, "yz");
});
add_task(async function testObjectNodeChildren() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const object = await walker.querySelector(walker.rootNode, "object");
const children = await walker.children(object);
await checkArray(walker, children, true, true, "1");
});
add_task(async function testNextSibling() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const y = await walker.querySelector(walker.rootNode, "#y");
is(y.id, "y", "Got the right node.");
const z = await walker.nextSibling(y);
is(z.id, "z", "nextSibling got the next node.");
const nothing = await walker.nextSibling(z);
is(nothing, null, "nextSibling on the last node returned null.");
});
add_task(async function testPreviousSibling() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const b = await walker.querySelector(walker.rootNode, "#b");
is(b.id, "b", "Got the right node.");
const a = await walker.previousSibling(b);
is(a.id, "a", "nextSibling got the next node.");
const nothing = await walker.previousSibling(a);
is(nothing, null, "previousSibling on the first node returned null.");
});
add_task(async function testFrameTraversal() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const childFrame = await walker.querySelector(walker.rootNode, "#childFrame");
const children = await walker.children(childFrame);
const nodes = children.nodes;
is(nodes.length, 1, "There should be only one child of the iframe");
is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
await walker.querySelector(nodes[0], "#z");
});
add_task(async function testLongValue() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
SimpleTest.registerCleanupFunction(async function() {
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const WalkerActor = require("devtools/server/actors/inspector/walker");
WalkerActor.setValueSummaryLength(WalkerActor.DEFAULT_VALUE_SUMMARY_LENGTH);
});
});
const longstringText = await ContentTask.spawn(gBrowser.selectedBrowser, null,
function() {
const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const testSummaryLength = 10;
const WalkerActor = require("devtools/server/actors/inspector/walker");
WalkerActor.setValueSummaryLength(testSummaryLength);
return content.document.getElementById("longstring").firstChild.nodeValue;
});
const node = await walker.querySelector(walker.rootNode, "#longstring");
ok(!node.inlineTextChild, "Text is too long to be inlined");
// Now we need to get the text node child...
const children = await walker.children(node, { maxNodes: 1 });
const textNode = children.nodes[0];
is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
const value = await textNode.getNodeValue();
const valueStr = await value.string();
is(valueStr, longstringText,
"Full node value should match the string from the document.");
});
add_task(async function testShortValue() {
const { walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
const shortstringText = await ContentTask.spawn(gBrowser.selectedBrowser, null,
function() {
return content.document.getElementById("shortstring").firstChild.nodeValue;
});
const node = await walker.querySelector(walker.rootNode, "#shortstring");
ok(!!node.inlineTextChild, "Text is short enough to be inlined");
// Now we need to get the text node child...
const children = await walker.children(node, { maxNodes: 1 });
const textNode = children.nodes[0];
is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
const value = await textNode.getNodeValue();
const valueStr = await value.string();
is(valueStr, shortstringText,
"Full node value should match the string from the document.");
});
add_task(async function testReleaseWalker() {
const { target, walker } =
await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
checkActorIDs.push(walker.actorID);
await walker.release();
for (const id of checkActorIDs) {
await checkMissing(target, id);
}
});

View File

@ -0,0 +1,188 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* exported assertOwnershipTrees, checkMissing, waitForMutation,
isSrcChange, isUnretained, isNewRoot,
assertSrcChange, assertUnload, assertFrameLoad, assertChildList,
*/
function serverOwnershipTree(walkerArg) {
return ContentTask.spawn(gBrowser.selectedBrowser, [walkerArg.actorID],
function(actorID) {
const { require } =
ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const { DebuggerServer } = require("devtools/server/main");
const { DocumentWalker } =
require("devtools/server/actors/inspector/document-walker");
// Convert actorID to current compartment string otherwise
// searchAllConnectionsForActor is confused and won't find the actor.
actorID = String(actorID);
const serverWalker = DebuggerServer.searchAllConnectionsForActor(actorID);
function sortOwnershipChildrenContentScript(children) {
return children.sort((a, b) => a.name.localeCompare(b.name));
}
function serverOwnershipSubtree(walker, node) {
const actor = walker._refMap.get(node);
if (!actor) {
return undefined;
}
const children = [];
const docwalker = new DocumentWalker(node, content);
let child = docwalker.firstChild();
while (child) {
const item = serverOwnershipSubtree(walker, child);
if (item) {
children.push(item);
}
child = docwalker.nextSibling();
}
return {
name: actor.actorID,
children: sortOwnershipChildrenContentScript(children),
};
}
return {
root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc),
orphaned: [...serverWalker._orphaned]
.map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
retained: [...serverWalker._retainedOrphans]
.map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
};
});
}
function sortOwnershipChildren(children) {
return children.sort((a, b) => a.name.localeCompare(b.name));
}
function clientOwnershipSubtree(node) {
return {
name: node.actorID,
children: sortOwnershipChildren(node.treeChildren()
.map(child => clientOwnershipSubtree(child))),
};
}
function clientOwnershipTree(walker) {
return {
root: clientOwnershipSubtree(walker.rootNode),
orphaned: [...walker._orphaned].map(o => clientOwnershipSubtree(o)),
retained: [...walker._retainedOrphans].map(o => clientOwnershipSubtree(o)),
};
}
function ownershipTreeSize(tree) {
let size = 1;
for (const child of tree.children) {
size += ownershipTreeSize(child);
}
return size;
}
async function assertOwnershipTrees(walker) {
const serverTree = await serverOwnershipTree(walker);
const clientTree = clientOwnershipTree(walker);
is(JSON.stringify(clientTree, null, " "), JSON.stringify(serverTree, null, " "),
"Server and client ownership trees should match.");
return ownershipTreeSize(clientTree.root);
}
// Verify that an actorID is inaccessible both from the client library and the server.
function checkMissing({client}, actorID) {
return new Promise(resolve => {
const front = client.getActor(actorID);
ok(!front, "Front shouldn't be accessible from the client for actorID: " + actorID);
client.request({
to: actorID,
type: "request",
}, response => {
is(response.error, "noSuchActor",
"node list actor should no longer be contactable.");
resolve(undefined);
});
});
}
// Load mutations aren't predictable, so keep accumulating mutations until
// the one we're looking for shows up.
function waitForMutation(walker, test, mutations = []) {
return new Promise(resolve => {
for (const change of mutations) {
if (test(change)) {
resolve(mutations);
}
}
walker.once("mutations", newMutations => {
waitForMutation(walker, test, mutations.concat(newMutations))
.then(finalMutations => {
resolve(finalMutations);
});
});
});
}
function assertAndStrip(mutations, message, test) {
const 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";
}
function isNewRoot(change) {
return change.type === "newRoot";
}
// 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);
}
// Make sure there's a childList change in the mutation list and strip
// that mutation out of the list
function assertChildList(mutations) {
return assertAndStrip(mutations, "Should have had a frame load change.", isChildList);
}

View File

@ -0,0 +1,54 @@
<html>
<head>
<meta charset="UTF-8">
<title>Inspector Search Test Data</title>
<style>
#pseudo {
display: block;
margin: 0;
}
#pseudo:before {
content: "before element";
}
#pseudo:after {
content: "after element";
}
</style>
<script type="text/javascript">
"use strict";
window.onload = function() {
window.opener.postMessage("ready", "*");
};
</script>
</head>
</body>
<!-- A comment
spread across multiple lines -->
<img width="100" height="100" src="large-image.jpg" />
<h1 id="pseudo">Heading 1</h1>
<p>A p tag with the text 'h1' inside of it.
<strong>A strong h1 result</strong>
</p>
<div id="arrows" northwest="↖" northeast="↗" southeast="↘" southwest="↙">
Unicode arrows
</div>
<h2>Heading 2</h2>
<h2>Heading 2</h2>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h3>Heading 3</h3>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h4>Heading 4</h4>
<h4>Heading 4</h4>
<div class="💩" id="💩" 💩="💩"></div>
</body>
</html>

View File

@ -0,0 +1,100 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Inspector Traversal Test Data</title>
<style type="text/css">
#pseudo::before {
content: "before";
}
#pseudo::after {
content: "after";
}
#pseudo-empty::before {
content: "before an empty element";
}
#shadow::before {
content: "Testing ::before on a shadow host";
}
</style>
<script type="text/javascript">
"use strict";
window.onload = function() {
// Set up a basic shadow DOM
const host = document.querySelector("#shadow");
if (host.attachShadow) {
const root = host.attachShadow({ mode: "open" });
const h3 = document.createElement("h3");
h3.append("Shadow ");
const em = document.createElement("em");
em.append("DOM");
const select = document.createElement("select");
select.setAttribute("multiple", "");
h3.appendChild(em);
root.appendChild(h3);
root.appendChild(select);
}
// Put a copy of the body in an iframe to test frame traversal.
const body = document.querySelector("body");
const data = "data:text/html,<html>" + body.outerHTML + "<html>";
const iframe = document.createElement("iframe");
iframe.setAttribute("id", "childFrame");
iframe.src = data;
body.appendChild(iframe);
};
</script>
</head>
<body style="background-color:white">
<h1>Inspector Actor Tests</h1>
<span id="longstring">longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong</span>
<span id="shortstring">short</span>
<span id="empty"></span>
<div id="longlist" data-test="exists">
<div id="a">a</div>
<div id="b">b</div>
<div id="c">c</div>
<div id="d">d</div>
<div id="e">e</div>
<div id="f">f</div>
<div id="g">g</div>
<div id="h">h</div>
<div id="i">i</div>
<div id="j">j</div>
<div id="k">k</div>
<div id="l">l</div>
<div id="m">m</div>
<div id="n">n</div>
<div id="o">o</div>
<div id="p">p</div>
<div id="q">q</div>
<div id="r">r</div>
<div id="s">s</div>
<div id="t">t</div>
<div id="u">u</div>
<div id="v">v</div>
<div id="w">w</div>
<div id="x">x</div>
<div id="y">y</div>
<div id="z">z</div>
</div>
<div id="longlist-sibling">
<div id="longlist-sibling-firstchild"></div>
</div>
<p id="edit-html"></p>
<select multiple><option>one</option><option>two</option></select>
<div id="pseudo"><span>middle</span></div>
<div id="pseudo-empty"></div>
<div id="shadow">light dom</div>
<object>
<div id="1"></div>
</object>
<div class="node-to-duplicate"></div>
<div id="scroll-into-view" style="margin-top: 1000px;">scroll</div>
</body>
</html>

View File

@ -2,7 +2,6 @@
tags = devtools
skip-if = os == 'android'
support-files =
animation-data.html
doc_Debugger.Source.prototype.introductionType.xul
Debugger.Source.prototype.element.js
Debugger.Source.prototype.element-2.js
@ -35,7 +34,6 @@ support-files =
test_suspendTimeouts.js
webconsole-helpers.js
webextension-helpers.js
[test_animation_actor-lifetime.html]
[test_animation-type-longhand.html]
[test_connection-manager.html]
skip-if = (verify && debug && (os == 'win'))
@ -58,7 +56,6 @@ skip-if = (verify && debug && (os == 'win'))
[test_framerate_06.html]
[test_getProcess.html]
[test_highlighter_paused_debugger.html]
[test_inspector-anonymous.html]
[test_inspector-changeattrs.html]
[test_inspector-changevalue.html]
[test_inspector-dead-nodes.html]
@ -70,25 +67,17 @@ skip-if = (verify && debug && (os == 'win'))
[test_inspector_getNodeFromActor.html]
[test_inspector_getOffsetParent.html]
[test_inspector-hide.html]
[test_inspector-insert.html]
[test_inspector-mutations-attr.html]
[test_inspector-mutations-events.html]
[test_inspector-mutations-childlist.html]
[test_inspector-mutations-frameload.html]
[test_inspector-mutations-value.html]
[test_inspector-pick-color.html]
[test_inspector-pseudoclass-lock.html]
[test_inspector-release.html]
[test_inspector-reload.html]
[test_inspector-remove.html]
[test_inspector-resize.html]
[test_inspector-resolve-url.html]
[test_inspector-retain.html]
[test_inspector-search.html]
[test_inspector-search-front.html]
[test_inspector-scroll-into-view.html]
[test_inspector-template.html]
[test_inspector-traversal.html]
[test_makeGlobalObjectReference.html]
[test_memory.html]
[test_memory_allocations_02.html]

View File

@ -1,16 +1,16 @@
/* exported attachURL, promiseDone, assertOwnershipTrees, checkMissing, checkAvailable,
promiseOnce, isSrcChange, isUnretained, isNewRoot, assertSrcChange, assertUnload,
assertFrameLoad, assertChildList, waitForMutation, addTest, addAsyncTest,
runNextTest */
/* exported attachURL, promiseDone,
promiseOnce, isNewRoot,
waitForMutation, addTest, addAsyncTest,
runNextTest, _documentWalker */
"use strict";
const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const {TargetFactory} = require("devtools/client/framework/target");
const {DebuggerServer} = require("devtools/server/main");
const {BrowserTestUtils} = require("resource://testing-common/BrowserTestUtils.jsm");
const {DocumentWalker: _documentWalker} = require("devtools/server/actors/inspector/document-walker");
const Services = require("Services");
const {DocumentWalker: _documentWalker} = require("devtools/server/actors/inspector/document-walker");
// Always log packets when running tests.
Services.prefs.setBoolPref("devtools.debugger.log", true);
@ -92,111 +92,6 @@ function promiseOnce(target, event) {
});
}
function sortOwnershipChildren(children) {
return children.sort((a, b) => a.name.localeCompare(b.name));
}
function serverOwnershipSubtree(walker, node) {
const actor = walker._refMap.get(node);
if (!actor) {
return undefined;
}
const children = [];
const docwalker = new _documentWalker(node, window);
let child = docwalker.firstChild();
while (child) {
const item = serverOwnershipSubtree(walker, child);
if (item) {
children.push(item);
}
child = docwalker.nextSibling();
}
return {
name: actor.actorID,
children: sortOwnershipChildren(children),
};
}
function serverOwnershipTree(walker) {
const serverWalker = DebuggerServer.searchAllConnectionsForActor(walker.actorID);
return {
root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc),
orphaned: [...serverWalker._orphaned]
.map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
retained: [...serverWalker._retainedOrphans]
.map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
};
}
function clientOwnershipSubtree(node) {
return {
name: node.actorID,
children: sortOwnershipChildren(node.treeChildren()
.map(child => clientOwnershipSubtree(child))),
};
}
function clientOwnershipTree(walker) {
return {
root: clientOwnershipSubtree(walker.rootNode),
orphaned: [...walker._orphaned].map(o => clientOwnershipSubtree(o)),
retained: [...walker._retainedOrphans].map(o => clientOwnershipSubtree(o)),
};
}
function ownershipTreeSize(tree) {
let size = 1;
for (const child of tree.children) {
size += ownershipTreeSize(child);
}
return size;
}
function assertOwnershipTrees(walker) {
const serverTree = serverOwnershipTree(walker);
const clientTree = clientOwnershipTree(walker);
is(JSON.stringify(clientTree, null, " "), JSON.stringify(serverTree, null, " "),
"Server and client ownership trees should match.");
return ownershipTreeSize(clientTree.root);
}
// Verify that an actorID is inaccessible both from the client library and the server.
function checkMissing({client}, actorID) {
return new Promise(resolve => {
const front = client.getActor(actorID);
ok(!front, "Front shouldn't be accessible from the client for actorID: " + actorID);
client.request({
to: actorID,
type: "request",
}, response => {
is(response.error, "noSuchActor",
"node list actor should no longer be contactable.");
resolve(undefined);
});
});
}
// Verify that an actorID is accessible both from the client library and the server.
function checkAvailable({client}, actorID) {
return new Promise(resolve => {
const front = client.getActor(actorID);
ok(front, "Front should be accessible from the client for actorID: " + actorID);
client.request({
to: actorID,
type: "garbageAvailableTest",
}, response => {
is(response.error, "unrecognizedPacketType",
"node list actor should be contactable.");
resolve(undefined);
});
});
}
function promiseDone(currentPromise) {
currentPromise.catch(err => {
ok(false, "Promise failed: " + err);
@ -209,62 +104,10 @@ function promiseDone(currentPromise) {
// Mutation list testing
function assertAndStrip(mutations, message, test) {
const 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";
}
function isNewRoot(change) {
return change.type === "newRoot";
}
// 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);
}
// Make sure there's a childList change in the mutation list and strip
// that mutation out of the list
function assertChildList(mutations) {
return assertAndStrip(mutations, "Should have had a frame load change.", isChildList);
}
// Load mutations aren't predictable, so keep accumulating mutations until
// the one we're looking for shows up.
function waitForMutation(walker, test, mutations = []) {

View File

@ -1,81 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1247243
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1247243</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" src="inspector-helpers.js"></script>
<script type="application/javascript">
"use strict";
window.onload = function() {
SimpleTest.waitForExplicitFinish();
let gWalker = null;
let animationsFront = null;
addTest(async function setup() {
info("Setting up inspector and animation actors.");
const url = document.getElementById("animationContent").href;
const { target } = await attachURL(url);
const inspector = await target.getInspector();
animationsFront = await target.getFront("animations");
gWalker = inspector.walker;
runNextTest();
});
addAsyncTest(async function testActorLifetime() {
info("Testing animated node actor");
const animatedNodeActor = await gWalker.querySelector(gWalker.rootNode,
".animated");
await animationsFront.getAnimationPlayersForNode(animatedNodeActor);
const animationsActor = DebuggerServer
.searchAllConnectionsForActor(animationsFront.actorID);
is(animationsActor.actors.length, 1,
"AnimationActor have 1 AnimationPlayerActors");
info("Testing AnimationPlayerActors release");
const stillNodeActor = await gWalker.querySelector(gWalker.rootNode,
".still");
await animationsFront.getAnimationPlayersForNode(stillNodeActor);
is(animationsActor.actors.length, 0,
"AnimationActor does not have any AnimationPlayerActors anymore");
info("Testing multi animated node actor");
const multiNodeActor = await gWalker.querySelector(gWalker.rootNode,
".multi");
await animationsFront.getAnimationPlayersForNode(multiNodeActor);
is(animationsActor.actors.length, 2,
"AnimationActor has now 2 AnimationPlayerActors");
info("Testing single animated node actor");
await animationsFront.getAnimationPlayersForNode(animatedNodeActor);
is(animationsActor.actors.length, 1,
"AnimationActor has only one AnimationPlayerActors");
info("Testing AnimationPlayerActors release again");
await animationsFront.getAnimationPlayersForNode(stillNodeActor);
is(animationsActor.actors.length, 0,
"AnimationActor does not have any AnimationPlayerActors anymore");
runNextTest();
});
runNextTest();
};
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1247243">Mozilla Bug 1247243</a>
<a id="animationContent" target="_blank" href="animation-data.html">Test Document</a>
</body>
</html>

View File

@ -1,204 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=777674
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 777674</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" src="inspector-helpers.js"></script>
<script type="application/javascript">
"use strict";
window.onload = function() {
const {DocumentWalker: _documentWalker} =
require("devtools/server/actors/inspector/document-walker");
const nodeFilterConstants =
require("devtools/shared/dom-node-filter-constants");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
SimpleTest.waitForExplicitFinish();
let gWalker = null;
let gInspectee = null;
addTest(async function setup() {
info("Setting up inspector and walker actors.");
const url = document.getElementById("inspectorContent").href;
const { target, doc } = await attachURL(url);
gInspectee = doc;
const inspector = await target.getInspector();
gWalker = inspector.walker;
runNextTest();
});
addAsyncTest(async function testXBLAnonymousInHTMLDocument() {
info("Testing XBL anonymous in an HTML document.");
const rawToolbarbutton = gInspectee.createElementNS(XUL_NS, "toolbarbutton");
gInspectee.documentElement.appendChild(rawToolbarbutton);
const toolbarbutton = await gWalker.querySelector(gWalker.rootNode, "toolbarbutton");
const children = await gWalker.children(toolbarbutton);
is(toolbarbutton.numChildren, 0, "XBL content is not visible in HTML doc");
is(children.nodes.length, 0, "XBL content is not returned in HTML doc");
runNextTest();
});
addAsyncTest(async function testNativeAnonymous() {
info("Testing native anonymous content with walker.");
const select = await gWalker.querySelector(gWalker.rootNode, "select");
const children = await gWalker.children(select);
is(select.numChildren, 2, "No native anon content for form control");
is(children.nodes.length, 2, "No native anon content for form control");
runNextTest();
});
addAsyncTest(async function testNativeAnonymousStartingNode() {
info("Tests attaching an element that a walker can't see.");
const serverWalker = DebuggerServer.searchAllConnectionsForActor(gWalker.actorID);
const docwalker = new _documentWalker(
gInspectee.querySelector("select"),
gInspectee.defaultView,
{
whatToShow: nodeFilterConstants.SHOW_ALL,
filter: () => {
return nodeFilterConstants.FILTER_ACCEPT;
},
}
);
const scrollbar = docwalker.lastChild();
is(scrollbar.tagName, "scrollbar", "An anonymous child has been fetched");
const node = await serverWalker.attachElement(scrollbar);
ok(node, "A response has arrived");
ok(node.node, "A node is in the response");
is(node.node.rawNode.tagName, "SELECT",
"The node has changed to a parent that the walker recognizes");
runNextTest();
});
addAsyncTest(async function testPseudoElements() {
info("Testing pseudo elements with walker.");
// Markup looks like: <div><::before /><span /><::after /></div>
const pseudo = await gWalker.querySelector(gWalker.rootNode, "#pseudo");
const children = await gWalker.children(pseudo);
is(pseudo.numChildren, 1, "::before/::after are not counted if there is a child");
is(children.nodes.length, 3, "Correct number of children");
const before = children.nodes[0];
ok(before.isAnonymous, "Child is anonymous");
ok(!before._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
ok(before._form.isNativeAnonymous, "Child is native anonymous");
const span = children.nodes[1];
ok(!span.isAnonymous, "Child is not anonymous");
const after = children.nodes[2];
ok(after.isAnonymous, "Child is anonymous");
ok(!after._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(!after._form.isShadowAnonymous, "Child is not shadow anonymous");
ok(after._form.isNativeAnonymous, "Child is native anonymous");
runNextTest();
});
addAsyncTest(async function testEmptyWithPseudo() {
info("Testing elements with no childrent, except for pseudos.");
info("Checking an element whose only child is a pseudo element");
const pseudo = await gWalker.querySelector(gWalker.rootNode, "#pseudo-empty");
const children = await gWalker.children(pseudo);
is(pseudo.numChildren, 1,
"::before/::after are is counted if there are no other children");
is(children.nodes.length, 1, "Correct number of children");
const before = children.nodes[0];
ok(before.isAnonymous, "Child is anonymous");
ok(!before._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
ok(before._form.isNativeAnonymous, "Child is native anonymous");
runNextTest();
});
addAsyncTest(async function testShadowAnonymous() {
if (true) {
// FIXME(bug 1465114)
runNextTest();
return;
}
info("Testing shadow DOM content.");
const shadow = await gWalker.querySelector(gWalker.rootNode, "#shadow");
const children = await gWalker.children(shadow);
is(shadow.numChildren, 3, "Children of the shadow root are counted");
is(children.nodes.length, 3, "Children returned from walker");
const before = children.nodes[0];
ok(before.isAnonymous, "Child is anonymous");
ok(!before._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
ok(before._form.isNativeAnonymous, "Child is native anonymous");
// <h3>Shadow <em>DOM</em></h3>
const shadowChild1 = children.nodes[1];
ok(shadowChild1.isAnonymous, "Child is anonymous");
ok(!shadowChild1._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(shadowChild1._form.isShadowAnonymous, "Child is shadow anonymous");
ok(!shadowChild1._form.isNativeAnonymous, "Child is not native anonymous");
const shadowSubChildren = await gWalker.children(children.nodes[1]);
is(shadowChild1.numChildren, 2, "Subchildren of the shadow root are counted");
is(shadowSubChildren.nodes.length, 2, "Subchildren are returned from walker");
// <em>DOM</em>
const shadowSubChild = children.nodes[1];
ok(shadowSubChild.isAnonymous, "Child is anonymous");
ok(!shadowSubChild._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(shadowSubChild._form.isShadowAnonymous, "Child is shadow anonymous");
ok(!shadowSubChild._form.isNativeAnonymous, "Child is not native anonymous");
// <select multiple></select>
const shadowChild2 = children.nodes[2];
ok(shadowChild2.isAnonymous, "Child is anonymous");
ok(!shadowChild2._form.isXBLAnonymous, "Child is not XBL anonymous");
ok(shadowChild2._form.isShadowAnonymous, "Child is shadow anonymous");
ok(!shadowChild2._form.isNativeAnonymous, "Child is not native anonymous");
runNextTest();
});
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>

View File

@ -1,114 +0,0 @@
<!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" src="inspector-helpers.js"></script>
<script type="application/javascript">
"use strict";
const {DocumentWalker} = require("devtools/server/actors/inspector/document-walker");
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
};
let gWalker = null;
let gInspectee = null;
addTest(async function setup() {
const url = document.getElementById("inspectorContent").href;
const { target, doc } = await attachURL(url);
gInspectee = doc;
const inspector = await target.getInspector();
gWalker = inspector.walker;
runNextTest();
});
addAsyncTest(async function testRearrange() {
const longlist = await gWalker.querySelector(gWalker.rootNode, "#longlist");
let children = await gWalker.children(longlist);
const nodeA = children.nodes[0];
is(nodeA.id, "a", "Got the expected node.");
// Move nodeA to the end of the list.
await gWalker.insertBefore(nodeA, longlist, null);
ok(!gInspectee.querySelector("#a").nextSibling,
"a should now be at the end of the list.");
children = await gWalker.children(longlist);
is(nodeA, children.nodes[children.nodes.length - 1],
"a should now be the last returned child.");
// Now move it to the middle of the list.
const nextNode = children.nodes[13];
await gWalker.insertBefore(nodeA, longlist, nextNode);
const sibling =
new DocumentWalker(gInspectee.querySelector("#a"), window).nextSibling();
is(sibling, nextNode.rawNode(), "Node should match the expected next node.");
children = await gWalker.children(longlist);
is(nodeA, children.nodes[13], "a should be where we expect it.");
is(nextNode, children.nodes[14], "next node should be where we expect it.");
runNextTest();
});
addAsyncTest(async function testInsertInvalidInput() {
const longlist = await gWalker.querySelector(gWalker.rootNode, "#longlist");
const children = await gWalker.children(longlist);
const nodeA = children.nodes[0];
const nextSibling = children.nodes[1];
// Now move it to the original location and make sure no mutation happens.
let hasMutated = false;
const observer = new gInspectee.defaultView.MutationObserver(() => {
hasMutated = true;
});
observer.observe(longlist.rawNode(), {
childList: true,
});
await gWalker.insertBefore(nodeA, longlist, nodeA);
ok(!hasMutated, "hasn't mutated");
hasMutated = false;
await gWalker.insertBefore(nodeA, longlist, nextSibling);
ok(!hasMutated, "still hasn't mutated after inserting before nextSibling");
hasMutated = false;
await gWalker.insertBefore(nodeA, longlist);
ok(hasMutated, "has mutated after inserting with null sibling");
hasMutated = false;
await gWalker.insertBefore(nodeA, longlist);
ok(!hasMutated, "hasn't mutated after inserting with null sibling again");
observer.disconnect();
runNextTest();
});
addTest(function cleanup() {
gWalker = null;
gInspectee = null;
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>

View File

@ -1,198 +0,0 @@
<!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" src="inspector-helpers.js"></script>
<script type="application/javascript">
"use strict";
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
};
let gInspectee = null;
let gWalker = null;
let gTarget = null;
async function setup(callback) {
const url = document.getElementById("inspectorContent").href;
const { target, doc } = await attachURL(url);
gInspectee = doc;
gTarget = target;
const inspector = await target.getInspector();
gWalker = inspector.walker;
await callback();
}
function teardown() {
gWalker = null;
gInspectee = null;
}
function assertOwnership() {
return assertOwnershipTrees(gWalker);
}
function loadChildSelector(selector) {
return gWalker.querySelector(gWalker.rootNode, "#childFrame").then(frame => {
ok(frame.numChildren > 0,
"Child frame should consider its loaded document as a child.");
return gWalker.children(frame);
}).then(children => {
return gWalker.querySelectorAll(children.nodes[0], selector);
}).then(nodeList => {
return nodeList.items();
});
}
function getUnloadedDoc(mutations) {
for (const change of mutations) {
if (isUnload(change)) {
return change.target;
}
}
return null;
}
addTest(function loadNewChild() {
setup(() => {
// Load a bunch of fronts for actors inside the child frame.
promiseDone(loadChildSelector("#longlist div").then(() => {
const childFrame = gInspectee.querySelector("#childFrame");
childFrame.src = "data:text/html,<html>new child</html>";
return waitForMutation(gWalker, isChildList);
}).then(mutations => {
const unloaded = getUnloadedDoc(mutations);
mutations = assertSrcChange(mutations);
mutations = assertUnload(mutations);
mutations = assertFrameLoad(mutations);
mutations = assertChildList(mutations);
is(mutations.length, 0, "Got the expected mutations.");
assertOwnership();
return checkMissing(gTarget, unloaded);
}).then(() => {
teardown();
}).then(runNextTest));
});
});
addTest(function loadNewChildTwice() {
setup(() => {
// Load a bunch of fronts for actors inside the child frame.
promiseDone(loadChildSelector("#longlist div").then(() => {
const childFrame = gInspectee.querySelector("#childFrame");
childFrame.src = "data:text/html,<html>new child</html>";
return waitForMutation(gWalker, isChildList);
}).then(mutations => {
// The first load went through as expected (as tested in loadNewChild)
// Now change the source again, but this time we *don't* expect
// an unload, because we haven't seen the new child document yet.
const childFrame = gInspectee.querySelector("#childFrame");
childFrame.src = "data:text/html,<html>second new child</html>";
return waitForMutation(gWalker, isChildList);
}).then(mutations => {
mutations = assertSrcChange(mutations);
mutations = assertFrameLoad(mutations);
mutations = assertChildList(mutations);
ok(!getUnloadedDoc(mutations), "Should not have gotten an unload.");
is(mutations.length, 0, "Got the expected mutations.");
assertOwnership();
}).then(() => {
teardown();
}).then(runNextTest));
});
});
addTest(function loadNewChildTwiceAndCareAboutIt() {
setup(() => {
// Load a bunch of fronts for actors inside the child frame.
promiseDone(loadChildSelector("#longlist div").then(() => {
const childFrame = gInspectee.querySelector("#childFrame");
childFrame.src = "data:text/html,<html>new child</html>";
return waitForMutation(gWalker, isChildList);
}).then(mutations => {
// Read the new child
return loadChildSelector("#longlist div");
}).then(() => {
// Now change the source again, and expect the same results as loadNewChild.
const childFrame = gInspectee.querySelector("#childFrame");
childFrame.src = "data:text/html,<html>second new child</html>";
return waitForMutation(gWalker, isChildList);
}).then(mutations => {
const unloaded = getUnloadedDoc(mutations);
mutations = assertSrcChange(mutations);
mutations = assertUnload(mutations);
mutations = assertFrameLoad(mutations);
mutations = assertChildList(mutations);
is(mutations.length, 0, "Got the expected mutations.");
assertOwnership();
return checkMissing(gTarget, unloaded);
}).then(() => {
teardown();
}).then(runNextTest));
});
});
addTest(function testBack() {
setup(() => {
// Load a bunch of fronts for actors inside the child frame.
promiseDone(loadChildSelector("#longlist div").then(() => {
const childFrame = gInspectee.querySelector("#childFrame");
childFrame.src = "data:text/html,<html>new child</html>";
return waitForMutation(gWalker, isChildList);
}).then(mutations => {
// Read the new child
return loadChildSelector("#longlist div");
}).then(() => {
// Now use history.back to change the source,
// and expect the same results as loadNewChild.
const childFrame = gInspectee.querySelector("#childFrame");
childFrame.contentWindow.history.back();
return waitForMutation(gWalker, isChildList);
}).then(mutations => {
const unloaded = getUnloadedDoc(mutations);
mutations = assertSrcChange(mutations);
mutations = assertUnload(mutations);
mutations = assertFrameLoad(mutations);
mutations = assertChildList(mutations);
is(mutations.length, 0, "Got the expected mutations.");
assertOwnership();
return checkMissing(gTarget, unloaded);
}).then(() => {
teardown();
}).then(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>

View File

@ -1,98 +0,0 @@
<!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" src="inspector-helpers.js"></script>
<script type="application/javascript">
"use strict";
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
};
let gWalker = null;
let gTarget = null;
function assertOwnership() {
return assertOwnershipTrees(gWalker);
}
addTest(async function setup() {
const url = document.getElementById("inspectorContent").href;
const { target } = await attachURL(url);
const inspector = await target.getInspector();
gTarget = target;
gWalker = inspector.walker;
runNextTest();
});
addTest(function testReleaseSubtree() {
let originalOwnershipSize = 0;
let longlist = null;
let firstChild = null;
promiseDone(gWalker.querySelectorAll(gWalker.rootNode, "#longlist div").then(list => {
// Make sure we have the 26 children of longlist in our ownership tree.
is(list.length, 26, "Expect 26 div children.");
// Make sure we've read in all those children and incorporated them
// in our ownership tree.
return list.items();
}).then((items)=> {
originalOwnershipSize = assertOwnership();
// Here is how the ownership tree is summed up:
// #document 1
// <html> 1
// <body> 1
// <div id=longlist> 1
// <div id=a>a</div> 26*2 (each child plus it's singleTextChild)
// ...
// <div id=z>z</div>
// -----
// 56
is(originalOwnershipSize, 56, "Correct number of items in ownership tree");
firstChild = items[0].actorID;
}).then(() => {
// Now get the longlist and release it from the ownership tree.
return gWalker.querySelector(gWalker.rootNode, "#longlist");
}).then(node => {
longlist = node.actorID;
return gWalker.releaseNode(node);
}).then(() => {
// Our ownership size should now be 53 fewer
// (we forgot about #longlist + 26 children + 26 singleTextChild nodes)
const newOwnershipSize = assertOwnership();
is(newOwnershipSize, originalOwnershipSize - 53,
"Ownership tree should be lower");
// Now verify that some nodes have gone away
return checkMissing(gTarget, longlist);
}).then(() => {
return checkMissing(gTarget, firstChild);
}).then(runNextTest));
});
addTest(function cleanup() {
gWalker = null;
gTarget = null;
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>

View File

@ -1,115 +0,0 @@
<!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" src="inspector-helpers.js"></script>
<script type="application/javascript">
"use strict";
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
};
let gWalker = null;
let gTarget = null;
let gInspectee = null;
function assertOwnership() {
return assertOwnershipTrees(gWalker);
}
function ignoreNode(node) {
// Duplicate the walker logic to skip blank nodes...
return node.nodeType === Node.TEXT_NODE &&
!/[^\s]/.test(node.nodeValue);
}
addTest(async function setup() {
const url = document.getElementById("inspectorContent").href;
const { target, doc } = await attachURL(url);
gInspectee = doc;
const inspector = await target.getInspector();
gWalker = inspector.walker;
gTarget = target;
runNextTest();
});
addTest(function testRemoveSubtree() {
let originalOwnershipSize = 0;
let longlist = null;
let longlistID = null;
let nextSibling = gInspectee.querySelector("#longlist").nextSibling;
while (nextSibling && ignoreNode(nextSibling)) {
nextSibling = nextSibling.nextSibling;
}
let previousSibling = gInspectee.querySelector("#longlist").previousSibling;
while (previousSibling && ignoreNode(previousSibling)) {
previousSibling = previousSibling.previousSibling;
}
promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(listFront => {
longlist = listFront;
longlistID = longlist.actorID;
}).then(() => {
return gWalker.children(longlist);
}).then((items)=> {
originalOwnershipSize = assertOwnership();
// Here is how the ownership tree is summed up:
// #document 1
// <html> 1
// <body> 1
// <div id=longlist> 1
// <div id=a>a</div> 26*2 (each child plus it's singleTextChild)
// ...
// <div id=z>z</div>
// -----
// 56
is(originalOwnershipSize, 56, "Correct number of items in ownership tree");
return gWalker.removeNode(longlist);
}).then(siblings => {
is(siblings.previousSibling.rawNode(), previousSibling,
"Should have returned the previous sibling.");
is(siblings.nextSibling.rawNode(), nextSibling,
"Should have returned the next sibling.");
return waitForMutation(gWalker, isChildList);
}).then(() => {
// Our ownership size should now be 51 fewer (we forgot about #longlist + 26
// children + 26 singleTextChild nodes, but learned about #longlist's
// prev/next sibling)
const newOwnershipSize = assertOwnership();
is(newOwnershipSize, originalOwnershipSize - 51,
"Ownership tree should be lower");
// Now verify that some nodes have gone away
return checkMissing(gTarget, longlistID);
}).then(runNextTest));
});
addTest(function cleanup() {
gWalker = null;
gTarget = null;
gInspectee = null;
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>

View File

@ -1,174 +0,0 @@
<!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" src="inspector-helpers.js"></script>
<script type="application/javascript">
"use strict";
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
};
let gWalker = null;
let gInspectee = null;
function assertOwnership() {
return assertOwnershipTrees(gWalker);
}
addTest(async function setup() {
const url = document.getElementById("inspectorContent").href;
const { target, doc } = await attachURL(url);
const inspector = await target.getInspector();
gInspectee = doc;
gWalker = inspector.walker;
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 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 => {
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(() => {
const 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();
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();
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;
const contentNode = gInspectee.querySelector("#a");
contentNode.remove();
// Now wait for that mutation and retain response to come in.
return Promise.all([
gWalker.retainNode(front),
waitForMutation(gWalker, isChildList),
]);
}).then(() => {
assertOwnership();
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();
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;
const contentNode = gInspectee.querySelector("#z");
contentNode.remove();
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) => {
// XXX: Switched to from ok() to todo_is() in Bug 1467712. Follow up in
// 1500960
// This is throwing because of
// `gInspectee.querySelector("#z").parentNode = null;` two blocks above...
// Even if you fix that, the test is still failing because "#a" was removed
// by the previous test. I am switching this to "#z" because I think that
// was the original intent. Still not failing with the expected error message
// Needs more work.
// ok(err, "noSuchActor", "Should have lost the race.");
is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
// Don't re-throw the error.
}).then(runNextTest));
});
addTest(function cleanup() {
gWalker = null;
gInspectee = null;
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>

View File

@ -1,292 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=835896
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 835896</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" src="inspector-helpers.js"></script>
<script type="application/javascript">
"use strict";
window.onload = function() {
const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const {WalkerSearch, WalkerIndex} =
require("devtools/server/actors/utils/walker-search");
SimpleTest.waitForExplicitFinish();
let walkerActor = null;
let walkerSearch = null;
let inspectee = null;
let inspector = null;
// WalkerSearch specific tests. This is to make sure search results are
// coming back as expected.
// See also test_inspector-search-front.html.
addAsyncTest(async function setup() {
info("Setting up inspector and walker actors.");
const url = document.getElementById("inspectorContent").href;
const { target, doc } = await attachURL(url);
inspectee = doc;
inspector = await target.getInspector();
const walkerFront = inspector.walker;
walkerActor = DebuggerServer.searchAllConnectionsForActor(walkerFront.actorID);
ok(walkerActor,
"Got a reference to the walker actor (" + walkerFront.actorID + ")");
walkerSearch = walkerActor.walkerSearch;
runNextTest();
});
addAsyncTest(function testIndexExists() {
info("Testing basic index APIs exist.");
const index = new WalkerIndex(walkerActor);
ok(index.data.size > 0, "public index is filled after getting");
index.clearIndex();
ok(!index._data, "private index is empty after clearing");
ok(index.data.size > 0, "public index is filled after getting");
index.destroy();
runNextTest();
});
addAsyncTest(function testSearchExists() {
info("Testing basic search APIs exist.");
ok(walkerSearch, "walker search exists on the WalkerActor");
ok(walkerSearch.search, "walker search has `search` method");
ok(walkerSearch.index, "walker search has `index` property");
is(walkerSearch.walker, walkerActor, "referencing the correct WalkerActor");
const search = new WalkerSearch(walkerActor);
ok(search, "a new search instance can be created");
ok(search.search, "new search instance has `search` method");
ok(search.index, "new search instance has `index` property");
isnot(search, walkerSearch, "new search instance differs from the WalkerActor's");
search.destroy();
runNextTest();
});
addAsyncTest(function testEmptySearch() {
info("Testing search with an empty query.");
let results = walkerSearch.search("");
is(results.length, 0, "No results when searching for ''");
results = walkerSearch.search(null);
is(results.length, 0, "No results when searching for null");
results = walkerSearch.search(undefined);
is(results.length, 0, "No results when searching for undefined");
results = walkerSearch.search(10);
is(results.length, 0, "No results when searching for 10");
runNextTest();
});
addAsyncTest(function testBasicSearchData() {
const testData = [
{
desc: "Search for tag with one result.",
search: "body",
expected: [
{node: inspectee.body, type: "tag"},
],
},
{
desc: "Search for tag with multiple results",
search: "h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "tag"},
{node: inspectee.querySelectorAll("h2")[1], type: "tag"},
{node: inspectee.querySelectorAll("h2")[2], type: "tag"},
],
},
{
desc: "Search for selector with multiple results",
search: "body > h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "selector"},
{node: inspectee.querySelectorAll("h2")[1], type: "selector"},
{node: inspectee.querySelectorAll("h2")[2], type: "selector"},
],
},
{
desc: "Search for selector with multiple results",
search: ":root h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "selector"},
{node: inspectee.querySelectorAll("h2")[1], type: "selector"},
{node: inspectee.querySelectorAll("h2")[2], type: "selector"},
],
},
{
desc: "Search for selector with multiple results",
search: "* h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "selector"},
{node: inspectee.querySelectorAll("h2")[1], type: "selector"},
{node: inspectee.querySelectorAll("h2")[2], type: "selector"},
],
},
{
desc: "Search with multiple matches in a single tag expecting a single result",
search: "💩",
expected: [
{node: inspectee.getElementById("💩"), type: "attributeValue"},
],
},
{
desc: "Search that has tag and text results",
search: "h1",
expected: [
{node: inspectee.querySelector("h1"), type: "tag"},
{node: inspectee.querySelector("h1 + p").childNodes[0], type: "text"},
{node: inspectee.querySelector("h1 + p > strong").childNodes[0], type: "text"},
],
},
];
for (const {desc, search, expected} of testData) {
info("Running test: " + desc);
const results = walkerSearch.search(search);
isDeeply(results, expected,
"Search returns correct results with '" + search + "'");
}
runNextTest();
});
addAsyncTest(function testPseudoElements() {
info("Testing ::before and ::after element matching");
const beforeElt = new _documentWalker(inspectee.querySelector("#pseudo"),
inspectee.defaultView).firstChild();
const afterElt = new _documentWalker(inspectee.querySelector("#pseudo"),
inspectee.defaultView).lastChild();
const styleText = inspectee.querySelector("style").childNodes[0];
// ::before
let results = walkerSearch.search("::before");
isDeeply(results, [ {node: beforeElt, type: "tag"} ],
"Tag search works for pseudo element");
results = walkerSearch.search("_moz_generated_content_before");
is(results.length, 0, "No results for anon tag name");
results = walkerSearch.search("before element");
isDeeply(results, [
{node: styleText, type: "text"},
{node: beforeElt, type: "text"},
], "Text search works for pseudo element");
// ::after
results = walkerSearch.search("::after");
isDeeply(results, [ {node: afterElt, type: "tag"} ],
"Tag search works for pseudo element");
results = walkerSearch.search("_moz_generated_content_after");
is(results.length, 0, "No results for anon tag name");
results = walkerSearch.search("after element");
isDeeply(results, [
{node: styleText, type: "text"},
{node: afterElt, type: "text"},
], "Text search works for pseudo element");
runNextTest();
});
addAsyncTest(async function testSearchMutationChangeResults() {
info("Testing search before and after a mutation.");
const expected = [
{node: inspectee.querySelectorAll("h3")[0], type: "tag"},
{node: inspectee.querySelectorAll("h3")[1], type: "tag"},
{node: inspectee.querySelectorAll("h3")[2], type: "tag"},
];
let results = walkerSearch.search("h3");
isDeeply(results, expected, "Search works with tag results");
await mutateDocumentAndWaitForMutation(() => {
expected[0].node.remove();
});
results = walkerSearch.search("h3");
isDeeply(results, [
expected[1],
expected[2],
], "Results are updated after removal");
// eslint-disable-next-line new-cap
await new Promise(resolve => {
info("Waiting for a mutation to happen");
const observer = new inspectee.defaultView.MutationObserver(() => {
resolve();
});
observer.observe(inspectee, {attributes: true, subtree: true});
inspectee.body.setAttribute("h3", "true");
});
results = walkerSearch.search("h3");
isDeeply(results, [
{node: inspectee.body, type: "attributeName"},
expected[1],
expected[2],
], "Results are updated after addition");
// eslint-disable-next-line new-cap
await new Promise(resolve => {
info("Waiting for a mutation to happen");
const observer = new inspectee.defaultView.MutationObserver(() => {
resolve();
});
observer.observe(inspectee, {attributes: true, childList: true, subtree: true});
inspectee.body.removeAttribute("h3");
expected[1].node.remove();
expected[2].node.remove();
});
results = walkerSearch.search("h3");
is(results.length, 0, "Results are updated after removal");
runNextTest();
});
runNextTest();
function mutateDocumentAndWaitForMutation(mutationFn) {
// eslint-disable-next-line new-cap
return new Promise(resolve => {
info("Listening to markup mutation on the inspectee");
const observer = new inspectee.defaultView.MutationObserver(resolve);
observer.observe(inspectee, {childList: true, subtree: true});
mutationFn();
});
}
};
</script>
</head>
<body>
<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -1,335 +0,0 @@
<!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" src="inspector-helpers.js"></script>
<script type="application/javascript">
"use strict";
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
};
let gInspectee = null;
let gTarget = null;
let gWalker = null;
const checkActorIDs = [];
function assertOwnership() {
assertOwnershipTrees(gWalker);
}
addTest(async function setup() {
const url = document.getElementById("inspectorContent").href;
const { target, doc } = await attachURL(url);
const inspector = await target.getInspector();
gInspectee = doc;
gTarget = target;
gWalker = inspector.walker;
runNextTest();
});
addTest(function testWalkerRoot() {
// Make sure that refetching the root document of the walker returns the same
// actor as the getWalker returned.
promiseDone(gWalker.document().then(root => {
ok(root === gWalker.rootNode,
"Re-fetching the document node should match the root document node.");
checkActorIDs.push(root.actorID);
assertOwnership();
}).then(runNextTest));
});
addTest(function testInnerHTML() {
promiseDone(gWalker.documentElement().then(docElement => {
return gWalker.innerHTML(docElement);
}).then(longstring => {
return longstring.string();
}).then(innerHTML => {
ok(innerHTML === gInspectee.documentElement.innerHTML, "innerHTML should match");
}).then(runNextTest));
});
addTest(function testOuterHTML() {
promiseDone(gWalker.documentElement().then(docElement => {
return gWalker.outerHTML(docElement);
}).then(longstring => {
return longstring.string();
}).then(outerHTML => {
ok(outerHTML === gInspectee.documentElement.outerHTML, "outerHTML should match");
}).then(runNextTest));
});
addTest(function testSetOuterHTMLNode() {
const newHTML = "<p id=\"edit-html-done\">after edit</p>";
promiseDone(gWalker.querySelector(gWalker.rootNode, "#edit-html").then(node => {
return gWalker.setOuterHTML(node, newHTML);
}).then(() => {
return gWalker.querySelector(gWalker.rootNode, "#edit-html-done");
}).then(node => {
return gWalker.outerHTML(node);
}).then(longstring => {
return longstring.string();
}).then(outerHTML => {
is(outerHTML, newHTML, "outerHTML has been updated");
}).then(() => {
return gWalker.querySelector(gWalker.rootNode, "#edit-html");
}).then(node => {
ok(!node, "The node with the old ID cannot be selected anymore");
}).then(runNextTest));
});
addTest(function testQuerySelector() {
promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(node => {
is(node.getAttribute("data-test"), "exists", "should have found the right node");
assertOwnership();
}).then(() => {
return gWalker.querySelector(gWalker.rootNode, "unknownqueryselector").then(node => {
ok(!node, "Should not find a node here.");
assertOwnership();
});
}).then(runNextTest));
});
addTest(function testQuerySelectors() {
let nodeList = null;
let firstNode = null;
let nodeListID = null;
promiseDone(gWalker.querySelectorAll(gWalker.rootNode, "#longlist div").then(list => {
nodeList = list;
is(nodeList.length, 26, "Expect 26 div children.");
assertOwnership();
return nodeList.item(0);
}).then(node => {
firstNode = node;
checkActorIDs.push(node.actorID);
is(node.id, "a", "First child should be a");
assertOwnership();
return nodeList.items();
}).then(nodes => {
is(nodes.length, 26, "Expect 26 nodes");
is(nodes[0], firstNode, "First node should be reused.");
ok(nodes[0]._parent, "Parent node should be set.");
ok(nodes[0]._next || nodes[0]._prev, "Siblings should be set.");
ok(nodes[25]._next || nodes[25]._prev,
"Siblings of " + nodes[25] + " should be set.");
assertOwnership();
return nodeList.items(-1);
}).then(nodes => {
is(nodes.length, 1, "Expect 1 node");
is(nodes[0].id, "z", "Expect it to be the last node.");
checkActorIDs.push(nodes[0].actorID);
// Save the node list ID so we can ensure it was destroyed.
nodeListID = nodeList.actorID;
assertOwnership();
return nodeList.release();
}).then(() => {
ok(!nodeList.actorID, "Actor should have been destroyed.");
assertOwnership();
return checkMissing(gTarget, nodeListID);
}).then(runNextTest));
});
// Helper to check the response of requests that return hasFirst/hasLast/nodes
// node lists (like `children` and `siblings`)
function nodeArrayChecker(first, last, ids) {
return function(response) {
is(response.hasFirst, first,
"Should " + (first ? "" : "not ") + " have the first node.");
is(response.hasLast, last, "Should " + (last ? "" : "not ") + " have the last node.");
is(response.nodes.length, ids.length,
"Should have " + ids.length + " children listed.");
let responseIds = "";
for (const node of response.nodes) {
responseIds += node.id;
}
is(responseIds, ids, "Correct nodes were returned.");
assertOwnership();
};
}
addTest(function testNoChildren() {
promiseDone(gWalker.querySelector(gWalker.rootNode, "#empty").then(empty => {
assertOwnership();
return gWalker.children(empty).then(nodeArrayChecker(true, true, ""));
}).then(runNextTest));
});
addTest(function testLongListTraversal() {
let longList;
let allChildren;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(node => {
longList = node;
// First call with no options, expect all children.
assertOwnership();
return gWalker.children(longList).then(response => {
nodeArrayChecker(true, true, "abcdefghijklmnopqrstuvwxyz")(response);
allChildren = response.nodes;
assertOwnership();
});
}).then(() => {
// maxNodes should limit us to the first 5 nodes.
assertOwnership();
return gWalker.children(longList, { maxNodes: 5 })
.then(nodeArrayChecker(true, false, "abcde"));
}).then(() => {
assertOwnership();
// maxNodes with the second item centered should still give us the first 5 nodes.
return gWalker.children(longList, { maxNodes: 5, center: allChildren[1] }).then(
nodeArrayChecker(true, false, "abcde")
);
}).then(() => {
// maxNodes with a center in the middle of the list should put that item in the middle
const center = allChildren[13];
is(center.id, "n", "Make sure I know how to count letters.");
return gWalker.children(longList, { maxNodes: 5, center: center }).then(
nodeArrayChecker(false, false, "lmnop")
);
}).then(() => {
// maxNodes with the second-to-last item centered should give us the last 5 nodes.
return gWalker.children(longList, { maxNodes: 5, center: allChildren[24] }).then(
nodeArrayChecker(false, true, "vwxyz")
);
}).then(() => {
// maxNodes with a start in the middle should start at that node and fetch 5
const start = allChildren[13];
is(start.id, "n", "Make sure I know how to count letters.");
return gWalker.children(longList, { maxNodes: 5, start: start }).then(
nodeArrayChecker(false, false, "nopqr")
);
}).then(() => {
// maxNodes near the end should only return what's left
return gWalker.children(longList, { maxNodes: 5, start: allChildren[24] }).then(
nodeArrayChecker(false, true, "yz")
);
}).then(runNextTest));
});
addTest(function testObjectNodeChildren() {
promiseDone(
gWalker.querySelector(gWalker.rootNode, "object")
.then(object => gWalker.children(object))
.then(nodeArrayChecker(true, true, "1"))
.then(runNextTest));
});
addTest(function testNextSibling() {
promiseDone(gWalker.querySelector(gWalker.rootNode, "#y").then(y => {
is(y.id, "y", "Got the right node.");
return gWalker.nextSibling(y);
}).then(z => {
is(z.id, "z", "nextSibling got the next node.");
return gWalker.nextSibling(z);
}).then(nothing => {
is(nothing, null, "nextSibling on the last node returned null.");
}).then(runNextTest));
});
addTest(function testPreviousSibling() {
promiseDone(gWalker.querySelector(gWalker.rootNode, "#b").then(b => {
is(b.id, "b", "Got the right node.");
return gWalker.previousSibling(b);
}).then(a => {
is(a.id, "a", "nextSibling got the next node.");
return gWalker.previousSibling(a);
}).then(nothing => {
is(nothing, null, "previousSibling on the first node returned null.");
}).then(runNextTest));
});
addTest(function testFrameTraversal() {
promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
return gWalker.children(childFrame);
}).then(children => {
const nodes = children.nodes;
is(nodes.length, 1, "There should be only one child of the iframe");
is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
return gWalker.querySelector(nodes[0], "#z");
}).then(runNextTest));
});
addTest(function testLongValue() {
const testSummaryLength = 10;
const WalkerActor = require("devtools/server/actors/inspector/walker");
WalkerActor.setValueSummaryLength(testSummaryLength);
SimpleTest.registerCleanupFunction(function() {
WalkerActor.setValueSummaryLength(WalkerActor.DEFAULT_VALUE_SUMMARY_LENGTH);
});
const longstringText = gInspectee.getElementById("longstring").firstChild.nodeValue;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#longstring").then(node => {
ok(!node.inlineTextChild, "Text is too long to be inlined");
// Now we need to get the text node child...
return gWalker.children(node, { maxNodes: 1 });
}).then(children => {
const textNode = children.nodes[0];
is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
return textNode;
}).then(textNode => {
return textNode.getNodeValue();
}).then(value => {
return value.string();
}).then(valueStr => {
is(valueStr, longstringText,
"Full node value should match the string from the document.");
}).then(runNextTest));
});
addTest(function testShortValue() {
const shortstringText = gInspectee.getElementById("shortstring").firstChild.nodeValue;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#shortstring").then(node => {
ok(!!node.inlineTextChild, "Text is short enough to be inlined");
// Now we need to get the text node child...
return gWalker.children(node, { maxNodes: 1 });
}).then(children => {
const textNode = children.nodes[0];
is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
return textNode;
}).then(textNode => {
return textNode.getNodeValue();
}).then(value => {
return value.string();
}).then(valueStr => {
is(valueStr, shortstringText,
"Full node value should match the string from the document.");
}).then(runNextTest));
});
addTest(function testReleaseWalker() {
checkActorIDs.push(gWalker.actorID);
promiseDone(gWalker.release().then(() => {
const promises = Array.from(checkActorIDs, (id) => checkMissing(gTarget, id));
return Promise.all(promises);
}).then(runNextTest));
});
addTest(function cleanup() {
gWalker = null;
gInspectee = null;
gTarget = null;
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>