summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests/browser/browser_inspector-search.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/tests/browser/browser_inspector-search.js')
-rw-r--r--devtools/server/tests/browser/browser_inspector-search.js347
1 files changed, 347 insertions, 0 deletions
diff --git a/devtools/server/tests/browser/browser_inspector-search.js b/devtools/server/tests/browser/browser_inspector-search.js
new file mode 100644
index 0000000000..21cf745ce1
--- /dev/null
+++ b/devtools/server/tests/browser/browser_inspector-search.js
@@ -0,0 +1,347 @@
+/* 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 SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [[walker.actorID]],
+ async function (actorID) {
+ const { require } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+ );
+ const {
+ DevToolsServer,
+ } = require("resource://devtools/server/devtools-server.js");
+ const {
+ DocumentWalker: _documentWalker,
+ } = require("resource://devtools/server/actors/inspector/document-walker.js");
+
+ // 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,
+ } = require("resource://devtools/server/actors/utils/walker-search.js");
+
+ 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");
+ }
+ );
+});