Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
Services.scriptloader.loadSubScript(
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 SpecialPowers.spawn(
gBrowser.selectedBrowser,
[[walker.actorID]],
async function (actorID) {
const { require } = ChromeUtils.importESModule(
);
const {
DevToolsServer,
const {
DocumentWalker: _documentWalker,
// Convert actorID to current compartment string otherwise
// searchAllConnectionsForActor is confused and won't find the actor.
actorID = String(actorID);
const walkerActor = DevToolsServer.searchAllConnectionsForActor(actorID);
const walkerSearch = walkerActor.walkerSearch;
const {
WalkerSearch,
WalkerIndex,
info("Testing basic index APIs exist.");
const index = new WalkerIndex(walkerActor);
Assert.greater(
index.data.size,
0,
"public index is filled after getting"
);
index.clearIndex();
ok(!index._data, "private index is empty after clearing");
Assert.greater(
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",
},
],
},
{
desc: "Search for XPath with one result",
search: "//strong",
expected: [
{ node: inspectee.querySelector("strong"), type: "xpath" },
],
},
{
desc: "Search for XPath with multiple results",
search: "//h2",
expected: [
{ node: inspectee.querySelectorAll("h2")[0], type: "xpath" },
{ node: inspectee.querySelectorAll("h2")[1], type: "xpath" },
{ node: inspectee.querySelectorAll("h2")[2], type: "xpath" },
],
},
{
desc: "Search for XPath via containing text",
search: "//*[contains(text(), 'p tag')]",
expected: [{ node: inspectee.querySelector("p"), type: "xpath" }],
},
{
desc: "Search for XPath matching text node",
search: "//strong/text()",
expected: [
{
node: inspectee.querySelector("strong").firstChild,
type: "xpath",
},
],
},
{
desc: "Search using XPath grouping expression",
search: "(//*)[2]",
expected: [{ node: inspectee.querySelector("head"), type: "xpath" }],
},
{
desc: "Search using XPath function",
search: "id('arrows')",
expected: [
{ node: inspectee.querySelector("#arrows"), type: "xpath" },
],
},
];
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");
}
);
});