summaryrefslogtreecommitdiffstats
path: root/devtools/client/dom/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/client/dom/test
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/dom/test')
-rw-r--r--devtools/client/dom/test/browser.ini18
-rw-r--r--devtools/client/dom/test/browser_dom_array.js66
-rw-r--r--devtools/client/dom/test/browser_dom_basic.js22
-rw-r--r--devtools/client/dom/test/browser_dom_fission_target_switching.js41
-rw-r--r--devtools/client/dom/test/browser_dom_iframe_picker.js80
-rw-r--r--devtools/client/dom/test/browser_dom_nodes_highlight.js73
-rw-r--r--devtools/client/dom/test/browser_dom_nodes_select.js43
-rw-r--r--devtools/client/dom/test/browser_dom_refresh.js30
-rw-r--r--devtools/client/dom/test/head.js201
-rw-r--r--devtools/client/dom/test/page_array.html19
-rw-r--r--devtools/client/dom/test/page_basic.html15
-rw-r--r--devtools/client/dom/test/page_dom_nodes.html18
12 files changed, 626 insertions, 0 deletions
diff --git a/devtools/client/dom/test/browser.ini b/devtools/client/dom/test/browser.ini
new file mode 100644
index 0000000000..379ca86471
--- /dev/null
+++ b/devtools/client/dom/test/browser.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ head.js
+ page_array.html
+ page_basic.html
+ page_dom_nodes.html
+ !/devtools/client/shared/test/shared-head.js
+ !/devtools/client/shared/test/telemetry-test-helpers.js
+
+[browser_dom_array.js]
+[browser_dom_basic.js]
+[browser_dom_fission_target_switching.js]
+[browser_dom_iframe_picker.js]
+[browser_dom_nodes_highlight.js]
+[browser_dom_nodes_select.js]
+[browser_dom_refresh.js]
diff --git a/devtools/client/dom/test/browser_dom_array.js b/devtools/client/dom/test/browser_dom_array.js
new file mode 100644
index 0000000000..9ef35b1717
--- /dev/null
+++ b/devtools/client/dom/test/browser_dom_array.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_array.html";
+const TEST_ARRAY = [
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+];
+
+/**
+ * Basic test that checks content of the DOM panel.
+ */
+add_task(async function () {
+ info("Test DOM Panel Array Expansion started");
+
+ const { panel } = await addTestTab(TEST_PAGE_URL);
+
+ // Expand specified row and wait till children are displayed.
+ await expandRow(panel, "_a");
+
+ // Verify that children is displayed now.
+ const childRows = getAllRowsForLabel(panel, "_a");
+
+ const item = childRows.pop();
+ is(item.name, "length", "length property is correct");
+ is(item.value, 26, "length property value is 26");
+
+ let i = 0;
+ for (const name in childRows) {
+ const row = childRows[name];
+
+ is(
+ parseInt(name, 10),
+ i++,
+ `index ${name} is correct and sorted into the correct position`
+ );
+ ok(typeof row.name === "number", "array index is displayed as a number");
+ is(TEST_ARRAY[name], row.value, `value for array[${name}] is ${row.value}`);
+ }
+});
diff --git a/devtools/client/dom/test/browser_dom_basic.js b/devtools/client/dom/test/browser_dom_basic.js
new file mode 100644
index 0000000000..c6b962015f
--- /dev/null
+++ b/devtools/client/dom/test/browser_dom_basic.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
+
+/**
+ * Basic test that checks content of the DOM panel.
+ */
+add_task(async function () {
+ info("Test DOM panel basic started");
+
+ const { panel } = await addTestTab(TEST_PAGE_URL);
+
+ // Expand specified row and wait till children are displayed.
+ await expandRow(panel, "_a");
+
+ // Verify that child is displayed now.
+ const childRow = getRowByLabel(panel, "_data");
+ ok(childRow, "Child row must exist");
+});
diff --git a/devtools/client/dom/test/browser_dom_fission_target_switching.js b/devtools/client/dom/test/browser_dom_fission_target_switching.js
new file mode 100644
index 0000000000..4393a7d842
--- /dev/null
+++ b/devtools/client/dom/test/browser_dom_fission_target_switching.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test top-level target switching in the DOM panel.
+
+const PARENT_PROCESS_URI = "about:robots";
+const CONTENT_PROCESS_URI = URL_ROOT_SSL + "page_basic.html";
+
+add_task(async function () {
+ // We use about:robots as the starting page because it will run in the parent process.
+ // Navigating from that page to a regular content page will always trigger a target
+ // switch, with or without fission.
+
+ info("Open a page that runs in the parent process");
+ const { panel } = await addTestTab(PARENT_PROCESS_URI);
+
+ const _aProperty = getRowByLabel(panel, "_a");
+ let buttonProperty = getRowByLabel(panel, "button");
+
+ ok(!_aProperty, "There is no _a property on the about:robots page");
+ ok(buttonProperty, "There is, however, a button property on this page");
+
+ info("Navigate to a page that runs in the content process");
+ // Wait for the DOM panel to refresh.
+ const store = getReduxStoreFromPanel(panel);
+ const onPropertiesFetched = waitForDispatch(store, "FETCH_PROPERTIES");
+ // Also wait for the toolbox to switch to the new target, to avoid hanging requests when
+ // the test ends.
+ await navigateTo(CONTENT_PROCESS_URI);
+ await onPropertiesFetched;
+
+ await waitFor(() => getRowByLabel(panel, "_a"));
+ ok(true, "This time, the _a property exists on this content process page");
+
+ buttonProperty = getRowByLabel(panel, "button");
+ ok(
+ !buttonProperty,
+ "There is, however, no more button property on this page"
+ );
+});
diff --git a/devtools/client/dom/test/browser_dom_iframe_picker.js b/devtools/client/dom/test/browser_dom_iframe_picker.js
new file mode 100644
index 0000000000..60e7747386
--- /dev/null
+++ b/devtools/client/dom/test/browser_dom_iframe_picker.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that the DOM panel works as expected when a specific frame is selected in the
+// iframe picker.
+
+const TEST_URL = `https://example.com/document-builder.sjs?html=
+ <h1>top_level</h1>
+ <iframe src="https://example.org/document-builder.sjs?html=in_iframe"></iframe>`;
+
+add_task(async function () {
+ const { panel } = await addTestTab(TEST_URL);
+ const toolbox = panel._toolbox;
+
+ info("Wait until the iframe picker button is visible");
+ try {
+ await waitFor(() => toolbox.doc.getElementById("command-button-frames"));
+ } catch (e) {
+ if (isFissionEnabled() && !isEveryFrameTargetEnabled()) {
+ ok(
+ true,
+ "Remote frames are not displayed in iframe picker if Fission is enabled but EFT is not"
+ );
+ return;
+ }
+ throw e;
+ }
+
+ info("Check `document` property when no specific frame is focused");
+ let documentPropertyValue = getDocumentPropertyValue(panel);
+
+ ok(
+ documentPropertyValue.startsWith("HTMLDocument https://example.com"),
+ `Got expected "document" value (${documentPropertyValue})`
+ );
+
+ info(
+ "Select the frame in the iframe picker and check that the document property is updated"
+ );
+ // Wait for the DOM panel to refresh.
+ const store = getReduxStoreFromPanel(panel);
+ let onPropertiesFetched = waitForDispatch(store, "FETCH_PROPERTIES");
+
+ const exampleOrgFrame = toolbox.doc.querySelector(
+ "#toolbox-frame-menu .menuitem:last-child .command"
+ );
+
+ exampleOrgFrame.click();
+ await onPropertiesFetched;
+
+ documentPropertyValue = getDocumentPropertyValue(panel);
+ ok(
+ documentPropertyValue.startsWith("HTMLDocument https://example.org"),
+ `Got expected "document" value (${documentPropertyValue})`
+ );
+
+ info(
+ "Select the top-level frame and check that the document property is updated"
+ );
+ onPropertiesFetched = waitForDispatch(store, "FETCH_PROPERTIES");
+
+ const exampleComFrame = toolbox.doc.querySelector(
+ "#toolbox-frame-menu .menuitem:first-child .command"
+ );
+ exampleComFrame.click();
+ await onPropertiesFetched;
+
+ documentPropertyValue = getDocumentPropertyValue(panel);
+ ok(
+ documentPropertyValue.startsWith("HTMLDocument https://example.com"),
+ `Got expected "document" value (${documentPropertyValue})`
+ );
+});
+
+function getDocumentPropertyValue(panel) {
+ return getRowByLabel(panel, "document").querySelector("td.treeValueCell")
+ .textContent;
+}
diff --git a/devtools/client/dom/test/browser_dom_nodes_highlight.js b/devtools/client/dom/test/browser_dom_nodes_highlight.js
new file mode 100644
index 0000000000..311a52b30c
--- /dev/null
+++ b/devtools/client/dom/test/browser_dom_nodes_highlight.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_dom_nodes.html";
+
+/**
+ * Checks that hovering nodes highlights them in the content page
+ */
+add_task(async function () {
+ info("Test DOM panel node highlight started");
+
+ const { panel, tab } = await addTestTab(TEST_PAGE_URL);
+ const toolbox = await gDevTools.getToolboxForTab(tab);
+ const highlighter = toolbox.getHighlighter();
+
+ const tests = [
+ {
+ expected: "h1",
+ getNode: async () => {
+ return getRowByIndex(panel, 0).querySelector(".objectBox-node");
+ },
+ },
+ {
+ expected: "h2",
+ getNode: async () => {
+ info("Expand specified row and wait till children are displayed");
+ await expandRow(panel, "B");
+ return getRowByIndex(panel, 1).querySelector(".objectBox-node");
+ },
+ },
+ ];
+
+ for (const test of tests) {
+ info(`Get the NodeFront for ${test.expected}`);
+ const node = await test.getNode();
+
+ info("Highlight the node by moving the cursor on it");
+ const onHighlighterShown = highlighter.waitForHighlighterShown();
+ EventUtils.synthesizeMouseAtCenter(
+ node,
+ {
+ type: "mouseover",
+ },
+ node.ownerDocument.defaultView
+ );
+ const { nodeFront } = await onHighlighterShown;
+ is(
+ nodeFront.displayName,
+ test.expected,
+ "The correct node was highlighted"
+ );
+
+ info("Unhighlight the node by moving the cursor away from it");
+ const onHighlighterHidden = highlighter.waitForHighlighterHidden();
+ const btn = toolbox.doc.querySelector("#toolbox-meatball-menu-button");
+ EventUtils.synthesizeMouseAtCenter(
+ btn,
+ {
+ type: "mouseover",
+ },
+ btn.ownerDocument.defaultView
+ );
+
+ const { nodeFront: unhighlightedNode } = await onHighlighterHidden;
+ is(
+ unhighlightedNode.displayName,
+ test.expected,
+ "The node was unhighlighted"
+ );
+ }
+});
diff --git a/devtools/client/dom/test/browser_dom_nodes_select.js b/devtools/client/dom/test/browser_dom_nodes_select.js
new file mode 100644
index 0000000000..7c71f2fde1
--- /dev/null
+++ b/devtools/client/dom/test/browser_dom_nodes_select.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_dom_nodes.html";
+
+/**
+ * Checks whether hovering nodes highlight them in the content page
+ */
+add_task(async function () {
+ info("Test DOM panel node highlight started");
+
+ const { panel, tab } = await addTestTab(TEST_PAGE_URL);
+ const toolbox = await gDevTools.getToolboxForTab(tab);
+ const node = getRowByIndex(panel, 0);
+
+ // Loading the inspector panel at first, to make it possible to listen for
+ // new node selections
+
+ await toolbox.loadTool("inspector");
+ const inspector = toolbox.getPanel("inspector");
+
+ const openInInspectorIcon = node.querySelector(".open-inspector");
+ ok(node !== null, "Node was logged as expected");
+
+ info(
+ "Clicking on the inspector icon and waiting for the " +
+ "inspector to be selected"
+ );
+ const onInspectorSelected = toolbox.once("inspector-selected");
+ const onInspectorUpdated = inspector.once("inspector-updated");
+ const onNewNode = toolbox.selection.once("new-node-front");
+
+ openInInspectorIcon.click();
+
+ await onInspectorSelected;
+ await onInspectorUpdated;
+ const nodeFront = await onNewNode;
+
+ ok(true, "Inspector selected and new node got selected");
+ is(nodeFront.displayName, "h1", "The expected node was selected");
+});
diff --git a/devtools/client/dom/test/browser_dom_refresh.js b/devtools/client/dom/test/browser_dom_refresh.js
new file mode 100644
index 0000000000..a82eb0accc
--- /dev/null
+++ b/devtools/client/dom/test/browser_dom_refresh.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
+
+/**
+ * Basic test that checks the Refresh action in DOM panel.
+ */
+add_task(async function () {
+ info("Test DOM panel basic started");
+
+ const { panel } = await addTestTab(TEST_PAGE_URL);
+
+ // Create a new variable in the page scope and refresh the panel.
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ content.wrappedJSObject._b = 10;
+ });
+
+ await refreshPanel(panel);
+
+ // Verify that the variable is displayed now.
+ const row = getRowByLabel(panel, "_b");
+ ok(row, "New variable must be displayed");
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ delete content.wrappedJSObject._b;
+ });
+});
diff --git a/devtools/client/dom/test/head.js b/devtools/client/dom/test/head.js
new file mode 100644
index 0000000000..93afb2fe09
--- /dev/null
+++ b/devtools/client/dom/test/head.js
@@ -0,0 +1,201 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
+
+"use strict";
+
+// shared-head.js handles imports, constants, and utility functions
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
+ this
+);
+
+// DOM panel actions.
+const constants = require("resource://devtools/client/dom/content/constants.js");
+
+// Uncomment this pref to dump all devtools emitted events to the console.
+// Services.prefs.setBoolPref("devtools.dom.enabled", true);
+
+// Enable the DOM panel
+Services.prefs.setBoolPref("devtools.dom.enabled", true);
+
+registerCleanupFunction(() => {
+ info("finish() was called, cleaning up...");
+ Services.prefs.clearUserPref("devtools.dump.emit");
+ Services.prefs.clearUserPref("devtools.dom.enabled");
+});
+
+/**
+ * Add a new test tab in the browser and load the given url.
+ * @param {String} url
+ * The url to be loaded in the new tab
+ * @return a promise that resolves to the tab object when
+ * the url is loaded
+ */
+async function addTestTab(url) {
+ info("Adding a new test tab with URL: '" + url + "'");
+
+ const tab = await addTab(url);
+
+ // Select the DOM panel and wait till it's initialized.
+ const panel = await initDOMPanel(tab);
+
+ // FETCH_PROPERTIES should be fired during the call to initDOMPanel
+ // But note that this behavior changed during a change in webconsole
+ // initialization. So this might be racy.
+ const doc = panel.panelWin.document;
+ const nodes = [...doc.querySelectorAll(".treeLabel")];
+ ok(!!nodes.length, "The DOM panel is already populated");
+
+ return {
+ tab,
+ browser: tab.linkedBrowser,
+ panel,
+ };
+}
+
+/**
+ * Open the DOM panel for the given tab.
+ *
+ * @param {Element} tab
+ * Optional tab element for which you want open the DOM panel.
+ * The default tab is taken from the global variable |tab|.
+ * @return a promise that is resolved once the web console is open.
+ */
+async function initDOMPanel(tab) {
+ tab = tab || gBrowser.selectedTab;
+ const toolbox = await gDevTools.showToolboxForTab(tab, { toolId: "dom" });
+ const panel = toolbox.getCurrentPanel();
+ return panel;
+}
+
+/**
+ * Synthesize asynchronous click event (with clean stack trace).
+ */
+function synthesizeMouseClickSoon(panel, element) {
+ return new Promise(resolve => {
+ executeSoon(() => {
+ EventUtils.synthesizeMouse(element, 2, 2, {}, panel.panelWin);
+ resolve();
+ });
+ });
+}
+
+/**
+ * Returns tree row with specified label.
+ */
+function getRowByLabel(panel, text) {
+ const doc = panel.panelWin.document;
+ const labels = [...doc.querySelectorAll(".treeLabel")];
+ const label = labels.find(node => node.textContent == text);
+ return label ? label.closest(".treeRow") : null;
+}
+
+/**
+ * Returns tree row with specified index.
+ */
+function getRowByIndex(panel, id) {
+ const doc = panel.panelWin.document;
+ const labels = [...doc.querySelectorAll(".treeLabel")];
+ const label = labels.find((node, i) => i == id);
+ return label ? label.closest(".treeRow") : null;
+}
+
+/**
+ * Returns the children (tree row text) of the specified object name as an
+ * array.
+ */
+function getAllRowsForLabel(panel, text) {
+ let rootObjectLevel;
+ let node;
+ const result = [];
+ const doc = panel.panelWin.document;
+ const nodes = [...doc.querySelectorAll(".treeLabel")];
+
+ // Find the label (object name) for which we want the children. We remove
+ // nodes from the start of the array until we reach the property. The children
+ // are then at the start of the array.
+ while (true) {
+ node = nodes.shift();
+
+ if (!node || node.textContent === text) {
+ rootObjectLevel = node.getAttribute("data-level");
+ break;
+ }
+ }
+
+ // Return an empty array if the node is not found.
+ if (!node) {
+ return result;
+ }
+
+ // Now get the children.
+ for (node of nodes) {
+ const level = node.getAttribute("data-level");
+
+ if (level > rootObjectLevel) {
+ result.push({
+ name: normalizeTreeValue(node.textContent),
+ value: normalizeTreeValue(
+ node.parentNode.nextElementSibling.textContent
+ ),
+ });
+ } else {
+ break;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Strings in the tree are in the form ""a"" and numbers in the form "1". We
+ * normalize these values by converting ""a"" to "a" and "1" to 1.
+ *
+ * @param {String} value
+ * The value to normalize.
+ * @return {String|Number}
+ * The normalized value.
+ */
+function normalizeTreeValue(value) {
+ if (value === `""`) {
+ return "";
+ }
+ if (value.startsWith(`"`) && value.endsWith(`"`)) {
+ return value.substr(1, value.length - 2);
+ }
+ if (isFinite(value) && parseInt(value, 10) == value) {
+ return parseInt(value, 10);
+ }
+
+ return value;
+}
+
+/**
+ * Expands elements with given label and waits till
+ * children are received from the backend.
+ */
+function expandRow(panel, labelText) {
+ const row = getRowByLabel(panel, labelText);
+ return synthesizeMouseClickSoon(panel, row).then(() => {
+ // Wait till children (properties) are fetched
+ // from the backend.
+ const store = getReduxStoreFromPanel(panel);
+ return waitForDispatch(store, "FETCH_PROPERTIES");
+ });
+}
+
+function refreshPanel(panel) {
+ const doc = panel.panelWin.document;
+ const button = doc.querySelector("#dom-refresh-button");
+ return synthesizeMouseClickSoon(panel, button).then(() => {
+ // Wait till children (properties) are fetched
+ // from the backend.
+ const store = getReduxStoreFromPanel(panel);
+ return waitForDispatch(store, "FETCH_PROPERTIES");
+ });
+}
+
+function getReduxStoreFromPanel(panel) {
+ return panel.panelWin.view.mainFrame.store;
+}
diff --git a/devtools/client/dom/test/page_array.html b/devtools/client/dom/test/page_array.html
new file mode 100644
index 0000000000..848d6c4ce5
--- /dev/null
+++ b/devtools/client/dom/test/page_array.html
@@ -0,0 +1,19 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>DOM Panel Array Expansion Test Page</title>
+ </head>
+ <body>
+ <h2>DOM Panel Array Expansion Test Page</h2>
+ <script type="text/javascript">
+ "use strict";
+ window._a = [
+ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+ "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
+ ];
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/dom/test/page_basic.html b/devtools/client/dom/test/page_basic.html
new file mode 100644
index 0000000000..170b3112a6
--- /dev/null
+++ b/devtools/client/dom/test/page_basic.html
@@ -0,0 +1,15 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>DOM test page</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ "use strict";
+ window._a = {_data: "test"};
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/dom/test/page_dom_nodes.html b/devtools/client/dom/test/page_dom_nodes.html
new file mode 100644
index 0000000000..64e78d08db
--- /dev/null
+++ b/devtools/client/dom/test/page_dom_nodes.html
@@ -0,0 +1,18 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>DOM test hovering nodes page</title>
+ </head>
+ <body>
+ <h1 id="a">Node highlight test</h1>
+ <h2 id="b">Node highlight test inside object</h2>
+ <script>
+ "use strict";
+ window.A = document.getElementById("a");
+ window.B = {_data: document.getElementById("b")};
+ </script>
+ </body>
+</html>