diff options
Diffstat (limited to 'toolkit/devtools/inspector/test')
79 files changed, 5611 insertions, 0 deletions
diff --git a/toolkit/devtools/inspector/test/browser.ini b/toolkit/devtools/inspector/test/browser.ini new file mode 100644 index 000000000..5d8400d79 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser.ini @@ -0,0 +1,82 @@ +[DEFAULT] +subsuite = devtools +support-files = + doc_frame_script.js + doc_inspector_breadcrumbs.html + doc_inspector_delete-selected-node-01.html + doc_inspector_delete-selected-node-02.html + doc_inspector_gcli-inspect-command.html + doc_inspector_highlight_after_transition.html + doc_inspector_highlighter-comments.html + doc_inspector_highlighter_csstransform.html + doc_inspector_highlighter.html + doc_inspector_highlighter_inline.html + doc_inspector_highlighter_rect.html + doc_inspector_highlighter_rect_iframe.html + doc_inspector_infobar_01.html + doc_inspector_infobar_02.html + doc_inspector_menu.html + doc_inspector_remove-iframe-during-load.html + doc_inspector_search.html + doc_inspector_search-suggestions.html + doc_inspector_select-last-selected-01.html + doc_inspector_select-last-selected-02.html + head.js + +[browser_inspector_breadcrumbs.js] +[browser_inspector_breadcrumbs_highlight_hover.js] +[browser_inspector_delete-selected-node-01.js] +[browser_inspector_delete-selected-node-02.js] +[browser_inspector_delete-selected-node-03.js] +[browser_inspector_destroy-after-navigation.js] +[browser_inspector_destroy-before-ready.js] +[browser_inspector_gcli-inspect-command.js] +skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988. +[browser_inspector_highlighter-01.js] +[browser_inspector_highlighter-02.js] +[browser_inspector_highlighter-03.js] +[browser_inspector_highlighter-04.js] +[browser_inspector_highlighter-by-type.js] +[browser_inspector_highlighter-comments.js] +[browser_inspector_highlighter-csstransform_01.js] +[browser_inspector_highlighter-csstransform_02.js] +[browser_inspector_highlighter-hover_01.js] +[browser_inspector_highlighter-hover_02.js] +[browser_inspector_highlighter-hover_03.js] +[browser_inspector_highlighter-iframes.js] +[browser_inspector_highlighter-inline.js] +[browser_inspector_highlighter-options.js] +[browser_inspector_highlighter-rect_01.js] +[browser_inspector_highlighter-rect_02.js] +[browser_inspector_highlighter-selector_01.js] +[browser_inspector_highlighter-selector_02.js] +[browser_inspector_highlighter-zoom.js] +[browser_inspector_iframe-navigation.js] +[browser_inspector_infobar_01.js] +[browser_inspector_initialization.js] +[browser_inspector_inspect-object-element.js] +[browser_inspector_invalidate.js] +[browser_inspector_keyboard-shortcuts.js] +[browser_inspector_menu-01-sensitivity.js] +[browser_inspector_menu-02-copy-items.js] +[browser_inspector_menu-03-paste-items.js] +[browser_inspector_menu-04-other.js] +[browser_inspector_navigation.js] +[browser_inspector_picker-stop-on-destroy.js] +[browser_inspector_picker-stop-on-tool-change.js] +[browser_inspector_pseudoclass-lock.js] +[browser_inspector_pseudoclass-menu.js] +[browser_inspector_reload-01.js] +[browser_inspector_reload-02.js] +[browser_inspector_remove-iframe-during-load.js] +[browser_inspector_scrolling.js] +skip-if = e10s # Test synthesize scrolling events in content. Also, see bug 1035661. +[browser_inspector_search-01.js] +[browser_inspector_search-02.js] +[browser_inspector_search-03.js] +[browser_inspector_select-docshell.js] +[browser_inspector_select-last-selected.js] +[browser_inspector_search-navigation.js] +[browser_inspector_sidebarstate.js] +[browser_inspector_switch-to-inspector-on-pick.js] +[browser_inspector_update-on-navigation.js] diff --git a/toolkit/devtools/inspector/test/browser_inspector_breadcrumbs.js b/toolkit/devtools/inspector/test/browser_inspector_breadcrumbs.js new file mode 100644 index 000000000..73028ebd1 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_breadcrumbs.js @@ -0,0 +1,73 @@ +/* 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 that the breadcrumbs widget content is correct. + +const TEST_URI = TEST_URL_ROOT + "doc_inspector_breadcrumbs.html"; +const NODES = [ + {selector: "#i1111", result: "i1 i11 i111 i1111"}, + {selector: "#i22", result: "i2 i22 i221"}, + {selector: "#i2111", result: "i2 i21 i211 i2111"}, + {selector: "#i21", result: "i2 i21 i211 i2111"}, + {selector: "#i22211", result: "i2 i22 i222 i2221 i22211"}, + {selector: "#i22", result: "i2 i22 i222 i2221 i22211"}, +]; + +add_task(function*() { + let { inspector } = yield openInspectorForURL(TEST_URI); + let container = inspector.panelDoc.getElementById("inspector-breadcrumbs"); + + for (let node of NODES) { + info("Testing node " + node.selector); + + info("Selecting node and waiting for breadcrumbs to update"); + let breadcrumbsUpdated = inspector.once("breadcrumbs-updated"); + yield selectNode(node.selector, inspector); + yield breadcrumbsUpdated; + + info("Performing checks for node " + node.selector); + let buttonsLabelIds = node.result.split(" "); + + // html > body > … + is(container.childNodes.length, buttonsLabelIds.length + 2, + "Node " + node.selector + ": Items count"); + + for (let i = 2; i < container.childNodes.length; i++) { + let expectedId = "#" + buttonsLabelIds[i - 2]; + let button = container.childNodes[i]; + let labelId = button.querySelector(".breadcrumbs-widget-item-id"); + is(labelId.textContent, expectedId, + "Node #" + node.selector + ": button " + i + " matches"); + } + + let checkedButton = container.querySelector("button[checked]"); + let labelId = checkedButton.querySelector(".breadcrumbs-widget-item-id"); + let id = inspector.selection.nodeFront.id; + is(labelId.textContent, "#" + id, + "Node #" + node.selector + ": selection matches"); + } + + yield testPseudoElements(inspector, container); +}); + +function* testPseudoElements(inspector, container) { + info ("Checking for pseudo elements"); + + let pseudoParent = yield getNodeFront("#pseudo-container", inspector); + let children = yield inspector.walker.children(pseudoParent); + is (children.nodes.length, 2, "Pseudo children returned from walker"); + + let beforeElement = children.nodes[0]; + let breadcrumbsUpdated = inspector.once("breadcrumbs-updated"); + yield selectNode(beforeElement, inspector); + yield breadcrumbsUpdated; + is(container.childNodes[3].textContent, "::before", "::before shows up in breadcrumb"); + + let afterElement = children.nodes[1]; + breadcrumbsUpdated = inspector.once("breadcrumbs-updated"); + yield selectNode(afterElement, inspector); + yield breadcrumbsUpdated; + is(container.childNodes[3].textContent, "::after", "::before shows up in breadcrumb"); +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_breadcrumbs_highlight_hover.js b/toolkit/devtools/inspector/test/browser_inspector_breadcrumbs_highlight_hover.js new file mode 100644 index 000000000..255aa9d35 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_breadcrumbs_highlight_hover.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Test that hovering over nodes on the breadcrumb buttons in the inspector shows the highlighter over +// those nodes +add_task(function*() { + info("Loading the test document and opening the inspector"); + yield addTab("data:text/html;charset=utf-8,<h1>foo</h1><span>bar</span>"); + let {toolbox, inspector} = yield openInspector(); + info("Selecting the test node"); + yield selectNode("span", inspector); + let bcButtons = inspector.breadcrumbs["container"]; + + let onNodeHighlighted = toolbox.once("node-highlight"); + let button = bcButtons.childNodes[1]; + EventUtils.synthesizeMouseAtCenter(button, {type: "mousemove"}, button.ownerDocument.defaultView); + yield onNodeHighlighted; + + let isVisible = yield isHighlighting(toolbox); + ok(isVisible, "The highlighter is shown on a markup container hover"); + + let highlightedNode = yield getHighlitNode(toolbox); + is(highlightedNode, getNode("body"), "The highlighter highlights the right node"); + + onNodeHighlighted = toolbox.once("node-highlight"); + button = bcButtons.childNodes[2]; + EventUtils.synthesizeMouseAtCenter(button, {type: "mousemove"}, button.ownerDocument.defaultView); + yield onNodeHighlighted; + + isVisible = yield isHighlighting(toolbox); + ok(isVisible, "The highlighter is shown on a markup container hover"); + + highlightedNode = yield getHighlitNode(toolbox); + is(highlightedNode, getNode("span"), "The highlighter highlights the right node"); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_delete-selected-node-01.js b/toolkit/devtools/inspector/test/browser_inspector_delete-selected-node-01.js new file mode 100644 index 000000000..d506c3699 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_delete-selected-node-01.js @@ -0,0 +1,24 @@ +/* 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 to ensure inspector handles deletion of selected node correctly. + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_delete-selected-node-01.html"; + +add_task(function* () { + let {inspector} = yield openInspectorForURL(TEST_URL); + + let span = yield getNodeFrontInFrame("span", "iframe", inspector); + yield selectNode(span, inspector); + + info("Removing selected <span> element."); + let parentNode = span.parentNode(); + yield inspector.walker.removeNode(span); + + // Wait for the inspector to process the mutation + yield inspector.once("inspector-updated"); + is(inspector.selection.nodeFront, parentNode, + "Parent node of selected <span> got selected."); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_delete-selected-node-02.js b/toolkit/devtools/inspector/test/browser_inspector_delete-selected-node-02.js new file mode 100644 index 000000000..67b76ddff --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_delete-selected-node-02.js @@ -0,0 +1,93 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Test that when nodes are being deleted in the page, the current selection +// and therefore the markup view, css rule view, computed view, font view, +// box model view, and breadcrumbs, reset accordingly to show the right node + +const TEST_PAGE = TEST_URL_ROOT + + "doc_inspector_delete-selected-node-02.html"; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_PAGE); + + yield testManuallyDeleteSelectedNode(); + yield testAutomaticallyDeleteSelectedNode(); + yield testDeleteSelectedNodeContainerFrame(); + + function* testManuallyDeleteSelectedNode() { + info("Selecting a node, deleting it via context menu and checking that " + + "its parent node is selected and breadcrumbs are updated."); + + yield selectNode("#deleteManually", inspector); + + info("Getting the node container in the markup view."); + let container = yield getContainerForSelector("#deleteManually", inspector); + + info("Simulating right-click on the markup view container."); + EventUtils.synthesizeMouse(container.tagLine, 2, 2, + {type: "contextmenu", button: 2}, inspector.panelWin); + + info("Waiting for the context menu to open."); + yield once(inspector.panelDoc.getElementById("inspectorPopupSet"), "popupshown"); + + info("Clicking 'Delete Node' in the context menu."); + inspector.panelDoc.getElementById("node-menu-delete").click(); + + info("Waiting for inspector to update."); + yield inspector.once("inspector-updated"); + + info("Inspector updated, performing checks."); + yield assertNodeSelectedAndPanelsUpdated("#selectedAfterDelete", "li#selectedAfterDelete"); + } + + function* testAutomaticallyDeleteSelectedNode() { + info("Selecting a node, deleting it via javascript and checking that " + + "its parent node is selected and breadcrumbs are updated."); + + let div = yield getNodeFront("#deleteAutomatically", inspector); + yield selectNode(div, inspector); + + info("Deleting selected node via javascript."); + yield inspector.walker.removeNode(div); + + info("Waiting for inspector to update."); + yield inspector.once("inspector-updated"); + + info("Inspector updated, performing checks."); + yield assertNodeSelectedAndPanelsUpdated("#deleteChildren", "ul#deleteChildren"); + } + + function* testDeleteSelectedNodeContainerFrame() { + info("Selecting a node inside iframe, deleting the iframe via javascript " + + "and checking the parent node of the iframe is selected and " + + "breadcrumbs are updated."); + + info("Selecting an element inside iframe."); + let iframe = yield getNodeFront("#deleteIframe", inspector); + let div = yield getNodeFrontInFrame("#deleteInIframe", iframe, inspector); + yield selectNode(div, inspector); + + info("Deleting selected node via javascript."); + yield inspector.walker.removeNode(iframe); + + info("Waiting for inspector to update."); + yield inspector.once("inspector-updated"); + + info("Inspector updated, performing checks."); + yield assertNodeSelectedAndPanelsUpdated("body", "body"); + } + + function* assertNodeSelectedAndPanelsUpdated(selector, crumbLabel) { + let nodeFront = yield getNodeFront(selector, inspector); + is(inspector.selection.nodeFront, nodeFront, "The right node is selected"); + + let breadcrumbs = inspector.panelDoc.getElementById("inspector-breadcrumbs"); + is(breadcrumbs.querySelector("button[checked=true]").textContent, crumbLabel, + "The right breadcrumb is selected"); + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_delete-selected-node-03.js b/toolkit/devtools/inspector/test/browser_inspector_delete-selected-node-03.js new file mode 100644 index 000000000..0055b8946 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_delete-selected-node-03.js @@ -0,0 +1,27 @@ +/* vim: set 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 to ensure inspector can handle destruction of selected node inside an +// iframe. + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_delete-selected-node-01.html"; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URL); + + let iframe = yield getNodeFront("iframe", inspector); + let node = yield getNodeFrontInFrame("span", iframe, inspector); + yield selectNode(node, inspector); + + info("Removing iframe."); + yield inspector.walker.removeNode(iframe); + yield inspector.selection.once("detached-front"); + + let body = yield getNodeFront("body", inspector); + + is(inspector.selection.nodeFront, body, "Selection is now the body node"); + + yield inspector.once("inspector-updated"); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_destroy-after-navigation.js b/toolkit/devtools/inspector/test/browser_inspector_destroy-after-navigation.js new file mode 100644 index 000000000..5533f4915 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_destroy-after-navigation.js @@ -0,0 +1,29 @@ +/* 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"; + +// Testing that closing the inspector after navigating to a page doesn't fail. + +const URL_1 = "data:text/plain;charset=UTF-8,abcde"; +const URL_2 = "data:text/plain;charset=UTF-8,12345"; + +add_task(function* () { + let { toolbox } = yield openInspectorForURL(URL_1); + + info("Navigating to different URL."); + let navigated = toolbox.target.once("navigate"); + content.location = URL_2; + + info("Waiting for 'navigate' event from toolbox target."); + yield navigated; + + info("Destroying toolbox"); + try { + yield toolbox.destroy(); + ok(true, "Toolbox destroyed"); + } catch (e) { + ok(false, "An exception occured while destroying toolbox"); + console.error(e); + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_destroy-before-ready.js b/toolkit/devtools/inspector/test/browser_inspector_destroy-before-ready.js new file mode 100644 index 000000000..0659c2f23 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_destroy-before-ready.js @@ -0,0 +1,26 @@ +/* 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 that switching to the inspector panel and not waiting for it to be fully +// loaded doesn't fail the test with unhandled rejected promises. + +add_task(function*() { + // At least one assertion is needed to avoid failing the test, but really, + // what we're interested in is just having the test pass when switching to the + // inspector. + ok(true); + + yield addTab("data:text/html;charset=utf-8,test inspector destroy"); + + info("Open the toolbox on the debugger panel"); + let target = TargetFactory.forTab(gBrowser.selectedTab); + let toolbox = yield gDevTools.showToolbox(target, "jsdebugger"); + + info("Switch to the inspector panel and immediately end the test"); + let onInspectorSelected = toolbox.once("inspector-selected"); + toolbox.selectTool("inspector"); + let inspector = yield onInspectorSelected; +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_gcli-inspect-command.js b/toolkit/devtools/inspector/test/browser_inspector_gcli-inspect-command.js new file mode 100644 index 000000000..27693daa8 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_gcli-inspect-command.js @@ -0,0 +1,134 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Testing that the gcli 'inspect' command works as it should. + +const TEST_URI = TEST_URL_ROOT + "doc_inspector_gcli-inspect-command.html"; + +add_task(function* () { + return helpers.addTabWithToolbar(TEST_URI, function(options) { + return helpers.audit(options, [ + { + setup: "inspect", + check: { + input: 'inspect', + hints: ' <selector>', + markup: 'VVVVVVV', + status: 'ERROR', + args: { + selector: { + message: 'Value required for \'selector\'.' + }, + } + }, + }, + { + setup: "inspect h1", + check: { + input: 'inspect h1', + hints: '', + markup: 'VVVVVVVVII', + status: 'ERROR', + args: { + selector: { message: 'No matches' }, + } + }, + }, + { + setup: "inspect span", + check: { + input: 'inspect span', + hints: '', + markup: 'VVVVVVVVEEEE', + status: 'ERROR', + args: { + selector: { message: 'Too many matches (2)' }, + } + }, + }, + { + setup: "inspect div", + check: { + input: 'inspect div', + hints: '', + markup: 'VVVVVVVVVVV', + status: 'VALID', + args: { + selector: { message: '' }, + } + }, + }, + { + setup: "inspect .someclas", + check: { + input: 'inspect .someclas', + hints: '', + markup: 'VVVVVVVVIIIIIIIII', + status: 'ERROR', + args: { + selector: { message: 'No matches' }, + } + }, + }, + { + setup: "inspect .someclass", + check: { + input: 'inspect .someclass', + hints: '', + markup: 'VVVVVVVVVVVVVVVVVV', + status: 'VALID', + args: { + selector: { message: '' }, + } + }, + }, + { + setup: "inspect #someid", + check: { + input: 'inspect #someid', + hints: '', + markup: 'VVVVVVVVVVVVVVV', + status: 'VALID', + args: { + selector: { message: '' }, + } + }, + }, + { + setup: "inspect button[disabled]", + check: { + input: 'inspect button[disabled]', + hints: '', + markup: 'VVVVVVVVVVVVVVVVVVVVVVVV', + status: 'VALID', + args: { + selector: { message: '' }, + } + }, + }, + { + setup: "inspect p>strong", + check: { + input: 'inspect p>strong', + hints: '', + markup: 'VVVVVVVVVVVVVVVV', + status: 'VALID', + args: { + selector: { message: '' }, + } + }, + }, + { + setup: "inspect :root", + check: { + input: 'inspect :root', + hints: '', + markup: 'VVVVVVVVVVVVV', + status: 'VALID' + }, + }, + ]); // helpers.audit + }); // helpers.addTabWithToolbar +}); // add_task diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlight_after_transition.js b/toolkit/devtools/inspector/test/browser_inspector_highlight_after_transition.js new file mode 100644 index 000000000..49c18b258 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlight_after_transition.js @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const TEST_URI = "http://example.com/browser/browser/devtools/inspector/" + + "test/browser_inspector_highlight_after_transition.html"; + +// Test that the nodeinfobar is never displayed above the top or below the +// bottom of the content area. +add_task(function*() { + info("Loading the test document and opening the inspector"); + + yield addTab(TEST_URI); + + let {inspector} = yield openInspector(); + + yield checkDivHeight(inspector); +}); + +function* checkDivHeight(inspector) { + let div = getNode("div"); + + div.setAttribute("visible", "true"); + + yield once(div, "transitionend"); + yield selectAndHighlightNode(div, inspector); + + let height = div.getBoundingClientRect().height; + + is (height, 201, "div is the correct height"); +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-01.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-01.js new file mode 100644 index 000000000..90f4d0cfa --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-01.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Test that hovering over nodes in the markup-view shows the highlighter over +// those nodes +add_task(function*() { + info("Loading the test document and opening the inspector"); + yield addTab("data:text/html;charset=utf-8,<h1>foo</h1><span>bar</span>"); + let {toolbox, inspector} = yield openInspector(); + + let isVisible = yield isHighlighting(toolbox); + ok(!isVisible, "The highlighter is hidden by default"); + + info("Selecting the test node"); + yield selectNode("span", inspector); + let container = yield getContainerForSelector("h1", inspector); + + let onHighlighterReady = toolbox.once("highlighter-ready"); + EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousemove"}, + inspector.markup.doc.defaultView); + yield onHighlighterReady; + + isVisible = yield isHighlighting(toolbox); + ok(isVisible, "The highlighter is shown on a markup container hover"); + + let node = yield getHighlitNode(toolbox); + is(node, getNode("h1"), "The highlighter highlights the right node"); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-02.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-02.js new file mode 100644 index 000000000..fc34e3973 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-02.js @@ -0,0 +1,42 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Test that the highlighter is correctly displayed over a variety of elements + +const TEST_URI = TEST_URL_ROOT + "doc_inspector_highlighter.html"; + +add_task(function*() { + let {toolbox, inspector} = yield openInspectorForURL(TEST_URI); + + info("Selecting the simple, non-transformed DIV"); + yield selectAndHighlightNode("#simple-div", inspector); + + let isVisible = yield isHighlighting(toolbox); + ok(isVisible, "The highlighter is shown"); + let highlightedNode = yield getHighlitNode(toolbox); + is(highlightedNode, getNode("#simple-div"), + "The highlighter's outline corresponds to the simple div"); + yield isNodeCorrectlyHighlighted("#simple-div", toolbox, + "non-zoomed"); + + info("Selecting the rotated DIV"); + yield selectAndHighlightNode("#rotated-div", inspector); + + isVisible = yield isHighlighting(toolbox); + ok(isVisible, "The highlighter is shown"); + yield isNodeCorrectlyHighlighted("#rotated-div", toolbox, + "rotated"); + + info("Selecting the zero width height DIV"); + yield selectAndHighlightNode("#widthHeightZero-div", inspector); + + isVisible = yield isHighlighting(toolbox); + ok(isVisible, "The highlighter is shown"); + yield isNodeCorrectlyHighlighted("#widthHeightZero-div", toolbox, + "zero width height"); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-03.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-03.js new file mode 100644 index 000000000..b4503fea0 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-03.js @@ -0,0 +1,72 @@ +/* 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 that iframes are correctly highlighted. + +const IFRAME_SRC = "<style>" + + "body {" + + "margin:0;" + + "height:100%;" + + "background-color:red" + + "}" + + "</style><body>hello from iframe</body>"; + +const DOCUMENT_SRC = "<style>" + + "iframe {" + + "height:200px;" + + "border: 11px solid black;" + + "padding: 13px;" + + "}" + + "body,iframe {" + + "margin:0" + + "}" + + "</style>" + + "<body>" + + "<iframe src='data:text/html;charset=utf-8," + IFRAME_SRC + "'></iframe>" + + "</body>"; + +const TEST_URI = "data:text/html;charset=utf-8," + DOCUMENT_SRC; + +add_task(function* () { + let { inspector, toolbox } = yield openInspectorForURL(TEST_URI); + + let iframeNode = getNode("iframe"); + + info("Waiting for box mode to show."); + let body = yield getNodeFront("body", inspector); + yield toolbox.highlighter.showBoxModel(body); + + info("Waiting for element picker to become active."); + yield toolbox.highlighterUtils.startPicker(); + + info("Moving mouse over iframe padding."); + yield moveMouseOver(iframeNode, 1, 1); + + info("Performing checks"); + yield isNodeCorrectlyHighlighted("iframe", toolbox); + + info("Scrolling the document"); + iframeNode.style.marginBottom = content.innerHeight + "px"; + content.scrollBy(0, 40); + + let iframeBodyNode = iframeNode.contentDocument.body; + + info("Moving mouse over iframe body"); + yield moveMouseOver(iframeNode, 40, 40); + + let highlightedNode = yield getHighlitNode(toolbox); + is(highlightedNode, iframeBodyNode, "highlighter shows the right node"); + yield isNodeCorrectlyHighlighted("iframe || body", toolbox); + + info("Waiting for the element picker to deactivate."); + yield inspector.toolbox.highlighterUtils.stopPicker(); + + function moveMouseOver(node, x, y) { + info("Waiting for element " + node + " to be highlighted"); + executeInContent("Test:SynthesizeMouse", {x, y, options: {type: "mousemove"}}, + {node}, false); + return inspector.toolbox.once("picker-node-hovered"); + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-04.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-04.js new file mode 100644 index 000000000..24c7133c1 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-04.js @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Check that various highlighter elements exist. + +const TEST_URL = "data:text/html;charset=utf-8,<div>test</div>"; + +// IDs of all highlighter elements that we expect to find in the canvasFrame. +const ELEMENTS = ["box-model-root", + "box-model-elements", + "box-model-margin", + "box-model-border", + "box-model-padding", + "box-model-content", + "box-model-guide-top", + "box-model-guide-right", + "box-model-guide-bottom", + "box-model-guide-left", + "box-model-nodeinfobar-container", + "box-model-nodeinfobar-tagname", + "box-model-nodeinfobar-id", + "box-model-nodeinfobar-classes", + "box-model-nodeinfobar-pseudo-classes", + "box-model-nodeinfobar-dimensions"]; + +add_task(function*() { + let {inspector, toolbox} = yield openInspectorForURL(TEST_URL); + + info("Show the box-model highlighter"); + let divFront = yield getNodeFront("div", inspector); + yield toolbox.highlighter.showBoxModel(divFront); + + for (let id of ELEMENTS) { + let foundId = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "id"); + is(foundId, id, "Element " + id + " found"); + } + + info("Hide the box-model highlighter"); + yield toolbox.highlighter.hideBoxModel(); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-by-type.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-by-type.js new file mode 100644 index 000000000..62a29e851 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-by-type.js @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Check that custom highlighters can be retrieved by type and that they expose +// the expected API. + +const TEST_URL = "data:text/html;charset=utf-8,custom highlighters"; + +add_task(function*() { + let {inspector, toolbox} = yield openInspectorForURL(TEST_URL); + + yield onlyOneInstanceOfMainHighlighter(inspector); + yield manyInstancesOfCustomHighlighters(inspector); + yield showHideMethodsAreAvailable(inspector); + yield unknownHighlighterTypeShouldntBeAccepted(inspector); +}); + +function* onlyOneInstanceOfMainHighlighter({inspector}) { + info("Check that the inspector always sends back the same main highlighter"); + + let h1 = yield inspector.getHighlighter(false); + let h2 = yield inspector.getHighlighter(false); + is(h1, h2, "The same highlighter front was returned"); + + is(h1.typeName, "highlighter", "The right front type was returned"); +} + +function* manyInstancesOfCustomHighlighters({inspector}) { + let h1 = yield inspector.getHighlighterByType("BoxModelHighlighter"); + let h2 = yield inspector.getHighlighterByType("BoxModelHighlighter"); + ok(h1 !== h2, "getHighlighterByType returns new instances every time (1)"); + + let h3 = yield inspector.getHighlighterByType("CssTransformHighlighter"); + let h4 = yield inspector.getHighlighterByType("CssTransformHighlighter"); + ok(h3 !== h4, "getHighlighterByType returns new instances every time (2)"); + ok(h3 !== h1 && h3 !== h2, + "getHighlighterByType returns new instances every time (3)"); + ok(h4 !== h1 && h4 !== h2, + "getHighlighterByType returns new instances every time (4)"); + + yield h1.finalize(); + yield h2.finalize(); + yield h3.finalize(); + yield h4.finalize(); +} + +function* showHideMethodsAreAvailable({inspector}) { + let h1 = yield inspector.getHighlighterByType("BoxModelHighlighter"); + let h2 = yield inspector.getHighlighterByType("CssTransformHighlighter"); + + ok("show" in h1, "Show method is present on the front API"); + ok("show" in h2, "Show method is present on the front API"); + ok("hide" in h1, "Hide method is present on the front API"); + ok("hide" in h2, "Hide method is present on the front API"); + + yield h1.finalize(); + yield h2.finalize(); +} + +function* unknownHighlighterTypeShouldntBeAccepted({inspector}) { + let h = yield inspector.getHighlighterByType("whatever"); + ok(!h, "No highlighter was returned for the invalid type"); +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-comments.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-comments.js new file mode 100644 index 000000000..c518a52e4 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-comments.js @@ -0,0 +1,87 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +/////////////////// +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("false"); + +// Test that hovering over the markup-view's containers doesn't always show the +// highlighter, depending on the type of node hovered over. + +const TEST_PAGE = TEST_URL_ROOT + + "doc_inspector_highlighter-comments.html"; + +add_task(function* () { + let {toolbox, inspector} = yield openInspectorForURL(TEST_PAGE); + let markupView = inspector.markup; + yield selectNode("p", inspector); + + info("Hovering over #id1 and waiting for highlighter to appear."); + yield hoverElement("#id1"); + yield assertHighlighterShownOn("#id1"); + + info("Hovering over comment node and ensuring highlighter doesn't appear."); + yield hoverComment(); + yield assertHighlighterHidden(); + + info("Hovering over #id1 again and waiting for highlighter to appear."); + yield hoverElement("#id1"); + yield assertHighlighterShownOn("#id1"); + + info("Hovering over #id2 and waiting for highlighter to appear."); + yield hoverElement("#id2"); + yield assertHighlighterShownOn("#id2"); + + info("Hovering over <script> and ensuring highlighter doesn't appear."); + yield hoverElement("script"); + yield assertHighlighterHidden(); + + info("Hovering over #id3 and waiting for highlighter to appear."); + yield hoverElement("#id3"); + yield assertHighlighterShownOn("#id3"); + + info("Hovering over hidden #id4 and ensuring highlighter doesn't appear."); + yield hoverElement("#id4"); + yield assertHighlighterHidden(); + + function hoverContainer(container) { + let promise = inspector.toolbox.once("node-highlight"); + EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"}, + markupView.doc.defaultView); + + return promise; + } + + function* hoverElement(selector) { + info("Hovering node " + selector + " in the markup view"); + let container = yield getContainerForSelector(selector, inspector); + return hoverContainer(container); + } + + function hoverComment() { + info("Hovering the comment node in the markup view"); + for (let [node, container] of markupView._containers) { + if (node.nodeType === Ci.nsIDOMNode.COMMENT_NODE) { + return hoverContainer(container); + } + } + } + + function* assertHighlighterShownOn(selector) { + let node = getNode(selector); + let highlightNode = yield getHighlitNode(toolbox); + is(node, highlightNode, "Highlighter is shown on the right node: " + selector); + } + + function* assertHighlighterHidden() { + let isVisible = yield isHighlighting(toolbox); + ok(!isVisible, "Highlighter is hidden"); + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-csstransform_01.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-csstransform_01.js new file mode 100644 index 000000000..ce6eaa423 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-csstransform_01.js @@ -0,0 +1,140 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Test the creation of the SVG highlighter elements of the css transform +// highlighter. + +const TEST_URL = "data:text/html;charset=utf-8," + + "<div id='transformed' style='border:1px solid red;width:100px;height:100px;transform:skew(13deg);'></div>" + + "<div id='untransformed' style='border:1px solid blue;width:100px;height:100px;'></div>" + + "<span id='inline' style='transform:rotate(90deg);'>this is an inline transformed element</span>"; + +add_task(function*() { + let {inspector, toolbox} = yield openInspectorForURL(TEST_URL); + let front = inspector.inspector; + + let highlighter = yield front.getHighlighterByType("CssTransformHighlighter"); + + yield isHiddenByDefault(highlighter, inspector); + yield has2PolygonsAnd4Lines(highlighter, inspector); + yield isNotShownForUntransformed(highlighter, inspector); + yield isNotShownForInline(highlighter, inspector); + yield isVisibleWhenShown(highlighter, inspector); + yield linesLinkThePolygons(highlighter, inspector); + + yield highlighter.finalize(); +}); + +function* isHiddenByDefault(highlighterFront, inspector) { + info("Checking that the highlighter is hidden by default"); + + let hidden = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-elements", "hidden"); + ok(hidden, "The highlighter is hidden by default"); +} + +function* has2PolygonsAnd4Lines(highlighterFront, inspector) { + info("Checking that the highlighter is made up of 4 lines and 2 polygons"); + + let value = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-untransformed", "class"); + is(value, "css-transform-untransformed", "The untransformed polygon exists"); + + value = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-transformed", "class"); + is(value, "css-transform-transformed", "The transformed polygon exists"); + + for (let nb of ["1", "2", "3", "4"]) { + value = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-line" + nb, "class"); + is(value, "css-transform-line", "The line " + nb + " exists"); + } +} + +function* isNotShownForUntransformed(highlighterFront, inspector) { + info("Asking to show the highlighter on the untransformed test node"); + + let node = yield getNodeFront("#untransformed", inspector); + yield highlighterFront.show(node); + + let hidden = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-elements", "hidden"); + ok(hidden, "The highlighter is still hidden"); +} + +function* isNotShownForInline(highlighterFront, inspector) { + info("Asking to show the highlighter on the inline test node"); + + let node = yield getNodeFront("#inline", inspector); + yield highlighterFront.show(node); + + let hidden = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-elements", "hidden"); + ok(hidden, "The highlighter is still hidden"); +} + +function* isVisibleWhenShown(highlighterFront, inspector) { + info("Asking to show the highlighter on the test node"); + + let node = yield getNodeFront("#transformed", inspector); + yield highlighterFront.show(node); + + let hidden = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-elements", "hidden"); + ok(!hidden, "The highlighter is visible"); + + info("Hiding the highlighter"); + yield highlighterFront.hide(); + + hidden = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-elements", "hidden"); + ok(hidden, "The highlighter is hidden"); +} + +function* linesLinkThePolygons(highlighterFront, inspector) { + info("Showing the highlighter on the transformed node"); + + let node = yield getNodeFront("#transformed", inspector); + yield highlighterFront.show(node); + + info("Checking that the 4 lines do link the 2 shape's corners"); + + let lines = []; + for (let nb of ["1", "2", "3", "4"]) { + let x1 = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-line" + nb, "x1"); + let y1 = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-line" + nb, "y1"); + let x2 = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-line" + nb, "x2"); + let y2 = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-line" + nb, "y2"); + lines.push({x1, y1, x2, y2}); + } + + let points1 = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-untransformed", "points"); + points1 = points1.split(" "); + + let points2 = yield getHighlighterNodeAttribute(highlighterFront, + "css-transform-transformed", "points"); + points2 = points2.split(" "); + + for (let i = 0; i < lines.length; i++) { + info("Checking line nb " + i); + let line = lines[i]; + + let p1 = points1[i].split(","); + is(p1[0], line.x1, "line " + i + "'s first point matches the untransformed x coordinate"); + is(p1[1], line.y1, "line " + i + "'s first point matches the untransformed y coordinate"); + + let p2 = points2[i].split(","); + is(p2[0], line.x2, "line " + i + "'s first point matches the transformed x coordinate"); + is(p2[1], line.y2, "line " + i + "'s first point matches the transformed y coordinate"); + } + + yield highlighterFront.hide(); +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-csstransform_02.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-csstransform_02.js new file mode 100644 index 000000000..976cc448d --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-csstransform_02.js @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* +Bug 1014547 - CSS transforms highlighter +Test that the highlighter elements created have the right size and coordinates. + +Note that instead of hard-coding values here, the assertions are made by +comparing with the result of LayoutHelpers.getAdjustedQuads. + +There's a separate test for checking that getAdjustedQuads actually returns +sensible values +(browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.js), +so the present test doesn't care about that, it just verifies that the css +transform highlighter applies those values correctly to the SVG elements +*/ + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_highlighter_csstransform.html"; + +add_task(function*() { + let {inspector, toolbox} = yield openInspectorForURL(TEST_URL); + let front = inspector.inspector; + + let highlighter = yield front.getHighlighterByType("CssTransformHighlighter"); + + let nodeFront = yield getNodeFront("#test-node", inspector); + + info("Displaying the transform highlighter on test node"); + yield highlighter.show(nodeFront); + + let {data} = yield executeInContent("Test:GetAllAdjustedQuads", { + selector: "#test-node" + }); + let [expected] = data.border; + + let points = yield getHighlighterNodeAttribute(highlighter, + "css-transform-transformed", "points"); + let polygonPoints = points.split(" ").map(p => { + return { + x: +p.substring(0, p.indexOf(",")), + y: +p.substring(p.indexOf(",")+1) + }; + }); + + for (let i = 1; i < 5; i ++) { + is(polygonPoints[i - 1].x, expected["p" + i].x, + "p" + i + " x coordinate is correct"); + is(polygonPoints[i - 1].y, expected["p" + i].y, + "p" + i + " y coordinate is correct"); + } + + info("Hiding the transform highlighter"); + yield highlighter.hide(); + yield highlighter.finalize(); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-hover_01.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-hover_01.js new file mode 100644 index 000000000..abb3ef924 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-hover_01.js @@ -0,0 +1,42 @@ +/* vim: set 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 that when first hovering over a node and immediately after selecting it +// by clicking on it leaves the highlighter visible for as long as the mouse is +// over the node + +const TEST_URL = "data:text/html;charset=utf-8,<p>It's going to be legen....</p>"; + +add_task(function*() { + let {toolbox, inspector} = yield openInspectorForURL(TEST_URL); + let p = getNode("p"); + + info("hovering over the <p> line in the markup-view"); + yield hoverContainer("p", inspector); + let isVisible = yield isHighlighting(toolbox); + ok(isVisible, "the highlighter is still visible"); + + info("selecting the <p> line by clicking in the markup-view"); + yield clickContainer("p", inspector); + + p.textContent = "wait for it ...."; + info("wait and see if the highlighter stays visible even after the node was selected"); + yield waitForTheBrieflyShowBoxModelTimeout(); + + let updated = inspector.once("inspector-updated"); + p.textContent = "dary!!!!"; + isVisible = yield isHighlighting(toolbox); + ok(isVisible, "the highlighter is still visible"); + yield updated; +}); + +function waitForTheBrieflyShowBoxModelTimeout() { + let deferred = promise.defer(); + // Note that the current timeout is 1 sec and is neither configurable nor + // exported anywhere we can access, so hard-coding the timeout + content.setTimeout(deferred.resolve, 1500); + return deferred.promise; +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-hover_02.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-hover_02.js new file mode 100644 index 000000000..5155eab00 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-hover_02.js @@ -0,0 +1,36 @@ +/* vim: set 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 that when after an element is selected and highlighted on hover, if the +// mouse leaves the markup-view and comes back again on the same element, that +// the highlighter is shown again on the node + +const TEST_URL = "data:text/html;charset=utf-8,<p>Select me!</p>"; + +add_task(function*() { + let {toolbox, inspector} = yield openInspectorForURL(TEST_URL); + + info("hover over the <p> line in the markup-view so that it's the currently hovered node"); + yield hoverContainer("p", inspector); + + info("select the <p> markup-container line by clicking"); + yield clickContainer("p", inspector); + let isVisible = yield isHighlighting(toolbox); + ok(isVisible, "the highlighter is shown"); + + info("listen to the highlighter's hidden event"); + let onHidden = waitForHighlighterEvent("hidden", toolbox.highlighter); + info("mouse-leave the markup-view"); + yield mouseLeaveMarkupView(inspector); + yield onHidden; + isVisible = yield isHighlighting(toolbox); + ok(!isVisible, "the highlighter is hidden after mouseleave"); + + info("hover over the <p> line again, which is still selected"); + yield hoverContainer("p", inspector); + isVisible = yield isHighlighting(toolbox); + ok(isVisible, "the highlighter is visible again"); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-hover_03.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-hover_03.js new file mode 100644 index 000000000..3c359f6c9 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-hover_03.js @@ -0,0 +1,55 @@ +/* vim: set 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 that once a node has been hovered over and marked as such, if it is +// navigated away using the keyboard, the highlighter moves to the new node, and +// if it is then navigated back to, it is briefly highlighted again + +const TEST_PAGE = "data:text/html;charset=utf-8," + + "<p id=\"one\">one</p><p id=\"two\">two</p>"; + +add_task(function*() { + let {inspector} = yield openInspectorForURL(TEST_PAGE); + + info("Making sure the markup-view frame is focused"); + inspector.markup._frame.focus(); + + // Mock the highlighter to easily track which node gets highlighted. + // We don't need to test here that the highlighter is actually visible, we + // just care about whether the markup-view asks it to be shown + let highlightedNode = null; + inspector.toolbox._highlighter.showBoxModel = function(nodeFront) { + highlightedNode = nodeFront; + return promise.resolve(); + }; + inspector.toolbox._highlighter.hideBoxModel = function() { + return promise.resolve(); + }; + + function* isHighlighting(selector, desc) { + let nodeFront = yield getNodeFront(selector, inspector); + is(highlightedNode, nodeFront, desc); + } + + info("Hover over <p#one> line in the markup-view"); + yield hoverContainer("#one", inspector); + yield isHighlighting("#one", "<p#one> is highlighted"); + + info("Navigate to <p#two> with the keyboard"); + let onUpdated = inspector.once("inspector-updated"); + EventUtils.synthesizeKey("VK_DOWN", {}); + yield onUpdated; + onUpdated = inspector.once("inspector-updated"); + EventUtils.synthesizeKey("VK_DOWN", {}); + yield onUpdated; + yield isHighlighting("#two", "<p#two> is highlighted"); + + info("Navigate back to <p#one> with the keyboard"); + onUpdated = inspector.once("inspector-updated"); + EventUtils.synthesizeKey("VK_UP", {}); + yield onUpdated; + yield isHighlighting("#one", "<p#one> is highlighted again"); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-iframes.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-iframes.js new file mode 100644 index 000000000..b682707a0 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-iframes.js @@ -0,0 +1,63 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Testing that moving the mouse over the document with the element picker +// started highlights nodes + +const NESTED_FRAME_SRC = "data:text/html;charset=utf-8," + + "nested iframe<div>nested div</div>"; + +const OUTER_FRAME_SRC = "data:text/html;charset=utf-8," + + "little frame<div>little div</div>" + + "<iframe src='" + NESTED_FRAME_SRC + "' />"; + +const TEST_URI = "data:text/html;charset=utf-8," + + "iframe tests for inspector" + + "<iframe src=\"" + OUTER_FRAME_SRC + "\" />"; + +add_task(function*() { + let {toolbox, inspector} = yield openInspectorForURL(TEST_URI); + let outerFrame = getNode("iframe"); + let outerFrameDiv = getNode("div", { document: outerFrame.contentDocument}); + let innerFrame = getNode("iframe", { document: outerFrame.contentDocument}); + let innerFrameDiv = getNode("div", { document: innerFrame.contentDocument}); + + info("Waiting for element picker to activate."); + yield inspector.toolbox.highlighterUtils.startPicker(); + + info("Moving mouse over outerFrameDiv"); + yield moveMouseOver(outerFrameDiv); + let highlightedNode = yield getHighlitNode(toolbox); + is(highlightedNode, outerFrameDiv, "outerFrameDiv is highlighted."); + + info("Moving mouse over innerFrameDiv"); + yield moveMouseOver(innerFrameDiv); + highlightedNode = yield getHighlitNode(toolbox); + is(highlightedNode, innerFrameDiv, "innerFrameDiv is highlighted."); + + info("Selecting root node"); + yield selectNode(inspector.walker.rootNode, inspector); + + info("Selecting an element from the nested iframe directly"); + let innerFrameFront = yield getNodeFrontInFrame("iframe", "iframe", inspector); + let innerFrameDivFront = yield getNodeFrontInFrame("div", innerFrameFront, inspector); + yield selectNode(innerFrameDivFront, inspector); + + is(inspector.breadcrumbs.nodeHierarchy.length, 9, "Breadcrumbs have 9 items."); + + info("Waiting for element picker to deactivate."); + yield inspector.toolbox.highlighterUtils.stopPicker(); + + function moveMouseOver(node) { + info("Waiting for element " + node + " to be highlighted"); + executeInContent("Test:SynthesizeMouse", { + options: {type: "mousemove"}, + center: true + }, {node}, false); + return inspector.toolbox.once("picker-node-hovered"); + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-inline.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-inline.js new file mode 100644 index 000000000..aa1f0ab31 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-inline.js @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Test that highlighting various inline boxes displays the right number of +// polygons in the page. + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_highlighter_inline.html"; +const TEST_DATA = [ + "body", + "h1", + "h2", + "h2 em", + "p", + "p span", + // The following test case used to fail. See bug 1139925. + "[dir=rtl] > span" +]; + +add_task(function*() { + info("Loading the test document and opening the inspector"); + let {toolbox, inspector} = yield openInspectorForURL(TEST_URL); + + for (let selector of TEST_DATA) { + info("Selecting and highlighting node " + selector); + yield selectAndHighlightNode(selector, inspector); + + info("Get all quads for this node"); + let {data} = yield executeInContent("Test:GetAllAdjustedQuads", {selector}); + + info("Iterate over the box-model regions and verify that the highlighter is correct"); + for (let region of ["margin", "border", "padding", "content"]) { + let {points} = yield getHighlighterRegionPath(region, toolbox.highlighter); + is(points.length, data[region].length, + "The highlighter's " + region + " path defines the correct number of boxes"); + } + + info("Verify that the guides define a rectangle that contains all content boxes"); + + let expectedContentRect = { + p1: {x: Infinity, y: Infinity}, + p2: {x: -Infinity, y: Infinity}, + p3: {x: -Infinity, y: -Infinity}, + p4: {x: Infinity, y: -Infinity} + }; + for (let {p1, p2, p3, p4} of data.content) { + expectedContentRect.p1.x = Math.min(expectedContentRect.p1.x, p1.x); + expectedContentRect.p1.y = Math.min(expectedContentRect.p1.y, p1.y); + expectedContentRect.p2.x = Math.max(expectedContentRect.p2.x, p2.x); + expectedContentRect.p2.y = Math.min(expectedContentRect.p2.y, p2.y); + expectedContentRect.p3.x = Math.max(expectedContentRect.p3.x, p3.x); + expectedContentRect.p3.y = Math.max(expectedContentRect.p3.y, p3.y); + expectedContentRect.p4.x = Math.min(expectedContentRect.p4.x, p4.x); + expectedContentRect.p4.y = Math.max(expectedContentRect.p4.y, p4.y); + } + + let contentRect = yield getGuidesRectangle(toolbox); + + for (let point of ["p1", "p2", "p3", "p4"]) { + is((contentRect[point].x), + (expectedContentRect[point].x), + "x coordinate of point " + point + + " of the content rectangle defined by the outer guides is correct"); + is((contentRect[point].y), + (expectedContentRect[point].y), + "y coordinate of point " + point + + " of the content rectangle defined by the outer guides is correct"); + } + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-options.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-options.js new file mode 100644 index 000000000..7a46e1a02 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-options.js @@ -0,0 +1,165 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Check that the box-model highlighter supports configuration options + +const TEST_URL = "data:text/html;charset=utf-8," + + "<body style='padding:2em;'>" + + "<div style='width:100px;height:100px;padding:2em;border:.5em solid black;margin:1em;'>test</div>" + + "</body>"; + +// Test data format: +// - desc: a string that will be output to the console. +// - options: json object to be passed as options to the highlighter. +// - checkHighlighter: a generator (async) function that should check the +// highlighter is correct. +const TEST_DATA = [ + { + desc: "Guides and infobar should be shown by default", + options: {}, + checkHighlighter: function*(toolbox) { + let hidden = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-nodeinfobar-container", "hidden"); + ok(!hidden, "Node infobar is visible"); + + hidden = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-elements", "hidden"); + ok(!hidden, "SVG container is visible"); + + for (let side of ["top", "right", "bottom", "left"]) { + hidden = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-guide-" + side, "hidden"); + ok(!hidden, side + " guide is visible"); + } + } + }, + { + desc: "All regions should be shown by default", + options: {}, + checkHighlighter: function*(toolbox) { + for (let region of ["margin", "border", "padding", "content"]) { + let {d} = yield getHighlighterRegionPath(region, toolbox.highlighter); + ok(d, "Region " + region + " has set coordinates"); + } + } + }, + { + desc: "Guides can be hidden", + options: {hideGuides: true}, + checkHighlighter: function*(toolbox) { + for (let side of ["top", "right", "bottom", "left"]) { + let hidden = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-guide-" + side, "hidden"); + is(hidden, "true", side + " guide has been hidden"); + } + } + }, + { + desc: "Infobar can be hidden", + options: {hideInfoBar: true}, + checkHighlighter: function*(toolbox) { + let hidden = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-nodeinfobar-container", "hidden"); + is(hidden, "true", "nodeinfobar has been hidden"); + } + }, + { + desc: "One region only can be shown (1)", + options: {showOnly: "content"}, + checkHighlighter: function*(toolbox) { + let {d} = yield getHighlighterRegionPath("margin", toolbox.highlighter); + ok(!d, "margin region is hidden"); + + ({d}) = yield getHighlighterRegionPath("border", toolbox.highlighter); + ok(!d, "border region is hidden"); + + ({d}) = yield getHighlighterRegionPath("padding", toolbox.highlighter); + ok(!d, "padding region is hidden"); + + ({d}) = yield getHighlighterRegionPath("content", toolbox.highlighter); + ok(d, "content region is shown"); + } + }, + { + desc: "One region only can be shown (2)", + options: {showOnly: "margin"}, + checkHighlighter: function*(toolbox) { + let {d} = yield getHighlighterRegionPath("margin", toolbox.highlighter); + ok(d, "margin region is shown"); + + ({d}) = yield getHighlighterRegionPath("border", toolbox.highlighter); + ok(!d, "border region is hidden"); + + ({d}) = yield getHighlighterRegionPath("padding", toolbox.highlighter); + ok(!d, "padding region is hidden"); + + ({d}) = yield getHighlighterRegionPath("content", toolbox.highlighter); + ok(!d, "content region is hidden"); + } + }, + { + desc: "Guides can be drawn around a given region (1)", + options: {region: "padding"}, + checkHighlighter: function*(toolbox) { + let topY1 = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-guide-top", "y1"); + let rightX1 = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-guide-right", "x1"); + let bottomY1 = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-guide-bottom", "y1"); + let leftX1 = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-guide-left", "x1"); + + let {points} = yield getHighlighterRegionPath("padding", toolbox.highlighter); + points = points[0]; + + is(Math.ceil(topY1), points[0][1], "Top guide's y1 is correct"); + is(Math.floor(rightX1), points[1][0], "Right guide's x1 is correct"); + is(Math.floor(bottomY1), points[2][1], "Bottom guide's y1 is correct"); + is(Math.ceil(leftX1), points[3][0], "Left guide's x1 is correct"); + } + }, + { + desc: "Guides can be drawn around a given region (2)", + options: {region: "margin"}, + checkHighlighter: function*(toolbox) { + let topY1 = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-guide-top", "y1"); + let rightX1 = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-guide-right", "x1"); + let bottomY1 = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-guide-bottom", "y1"); + let leftX1 = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-guide-left", "x1"); + + let {points} = yield getHighlighterRegionPath("margin", toolbox.highlighter); + points = points[0]; + + is(Math.ceil(topY1), points[0][1], "Top guide's y1 is correct"); + is(Math.floor(rightX1), points[1][0], "Right guide's x1 is correct"); + is(Math.floor(bottomY1), points[2][1], "Bottom guide's y1 is correct"); + is(Math.ceil(leftX1), points[3][0], "Left guide's x1 is correct"); + } + } +]; + +add_task(function*() { + let {inspector, toolbox} = yield openInspectorForURL(TEST_URL); + + let divFront = yield getNodeFront("div", inspector); + + for (let {desc, options, checkHighlighter} of TEST_DATA) { + info("Running test: " + desc); + + info("Show the box-model highlighter with options " + options); + yield toolbox.highlighter.showBoxModel(divFront, options); + + yield checkHighlighter(toolbox); + + info("Hide the box-model highlighter"); + yield toolbox.highlighter.hideBoxModel(); + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-rect_01.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-rect_01.js new file mode 100644 index 000000000..005627dc5 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-rect_01.js @@ -0,0 +1,108 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Test that the custom rect highlighter provides the right API, ensures that +// the input is valid and that it does create a box with the right dimensions, +// at the right position. + +const TEST_URL = "data:text/html;charset=utf-8,Rect Highlighter Test"; + +add_task(function*() { + let {inspector, toolbox} = yield openInspectorForURL(TEST_URL); + let front = inspector.inspector; + let highlighter = yield front.getHighlighterByType("RectHighlighter"); + let body = yield getNodeFront("body", inspector); + + info("Make sure the highlighter returned is correct"); + + ok(highlighter, "The RectHighlighter custom type was created"); + is(highlighter.typeName, "customhighlighter", + "The RectHighlighter has the right type"); + ok(highlighter.show && highlighter.hide, + "The RectHighlighter has the expected show/hide methods"); + + info("Check that the highlighter is hidden by default"); + + let hidden = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "hidden"); + is(hidden, "true", "The highlighter is hidden by default"); + + info("Check that nothing is shown if no rect is passed"); + + yield highlighter.show(body); + hidden = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "hidden"); + is(hidden, "true", "The highlighter is hidden when no rect is passed"); + + info("Check that nothing is shown if rect is incomplete or invalid"); + + yield highlighter.show(body, { + rect: {x: 0, y: 0} + }); + hidden = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "hidden"); + is(hidden, "true", "The highlighter is hidden when the rect is incomplete"); + + yield highlighter.show(body, { + rect: {x: 0, y: 0, width: -Infinity, height: 0} + }); + hidden = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "hidden"); + is(hidden, "true", "The highlighter is hidden when the rect is invalid (1)"); + + yield highlighter.show(body, { + rect: {x: 0, y: 0, width: 5, height: -45} + }); + hidden = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "hidden"); + is(hidden, "true", "The highlighter is hidden when the rect is invalid (2)"); + + yield highlighter.show(body, { + rect: {x: "test", y: 0, width: 5, height: 5} + }); + hidden = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "hidden"); + is(hidden, "true", "The highlighter is hidden when the rect is invalid (3)"); + + info("Check that the highlighter is displayed when valid options are passed"); + + yield highlighter.show(body, { + rect: {x: 5, y: 5, width: 50, height: 50} + }); + hidden = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "hidden"); + ok(!hidden, "The highlighter is displayed"); + let style = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "style"); + is(style, "left:5px;top:5px;width:50px;height:50px;", + "The highlighter is positioned correctly"); + + info("Check that the highlighter can be displayed at x=0 y=0"); + + yield highlighter.show(body, { + rect: {x: 0, y: 0, width: 50, height: 50} + }); + hidden = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "hidden"); + ok(!hidden, "The highlighter is displayed when x=0 and y=0"); + style = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "style"); + is(style, "left:0px;top:0px;width:50px;height:50px;", + "The highlighter is positioned correctly"); + + info("Check that the highlighter is hidden when dimensions are 0"); + + yield highlighter.show(body, { + rect: {x: 0, y: 0, width: 0, height: 0} + }); + hidden = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "hidden"); + is(hidden, "true", "The highlighter is hidden width and height are 0"); + + info("Check that a fill color can be passed"); + + yield highlighter.show(body, { + rect: {x: 100, y: 200, width: 500, height: 200}, + fill: "red" + }); + hidden = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "hidden"); + ok(!hidden, "The highlighter is displayed"); + style = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "style"); + is(style, "left:100px;top:200px;width:500px;height:200px;background:red;", + "The highlighter has the right background color"); + + yield highlighter.hide(); + yield highlighter.finalize(); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-rect_02.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-rect_02.js new file mode 100644 index 000000000..62579ec78 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-rect_02.js @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Test that the custom rect highlighter positions the rectangle relative to the +// viewport of the context node we pass to it. + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_highlighter_rect.html"; + +add_task(function*() { + let {inspector, toolbox} = yield openInspectorForURL(TEST_URL); + let front = inspector.inspector; + let highlighter = yield front.getHighlighterByType("RectHighlighter"); + + info("Showing the rect highlighter in the context of the iframe"); + + // Get the reference to a context node inside the iframe + let childBody = yield getNodeFrontInFrame("body", "iframe", inspector); + yield highlighter.show(childBody, { + rect: {x: 50, y: 50, width: 100, height: 100} + }); + + let style = yield getHighlighterNodeAttribute(highlighter, "highlighted-rect", "style"); + + // The parent body has margin=50px and border=10px + // The parent iframe also has margin=50px and border=10px + // = 50 + 10 + 50 + 10 = 120px + // The rect is aat x=50 and y=50, so left and top should be 170px + is(style, "left:170px;top:170px;width:100px;height:100px;", + "The highlighter is correctly positioned"); + + yield highlighter.hide(); + yield highlighter.finalize(); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-selector_01.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-selector_01.js new file mode 100644 index 000000000..3fc6ab31d --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-selector_01.js @@ -0,0 +1,63 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Test that the custom selector highlighter creates as many box-model +// highlighters as there are nodes that match the given selector + +const TEST_URL = "data:text/html;charset=utf-8," + + "<div id='test-node'>test node</div>" + + "<ul>" + + " <li class='item'>item</li>" + + " <li class='item'>item</li>" + + " <li class='item'>item</li>" + + " <li class='item'>item</li>" + + " <li class='item'>item</li>" + + "</ul>"; + +const TEST_DATA = [{ + selector: "#test-node", + containerCount: 1 +}, { + selector: null, + containerCount: 0, +}, { + selector: undefined, + containerCount: 0, +}, { + selector: ".invalid-class", + containerCount: 0 +}, { + selector: ".item", + containerCount: 5 +}, { + selector: "#test-node, ul, .item", + containerCount: 7 +}]; + +add_task(function*() { + let {inspector, toolbox} = yield openInspectorForURL(TEST_URL); + let front = inspector.inspector; + let highlighter = yield front.getHighlighterByType("SelectorHighlighter"); + + let contextNode = yield getNodeFront("body", inspector); + + for (let {selector, containerCount} of TEST_DATA) { + info("Showing the highlighter on " + selector + ". Expecting " + + containerCount + " highlighter containers"); + + yield highlighter.show(contextNode, {selector}); + + let {actorID, connPrefix} = getHighlighterActorID(highlighter); + let {data: nb} = yield executeInContent("Test:GetSelectorHighlighterBoxNb", + {actorID, connPrefix}); + ok(nb !== null, "The number of highlighters was retrieved"); + + is(nb, containerCount, "The correct number of highlighers were created"); + yield highlighter.hide(); + } + + yield highlighter.finalize(); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-selector_02.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-selector_02.js new file mode 100644 index 000000000..b6903cce3 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-selector_02.js @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Test that the custom selector highlighter creates highlighters for nodes in +// the right frame. + +const FRAME_SRC = "data:text/html;charset=utf-8," + + "<div class=sub-level-node></div>"; + +const TEST_URL = "data:text/html;charset=utf-8," + + "<div class=root-level-node></div>" + + "<iframe src=\"" + FRAME_SRC + "\" />"; + +const TEST_DATA = [{ + selector: ".root-level-node", + containerCount: 1 +}, { + selector: ".sub-level-node", + containerCount: 0 +}, { + inIframe: true, + selector: ".root-level-node", + containerCount: 0 +}, { + inIframe: true, + selector: ".sub-level-node", + containerCount: 1 +}]; + +add_task(function*() { + let {inspector, toolbox} = yield openInspectorForURL(TEST_URL); + let front = inspector.inspector; + let highlighter = yield front.getHighlighterByType("SelectorHighlighter"); + + for (let {inIframe, selector, containerCount} of TEST_DATA) { + info("Showing the highlighter on " + selector + ". Expecting " + + containerCount + " highlighter containers"); + + let contextNode; + if (inIframe) { + contextNode = yield getNodeFrontInFrame("body", "iframe", inspector); + } else { + contextNode = yield getNodeFront("body", inspector); + } + + yield highlighter.show(contextNode, {selector}); + + let {actorID, connPrefix} = getHighlighterActorID(highlighter); + let {data: nb} = yield executeInContent("Test:GetSelectorHighlighterBoxNb", + {actorID, connPrefix}); + ok(nb !== null, "The number of highlighters was retrieved"); + + is(nb, containerCount, "The correct number of highlighers were created"); + yield highlighter.hide(); + } + + yield highlighter.finalize(); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_highlighter-zoom.js b/toolkit/devtools/inspector/test/browser_inspector_highlighter-zoom.js new file mode 100644 index 000000000..8bc4a742b --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_highlighter-zoom.js @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Test that the highlighter stays correctly positioned and has the right aspect +// ratio even when the page is zoomed in or out. + +const TEST_URL = "data:text/html;charset=utf-8,<div>zoom me</div>"; + +// TEST_LEVELS entries should contain the following properties: +// - level: the zoom level to test +// - expected: the style attribute value to check for on the root highlighter +// element. +const TEST_LEVELS = [{ + level: 2, + expected: "position:absolute;transform-origin:top left;transform:scale(0.5);width:200%;height:200%;" +}, { + level: 1, + expected: "position:absolute;width:100%;height:100%;" +}, { + level: .5, + expected: "position:absolute;transform-origin:top left;transform:scale(2);width:50%;height:50%;" +}]; + +add_task(function*() { + let {inspector, toolbox} = yield openInspectorForURL(TEST_URL); + + info("Highlighting the test node"); + + yield hoverElement("div", inspector); + let isVisible = yield isHighlighting(toolbox); + ok(isVisible, "The highlighter is visible"); + + for (let {level, expected} of TEST_LEVELS) { + info("Zoom to level " + level + " and check that the highlighter is correct"); + + let {actorID, connPrefix} = getHighlighterActorID(toolbox.highlighter); + yield zoomPageTo(level, actorID, connPrefix); + isVisible = yield isHighlighting(toolbox); + ok(isVisible, "The highlighter is still visible at zoom level " + level); + + yield isNodeCorrectlyHighlighted("div", toolbox); + + info("Check that the highlighter root wrapper node was scaled down"); + + let style = yield getRootNodeStyle(toolbox); + is(style, expected, "The style attribute of the root element is correct"); + } +}); + +function* hoverElement(selector, inspector) { + info("Hovering node " + selector + " in the markup view"); + let container = yield getContainerForSelector(selector, inspector); + yield hoverContainer(container, inspector); +} + +function* hoverContainer(container, inspector) { + let onHighlight = inspector.toolbox.once("node-highlight"); + EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"}, + inspector.markup.doc.defaultView); + yield onHighlight; +} + +function* getRootNodeStyle(toolbox) { + let value = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-root", "style"); + return value; +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_iframe-navigation.js b/toolkit/devtools/inspector/test/browser_inspector_iframe-navigation.js new file mode 100644 index 000000000..6d74ca81a --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_iframe-navigation.js @@ -0,0 +1,50 @@ +/* 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 that the highlighter element picker still works through iframe +// navigations. + +const TEST_URI = "data:text/html;charset=utf-8," + + "<p>bug 699308 - test iframe navigation</p>" + + "<iframe src='data:text/html;charset=utf-8,hello world'></iframe>"; + +add_task(function* () { + let { inspector, toolbox } = yield openInspectorForURL(TEST_URI); + let iframe = getNode("iframe"); + + info("Starting element picker."); + yield toolbox.highlighterUtils.startPicker(); + + info("Waiting for highlighter to activate."); + let highlighterShowing = toolbox.once("highlighter-ready"); + executeInContent("Test:SynthesizeMouse", { + options: {type: "mousemove"}, + x: 1, + y: 1 + }, {node: content.document.body}, false); + yield highlighterShowing; + + let isVisible = yield isHighlighting(toolbox); + ok(isVisible, "Inspector is highlighting."); + + yield reloadFrame(); + info("Frame reloaded. Reloading again."); + + yield reloadFrame(); + info("Frame reloaded twice."); + + isVisible = yield isHighlighting(toolbox); + ok(isVisible, "Inspector is highlighting after iframe nav."); + + info("Stopping element picker."); + yield toolbox.highlighterUtils.stopPicker(); + + function reloadFrame() { + info("Reloading frame."); + let frameLoaded = once(iframe, "load"); + iframe.contentWindow.location.reload(); + return frameLoaded; + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_infobar_01.js b/toolkit/devtools/inspector/test/browser_inspector_infobar_01.js new file mode 100644 index 000000000..54ce98768 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_infobar_01.js @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Check the position and text content of the highlighter nodeinfo bar. + +const TEST_URI = "http://example.com/browser/browser/devtools/inspector/" + + "test/doc_inspector_infobar_01.html"; + +add_task(function*() { + yield addTab(TEST_URI); + let {inspector} = yield openInspector(); + + let testData = [ + { + selector: "#top", + position: "bottom", + tag: "DIV", + id: "top", + classes: ".class1.class2", + dims: "500" + " \u00D7 " + "100" + }, + { + selector: "#vertical", + position: "overlap", + tag: "DIV", + id: "vertical", + classes: "" + // No dims as they will vary between computers + }, + { + selector: "#bottom", + position: "top", + tag: "DIV", + id: "bottom", + classes: "", + dims: "500" + " \u00D7 " + "100" + }, + { + selector: "body", + position: "bottom", + tag: "BODY", + classes: "" + // No dims as they will vary between computers + }, + ]; + + for (let currTest of testData) { + yield testPosition(currTest, inspector); + } +}); + +function* testPosition(test, inspector) { + info("Testing " + test.selector); + + yield selectAndHighlightNode(test.selector, inspector); + + let highlighter = inspector.toolbox.highlighter; + let position = yield getHighlighterNodeAttribute(highlighter, + "box-model-nodeinfobar-container", "position"); + is(position, test.position, "Node " + test.selector + ": position matches"); + + let tag = yield getHighlighterNodeTextContent(highlighter, + "box-model-nodeinfobar-tagname"); + is(tag, test.tag, "node " + test.selector + ": tagName matches."); + + if (test.id) { + let id = yield getHighlighterNodeTextContent(highlighter, + "box-model-nodeinfobar-id"); + is(id, "#" + test.id, "node " + test.selector + ": id matches."); + } + + let classes = yield getHighlighterNodeTextContent(highlighter, + "box-model-nodeinfobar-classes"); + is(classes, test.classes, "node " + test.selector + ": classes match."); + + if (test.dims) { + let dims = yield getHighlighterNodeTextContent(highlighter, + "box-model-nodeinfobar-dimensions"); + is(dims, test.dims, "node " + test.selector + ": dims match."); + } +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_initialization.js b/toolkit/devtools/inspector/test/browser_inspector_initialization.js new file mode 100644 index 000000000..903511f4b --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_initialization.js @@ -0,0 +1,141 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Tests for different ways to initialize the inspector. + +const DOCUMENT_HTML = '<div id="first" style="{margin: 10em; font-size: 14pt;' + + 'font-family: helvetica, sans-serif; color: #AAA}">\n' + + '<h1>Some header text</h1>\n' + + '<p id="salutation" style="{font-size: 12pt}">hi.</p>\n' + + '<p id="body" style="{font-size: 12pt}">I am a test-case. This text exists ' + + 'solely to provide some things to test the inspector initialization.</p>\n' + + 'If you are reading this, you should go do something else instead. Maybe ' + + 'read a book. Or better yet, write some test-cases for another bit of code. ' + + '<span style="{font-style: italic}">Inspector\'s!</span></p>\n' + + '<p id="closing">end transmission</p>\n' + + '</div>'; + +const TEST_URI = "data:text/html;charset=utf-8,test page"; + +add_task(function* () { + let tab = yield addTab(TEST_URI); + content.document.body.innerHTML = DOCUMENT_HTML; + content.document.title = "Inspector Initialization Test"; + + yield testToolboxInitialization(tab); + yield testContextMenuInitialization(); + yield testContextMenuInspectorAlreadyOpen(); +}); + +function* testToolboxInitialization(tab) { + let target = TargetFactory.forTab(tab); + + info("Opening inspector with gDevTools."); + let toolbox = yield gDevTools.showToolbox(target, "inspector"); + let inspector = toolbox.getCurrentPanel(); + + ok(true, "Inspector started, and notification received."); + ok(inspector, "Inspector instance is accessible."); + ok(inspector.isReady, "Inspector instance is ready."); + is(inspector.target.tab, tab, "Valid target."); + + yield selectNode("p", inspector); + yield testMarkupView("p", inspector); + yield testBreadcrumbs("p", inspector); + + let span = getNode("span"); + span.scrollIntoView(); + + yield selectNode("span", inspector); + yield testMarkupView("span", inspector); + yield testBreadcrumbs("span", inspector); + + info("Destroying toolbox"); + let destroyed = toolbox.once("destroyed"); + toolbox.destroy(); + yield destroyed; + + ok("true", "'destroyed' notification received."); + ok(!gDevTools.getToolbox(target), "Toolbox destroyed."); +} + +function* testContextMenuInitialization() { + info("Opening inspector by clicking on 'Inspect Element' context menu item"); + let salutation = getNode("#salutation"); + + yield clickOnInspectMenuItem(salutation); + + info("Checking inspector state."); + yield testMarkupView("#salutation"); + yield testBreadcrumbs("#salutation"); +} + +function* testContextMenuInspectorAlreadyOpen() { + info("Changing node by clicking on 'Inspect Element' context menu item"); + + let inspector = getActiveInspector(); + ok(inspector, "Inspector is active"); + + let closing = getNode("#closing"); + yield clickOnInspectMenuItem(closing); + + ok(true, "Inspector was updated when 'Inspect Element' was clicked."); + yield testMarkupView("#closing", inspector); + yield testBreadcrumbs("#closing", inspector); +} + +function* testMarkupView(selector, inspector) { + inspector = inspector || getActiveInspector(); + let nodeFront = yield getNodeFront(selector, inspector); + try { + is(inspector.selection.nodeFront, nodeFront, + "Right node is selected in the markup view"); + } catch(ex) { + ok(false, "Got exception while resolving selected node of markup view."); + console.error(ex); + } +} + +function* testBreadcrumbs(selector, inspector) { + inspector = inspector || getActiveInspector(); + let nodeFront = yield getNodeFront(selector, inspector); + + let b = inspector.breadcrumbs; + let expectedText = b.prettyPrintNodeAsText(nodeFront); + let button = b.container.querySelector("button[checked=true]"); + ok(button, "A crumbs is checked=true"); + is(button.getAttribute("tooltiptext"), expectedText, "Crumb refers to the right node"); +} + +function* clickOnInspectMenuItem(node) { + info("Showing the contextual menu on node " + node); + yield executeInContent("Test:SynthesizeMouse", { + center: true, + options: {type: "contextmenu", button: 2} + }, {node}); + + // nsContextMenu also requires the popupNode to be set, but we can't set it to + // node under e10s as it's a CPOW, not a DOM node. But under e10s, + // nsContextMenu won't use the property anyway, so just try/catching is ok. + try { + document.popupNode = node; + } catch (e) {} + + let contentAreaContextMenu = document.querySelector("#contentAreaContextMenu"); + let contextMenu = new nsContextMenu(contentAreaContextMenu); + + info("Triggering inspect action and hiding the menu."); + yield contextMenu.inspectNode(); + + contentAreaContextMenu.hidden = true; + contentAreaContextMenu.hidePopup(); + contextMenu.hiding(); + + info("Waiting for inspector to update."); + yield getActiveInspector().once("inspector-updated"); +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_inspect-object-element.js b/toolkit/devtools/inspector/test/browser_inspector_inspect-object-element.js new file mode 100644 index 000000000..daec36a23 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_inspect-object-element.js @@ -0,0 +1,18 @@ +/* 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"; + +// A regression test for bug 665880 to make sure elements inside <object> can +// be inspected without exceptions. + +const TEST_URI = "data:text/html;charset=utf-8," + + "<object><p>browser_inspector_inspect-object-element.js</p></object>"; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URI); + let objectNode = getNode("object"); + ok(objectNode, "We have the object node"); + + yield selectNode("object", inspector); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_invalidate.js b/toolkit/devtools/inspector/test/browser_inspector_invalidate.js new file mode 100644 index 000000000..c6dae1aae --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_invalidate.js @@ -0,0 +1,38 @@ +/* vim: set 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 that highlighter handles geometry changes correctly. + +const TEST_URI = "data:text/html;charset=utf-8," + + "browser_inspector_invalidate.js\n" + + "<div style=\"width: 100px; height: 100px; background:yellow;\"></div>"; + +add_task(function*() { + let {toolbox, inspector} = yield openInspectorForURL(TEST_URI); + let div = getNode("div"); + let divFront = yield getNodeFront("div", inspector); + + info("Waiting for highlighter to activate"); + yield inspector.toolbox.highlighter.showBoxModel(divFront); + + let rect = yield getSimpleBorderRect(toolbox); + is(rect.width, 100, "The highlighter has the right width."); + + info("Changing the test element's size and waiting for the highlighter to update"); + let {actorID, connPrefix} = getHighlighterActorID(toolbox.highlighter); + yield executeInContent("Test:ChangeHighlightedNodeWaitForUpdate", { + name: "style", + value: "width: 200px; height: 100px; background:yellow;", + actorID, + connPrefix + }); + + rect = yield getSimpleBorderRect(toolbox); + is(rect.width, 200, "The highlighter has the right width after update"); + + info("Waiting for highlighter to hide"); + yield inspector.toolbox.highlighter.hideBoxModel(); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_keyboard-shortcuts.js b/toolkit/devtools/inspector/test/browser_inspector_keyboard-shortcuts.js new file mode 100644 index 000000000..f903f2481 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_keyboard-shortcuts.js @@ -0,0 +1,42 @@ +/* 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"; + +// Tests that the keybindings for highlighting different elements work as +// intended. + +const TEST_URI = "data:text/html;charset=utf-8," + + "<html><head><title>Test for the highlighter keybindings</title></head>" + + "<body><h1>Hello</h1><p><strong>Greetings, earthlings!</strong>" + + " I come in peace.</p></body></html>"; + +const TEST_DATA = [ + { key: "VK_RIGHT", selectedNode: "h1" }, + { key: "VK_DOWN", selectedNode: "p" }, + { key: "VK_UP", selectedNode: "h1" }, + { key: "VK_LEFT", selectedNode: "body" }, +]; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URI); + let bodyFront = yield getNodeFront("body", inspector); + is(inspector.selection.nodeFront, bodyFront, + "Body should be selected initially."); + + info("Focusing the currently active breadcrumb button"); + let bc = inspector.breadcrumbs; + bc.nodeHierarchy[bc.currentIndex].button.focus(); + + for (let { key, selectedNode } of TEST_DATA) { + info("Pressing " + key + " to select " + selectedNode); + + let updated = inspector.once("inspector-updated"); + EventUtils.synthesizeKey(key, {}); + yield updated; + + let selectedNodeFront = yield getNodeFront(selectedNode, inspector); + is(inspector.selection.nodeFront, selectedNodeFront, + selectedNode + " is selected."); + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_menu-01-sensitivity.js b/toolkit/devtools/inspector/test/browser_inspector_menu-01-sensitivity.js new file mode 100644 index 000000000..8781765c8 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_menu-01-sensitivity.js @@ -0,0 +1,202 @@ +/* vim: set 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 that context menu items are enabled / disabled correctly. + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html"; + +const PASTE_MENU_ITEMS = [ + "node-menu-pasteinnerhtml", + "node-menu-pasteouterhtml", + "node-menu-pastebefore", + "node-menu-pasteafter", + "node-menu-pastefirstchild", + "node-menu-pastelastchild", +]; + +const ALL_MENU_ITEMS = [ + "node-menu-edithtml", + "node-menu-copyinner", + "node-menu-copyouter", + "node-menu-copyuniqueselector", + "node-menu-copyimagedatauri", + "node-menu-showdomproperties", + "node-menu-delete", + "node-menu-pseudo-hover", + "node-menu-pseudo-active", + "node-menu-pseudo-focus" +].concat(PASTE_MENU_ITEMS); + +const ITEMS_WITHOUT_SHOWDOMPROPS = + ALL_MENU_ITEMS.filter(item => item != "node-menu-showdomproperties"); + +const TEST_CASES = [ + { + desc: "doctype node with empty clipboard", + selector: null, + disabled: ITEMS_WITHOUT_SHOWDOMPROPS, + }, + { + desc: "doctype node with html on clipboard", + clipboardData: "<p>some text</p>", + clipboardDataType: "html", + selector: null, + disabled: ITEMS_WITHOUT_SHOWDOMPROPS, + }, + { + desc: "element node HTML on the clipboard", + clipboardData: "<p>some text</p>", + clipboardDataType: "html", + disabled: ["node-menu-copyimagedatauri"], + selector: "#sensitivity", + }, + { + desc: "<html> element", + clipboardData: "<p>some text</p>", + clipboardDataType: "html", + selector: "html", + disabled: [ + "node-menu-copyimagedatauri", + "node-menu-pastebefore", + "node-menu-pasteafter", + "node-menu-pastefirstchild", + "node-menu-pastelastchild", + ], + }, + { + desc: "<body> with HTML on clipboard", + clipboardData: "<p>some text</p>", + clipboardDataType: "html", + selector: "body", + disabled: [ + "node-menu-copyimagedatauri", + "node-menu-pastebefore", + "node-menu-pasteafter", + ] + }, + { + desc: "<img> with HTML on clipboard", + clipboardData: "<p>some text</p>", + clipboardDataType: "html", + selector: "img", + disabled: [] + }, + { + desc: "<head> with HTML on clipboard", + clipboardData: "<p>some text</p>", + clipboardDataType: "html", + selector: "head", + disabled: [ + "node-menu-copyimagedatauri", + "node-menu-pastebefore", + "node-menu-pasteafter", + ] + }, + { + desc: "<element> with text on clipboard", + clipboardData: "some text", + clipboardDataType: undefined, + selector: "#paste-area", + disabled: ["node-menu-copyimagedatauri"], + }, + { + desc: "<element> with base64 encoded image data uri on clipboard", + clipboardData: + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" + + "AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==", + clipboardDataType: undefined, + selector: "#paste-area", + disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]), + }, + { + desc: "<element> with empty string on clipboard", + clipboardData: "", + clipboardDataType: undefined, + selector: "#paste-area", + disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]), + }, + { + desc: "<element> with whitespace only on clipboard", + clipboardData: " \n\n\t\n\n \n", + clipboardDataType: undefined, + selector: "#paste-area", + disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]), + }, +]; + +let clipboard = require("sdk/clipboard"); +registerCleanupFunction(() => { + clipboard = null; +}); + +add_task(function *() { + let { inspector } = yield openInspectorForURL(TEST_URL); + for (let test of TEST_CASES) { + let { desc, disabled, selector } = test; + + info(`Test ${desc}`); + setupClipboard(test.clipboardData, test.clipboardDataType); + + let front = yield getNodeFrontForSelector(selector, inspector); + + info("Selecting the specified node."); + yield selectNode(front, inspector); + + info("Simulating context menu click on the selected node container."); + contextMenuClick(getContainerForNodeFront(front, inspector).tagLine); + + for (let menuitem of ALL_MENU_ITEMS) { + let elt = inspector.panelDoc.getElementById(menuitem); + let shouldBeDisabled = disabled.indexOf(menuitem) !== -1; + let isDisabled = elt.hasAttribute("disabled"); + + is(isDisabled, shouldBeDisabled, + `#${menuitem} should be ${shouldBeDisabled ? "disabled" : "enabled"} `); + } + } +}); + +/** + * A helper that fetches a front for a node that matches the given selector or + * doctype node if the selector is falsy. + */ +function* getNodeFrontForSelector(selector, inspector) { + if (selector) { + info("Retrieving front for selector " + selector); + return getNodeFront(selector, inspector); + } else { + info("Retrieving front for doctype node"); + let {nodes} = yield inspector.walker.children(inspector.walker.rootNode); + return nodes[0]; + } +} + +/** + * A helper that populates the clipboard with data of given type. Clears the + * clipboard if data is falsy. + */ +function setupClipboard(data, type) { + if (data) { + info("Populating clipboard with " + type + " data."); + clipboard.set(data, type); + } else { + info("Clearing clipboard."); + clipboard.set("", "text"); + } +} + +/** + * A helper that simulates a contextmenu event on the given chrome DOM element. + */ +function contextMenuClick(element) { + let evt = element.ownerDocument.createEvent('MouseEvents'); + let button = 2; // right click + + evt.initMouseEvent('contextmenu', true, true, + element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false, + false, false, false, button, null); + + element.dispatchEvent(evt); +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_menu-02-copy-items.js b/toolkit/devtools/inspector/test/browser_inspector_menu-02-copy-items.js new file mode 100644 index 000000000..3f8ab24ce --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_menu-02-copy-items.js @@ -0,0 +1,51 @@ +/* vim: set 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 that the various copy items in the context menu works correctly. + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html"; +const COPY_ITEMS_TEST_DATA = [ + { + desc: "copy inner html", + id: "node-menu-copyinner", + selector: "[data-id=\"copy\"]", + text: "Paragraph for testing copy", + }, + { + desc: "copy outer html", + id: "node-menu-copyouter", + selector: "[data-id=\"copy\"]", + text: "<p data-id=\"copy\">Paragraph for testing copy</p>", + }, + { + desc: "copy unique selector", + id: "node-menu-copyuniqueselector", + selector: "[data-id=\"copy\"]", + text: "body > div:nth-child(1) > p:nth-child(2)", + }, + { + desc: "copy image data uri", + id: "node-menu-copyimagedatauri", + selector: "#copyimage", + text: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" + + "AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==", + }, +]; + +add_task(function *() { + let { inspector } = yield openInspectorForURL(TEST_URL); + for (let {desc, id, selector, text} of COPY_ITEMS_TEST_DATA) { + info("Testing " + desc); + yield selectNode(selector, inspector); + + let item = inspector.panelDoc.getElementById(id); + ok(item, "The popup has a " + desc + " menu item."); + + let deferred = promise.defer(); + waitForClipboard(text, () => item.doCommand(), + deferred.resolve, deferred.reject); + yield deferred.promise; + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_menu-03-paste-items.js b/toolkit/devtools/inspector/test/browser_inspector_menu-03-paste-items.js new file mode 100644 index 000000000..78c1004e1 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_menu-03-paste-items.js @@ -0,0 +1,150 @@ +/* vim: set 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 that different paste items work in the context menu + + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html"; +const PASTE_ADJACENT_HTML_DATA = [ + { + desc: "As First Child", + clipboardData: "2", + menuId: "node-menu-pastefirstchild", + }, + { + desc: "As Last Child", + clipboardData: "4", + menuId: "node-menu-pastelastchild", + }, + { + desc: "Before", + clipboardData: "1", + menuId: "node-menu-pastebefore", + }, + { + desc: "After", + clipboardData: "<span>5</span>", + menuId: "node-menu-pasteafter", + }, +]; + + +let clipboard = require("sdk/clipboard"); +registerCleanupFunction(() => { + clipboard = null; +}); + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URL); + + yield testPasteOuterHTMLMenu(); + yield testPasteInnerHTMLMenu(); + yield testPasteAdjacentHTMLMenu(); + + function* testPasteOuterHTMLMenu() { + info("Testing that 'Paste Outer HTML' menu item works."); + clipboard.set("this was pasted (outerHTML)"); + let outerHTMLSelector = "#paste-area h1"; + + let nodeFront = yield getNodeFront(outerHTMLSelector, inspector); + yield selectNode(nodeFront, inspector); + + contextMenuClick(getContainerForNodeFront(nodeFront, inspector).tagLine); + + let onNodeReselected = inspector.markup.once("reselectedonremoved"); + let menu = inspector.panelDoc.getElementById("node-menu-pasteouterhtml"); + dispatchCommandEvent(menu); + + info("Waiting for inspector selection to update"); + yield onNodeReselected; + + ok(content.document.body.outerHTML.contains(clipboard.get()), + "Clipboard content was pasted into the node's outer HTML."); + ok(!getNode(outerHTMLSelector, { expectNoMatch: true }), + "The original node was removed."); + } + + function* testPasteInnerHTMLMenu() { + info("Testing that 'Paste Inner HTML' menu item works."); + clipboard.set("this was pasted (innerHTML)"); + let innerHTMLSelector = "#paste-area .inner"; + let getInnerHTML = () => content.document.querySelector(innerHTMLSelector).innerHTML; + let origInnerHTML = getInnerHTML(); + + let nodeFront = yield getNodeFront(innerHTMLSelector, inspector); + yield selectNode(nodeFront, inspector); + + contextMenuClick(getContainerForNodeFront(nodeFront, inspector).tagLine); + + let onMutation = inspector.once("markupmutation"); + let menu = inspector.panelDoc.getElementById("node-menu-pasteinnerhtml"); + dispatchCommandEvent(menu); + + info("Waiting for mutation to occur"); + yield onMutation; + + ok(getInnerHTML() === clipboard.get(), + "Clipboard content was pasted into the node's inner HTML."); + ok(getNode(innerHTMLSelector), "The original node has been preserved."); + yield undoChange(inspector); + ok(getInnerHTML() === origInnerHTML, "Previous innerHTML has been " + + "restored after undo"); + } + + function* testPasteAdjacentHTMLMenu() { + let refSelector = "#paste-area .adjacent .ref"; + let adjacentNode = content.document.querySelector(refSelector).parentNode; + let nodeFront = yield getNodeFront(refSelector, inspector); + yield selectNode(nodeFront, inspector); + let markupTagLine = getContainerForNodeFront(nodeFront, inspector).tagLine; + + for (let { clipboardData, menuId } of PASTE_ADJACENT_HTML_DATA) { + let menu = inspector.panelDoc.getElementById(menuId); + info(`Testing ${getLabelFor(menu)} for ${clipboardData}`); + clipboard.set(clipboardData); + + contextMenuClick(markupTagLine); + let onMutation = inspector.once("markupmutation"); + dispatchCommandEvent(menu); + + info("Waiting for mutation to occur"); + yield onMutation; + } + + ok(adjacentNode.innerHTML.trim() === "1<span class=\"ref\">234</span>" + + "<span>5</span>", "The Paste as Last Child / as First Child / Before " + + "/ After worked as expected"); + yield undoChange(inspector); + ok(adjacentNode.innerHTML.trim() === "1<span class=\"ref\">234</span>", + "Undo works for paste adjacent HTML"); + } + + function dispatchCommandEvent(node) { + info("Dispatching command event on " + node); + let commandEvent = document.createEvent("XULCommandEvent"); + commandEvent.initCommandEvent("command", true, true, window, 0, false, false, + false, false, null); + node.dispatchEvent(commandEvent); + } + + function contextMenuClick(element) { + info("Simulating contextmenu event on " + element); + let evt = element.ownerDocument.createEvent('MouseEvents'); + let button = 2; // right click + + evt.initMouseEvent('contextmenu', true, true, + element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false, + false, false, false, button, null); + + element.dispatchEvent(evt); + } + + function getLabelFor(elt) { + if (typeof elt === "string") + elt = inspector.panelDoc.querySelector(elt); + let isInPasteSubMenu = elt.matches("#node-menu-paste-extra-submenu *"); + return `"${isInPasteSubMenu ? "Paste > " : ""}${elt.label}"`; + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_menu-04-other.js b/toolkit/devtools/inspector/test/browser_inspector_menu-04-other.js new file mode 100644 index 000000000..1f54aa761 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_menu-04-other.js @@ -0,0 +1,73 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Tests for menuitem functionality that doesn't fit into any specific category + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html"; + +add_task(function* () { + let { inspector, toolbox } = yield openInspectorForURL(TEST_URL); + + yield testShowDOMProperties(); + yield testDeleteNode(); + yield testDeleteRootNode(); + + function* testShowDOMProperties() { + info("Testing 'Show DOM Properties' menu item."); + let showDOMPropertiesNode = inspector.panelDoc.getElementById("node-menu-showdomproperties"); + ok(showDOMPropertiesNode, "the popup menu has a show dom properties item"); + + let consoleOpened = toolbox.once("webconsole-ready"); + + info("Triggering 'Show DOM Properties' and waiting for inspector open"); + dispatchCommandEvent(showDOMPropertiesNode); + yield consoleOpened; + + let webconsoleUI = toolbox.getPanel("webconsole").hud.ui; + let messagesAdded = webconsoleUI.once("new-messages"); + yield messagesAdded; + + info("Checking if 'inspect($0)' was evaluated"); + ok(webconsoleUI.jsterm.history[0] === 'inspect($0)'); + + yield toolbox.toggleSplitConsole(); + } + + function* testDeleteNode() { + info("Testing 'Delete Node' menu item for normal elements."); + + yield selectNode("#delete", inspector); + let deleteNode = inspector.panelDoc.getElementById("node-menu-delete"); + ok(deleteNode, "the popup menu has a delete menu item"); + + let updated = inspector.once("inspector-updated"); + + info("Triggering 'Delete Node' and waiting for inspector to update"); + dispatchCommandEvent(deleteNode); + yield updated; + + ok(!getNode("#delete", { expectNoMatch: true }), "Node deleted"); + } + + function* testDeleteRootNode() { + info("Testing 'Delete Node' menu item does not delete root node."); + yield selectNode("html", inspector); + + let deleteNode = inspector.panelDoc.getElementById("node-menu-delete"); + dispatchCommandEvent(deleteNode); + + executeSoon(() => { + ok(content.document.documentElement, "Document element still alive."); + }); + } + + function dispatchCommandEvent(node) { + info("Dispatching command event on " + node); + let commandEvent = document.createEvent("XULCommandEvent"); + commandEvent.initCommandEvent("command", true, true, window, 0, false, false, + false, false, null); + node.dispatchEvent(commandEvent); + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_navigation.js b/toolkit/devtools/inspector/test/browser_inspector_navigation.js new file mode 100644 index 000000000..80fb316da --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_navigation.js @@ -0,0 +1,43 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Test that inspector updates when page is navigated. + +const TEST_URL_FILE = "browser/browser/devtools/inspector/test/" + + "doc_inspector_breadcrumbs.html"; + +const TEST_URL_1 = "http://test1.example.org/" + TEST_URL_FILE; +const TEST_URL_2 = "http://test2.example.org/" + TEST_URL_FILE; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URL_1); + let markuploaded = inspector.once("markuploaded"); + + yield selectNode("#i1", inspector); + + info("Navigating to a different page."); + content.location = TEST_URL_2; + + info("Waiting for markup view to load after navigation."); + yield markuploaded; + + ok(true, "New page loaded"); + yield selectNode("#i1", inspector); + + markuploaded = inspector.once("markuploaded"); + + info("Going back in history"); + content.history.go(-1); + + info("Waiting for markup view to load after going back in history."); + yield markuploaded; + + ok(true, "Old page loaded"); + is(content.location.href, TEST_URL_1, "URL is correct."); + + yield selectNode("#i1", inspector); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_picker-stop-on-destroy.js b/toolkit/devtools/inspector/test/browser_inspector_picker-stop-on-destroy.js new file mode 100644 index 000000000..64c80c1e6 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_picker-stop-on-destroy.js @@ -0,0 +1,30 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Test that the highlighter's picker should be stopped when the toolbox is +// closed + +const TEST_URI = "data:text/html;charset=utf-8," + + "<p>testing the highlighter goes away on destroy</p>"; + +add_task(function* () { + let { inspector, toolbox } = yield openInspectorForURL(TEST_URI); + let pickerStopped = toolbox.once("picker-stopped"); + + yield selectNode("p", inspector); + + info("Inspector displayed and ready, starting the picker."); + yield toolbox.highlighterUtils.startPicker(); + + info("Destroying the toolbox."); + yield toolbox.destroy(); + + info("Waiting for the picker-stopped event that should be fired when the " + + "toolbox is destroyed."); + yield pickerStopped; + + ok(true, "picker-stopped event fired after switch tools, so picker is closed."); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_picker-stop-on-tool-change.js b/toolkit/devtools/inspector/test/browser_inspector_picker-stop-on-tool-change.js new file mode 100644 index 000000000..72ca7d6c7 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_picker-stop-on-tool-change.js @@ -0,0 +1,27 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Test that the highlighter's picker is stopped when a different tool is +// selected + +const TEST_URI = "data:text/html;charset=UTF-8," + + "testing the highlighter goes away on tool selection"; + +add_task(function* () { + let { toolbox } = yield openInspectorForURL(TEST_URI); + let pickerStopped = toolbox.once("picker-stopped"); + + info("Starting the inspector picker"); + yield toolbox.highlighterUtils.startPicker(); + + info("Selecting another tool than the inspector in the toolbox"); + yield toolbox.selectNextTool(); + + info("Waiting for the picker-stopped event to be fired"); + yield pickerStopped; + + ok(true, "picker-stopped event fired after switch tools; picker is closed"); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_pseudoclass-lock.js b/toolkit/devtools/inspector/test/browser_inspector_pseudoclass-lock.js new file mode 100644 index 000000000..2466a314a --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_pseudoclass-lock.js @@ -0,0 +1,151 @@ +/* vim: set 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 that locking the pseudoclass displays correctly in the ruleview + +const PSEUDO = ":hover"; +const TEST_URL = 'data:text/html;charset=UTF-8,' + + '<head>' + + ' <style>div {color:red;} div:hover {color:blue;}</style>' + + '</head>' + + '<body>' + + ' <div id="parent-div">' + + ' <div id="div-1">test div</div>' + + ' <div id="div-2">test div2</div>' + + ' </div>' + + '</body>'; + +add_task(function*() { + info("Creating the test tab and opening the rule-view"); + let {toolbox, inspector} = yield openInspectorForURL(TEST_URL); + + let view = yield ensureRuleView(inspector); + + info("Selecting the test node"); + yield selectNode("#div-1", inspector); + + yield togglePseudoClass(inspector); + yield assertPseudoAddedToNode(inspector, view); + + yield togglePseudoClass(inspector); + yield assertPseudoRemovedFromNode(); + yield assertPseudoRemovedFromView(inspector, view); + + yield togglePseudoClass(inspector); + yield testNavigate(inspector, view); + + info("Destroying the toolbox"); + yield toolbox.destroy(); + yield assertPseudoRemovedFromNode(getNode("#div-1")); +}); + + +function* togglePseudoClass(inspector) { + info("Toggle the pseudoclass, wait for it to be applied"); + + // Give the inspector panels a chance to update when the pseudoclass changes + let onPseudo = inspector.selection.once("pseudoclass"); + let onRefresh = inspector.once("rule-view-refreshed"); + + // Walker uses SDK-events so calling walker.once does not return a promise. + let onMutations = once(inspector.walker, "mutations"); + + yield inspector.togglePseudoClass(PSEUDO); + + yield onPseudo; + yield onRefresh; + yield onMutations; +} + +function* testNavigate(inspector, ruleview) { + yield selectNode("#parent-div", inspector); + + info("Make sure the pseudoclass is still on after navigating to a parent"); + let res = yield executeInContent("Test:HasPseudoClassLock", + {pseudo: PSEUDO}, + {node: getNode("#div-1")}); + ok(res.data, "pseudo-class lock is still applied after inspecting ancestor"); + + let onPseudo = inspector.selection.once("pseudoclass"); + yield selectNode("#div-2", inspector); + yield onPseudo; + + info("Make sure the pseudoclass is removed after navigating to a non-hierarchy node"); + res = yield executeInContent("Test:HasPseudoClassLock", + {pseudo: PSEUDO}, + {node: getNode("#div-1")}); + ok(!res.data, "pseudo-class lock is removed after inspecting sibling node"); + + yield selectNode("#div-1", inspector); + yield togglePseudoClass(inspector); +} + +function* showPickerOn(selector, inspector) { + let highlighter = inspector.toolbox.highlighter; + let nodeFront = yield getNodeFront(selector, inspector); + yield highlighter.showBoxModel(nodeFront); +} + +function* assertPseudoAddedToNode(inspector, ruleview) { + info("Make sure the pseudoclass lock is applied to #div-1 and its ancestors"); + + let node = getNode("#div-1"); + do { + let {data: hasLock} = yield executeInContent("Test:HasPseudoClassLock", + {pseudo: PSEUDO}, {node}); + ok(hasLock, "pseudo-class lock has been applied"); + node = node.parentNode; + } while (node.parentNode) + + info("Check that the ruleview contains the pseudo-class rule"); + let rules = ruleview.element.querySelectorAll(".ruleview-rule.theme-separator"); + is(rules.length, 3, "rule view is showing 3 rules for pseudo-class locked div"); + is(rules[1]._ruleEditor.rule.selectorText, "div:hover", "rule view is showing " + PSEUDO + " rule"); + + info("Show the highlighter on #div-1"); + yield showPickerOn("#div-1", inspector); + + info("Check that the infobar selector contains the pseudo-class"); + let value = yield getHighlighterNodeTextContent(inspector.toolbox.highlighter, + "box-model-nodeinfobar-pseudo-classes"); + is(value, PSEUDO, "pseudo-class in infobar selector"); + yield inspector.toolbox.highlighter.hideBoxModel(); +} + +function* assertPseudoRemovedFromNode() { + info("Make sure the pseudoclass lock is removed from #div-1 and its ancestors"); + let node = getNode("#div-1"); + do { + let {data: hasLock} = yield executeInContent("Test:HasPseudoClassLock", + {pseudo: PSEUDO}, {node}); + ok(!hasLock, "pseudo-class lock has been removed"); + node = node.parentNode; + } while (node.parentNode) +} + +function* assertPseudoRemovedFromView(inspector, ruleview) { + info("Check that the ruleview no longer contains the pseudo-class rule"); + let rules = ruleview.element.querySelectorAll(".ruleview-rule.theme-separator"); + is(rules.length, 2, "rule view is showing 2 rules after removing lock"); + + yield showPickerOn("#div-1", inspector); + + let value = yield getHighlighterNodeTextContent(inspector.toolbox.highlighter, + "box-model-nodeinfobar-pseudo-classes"); + is(value, "", "pseudo-class removed from infobar selector"); + yield inspector.toolbox.highlighter.hideBoxModel(); +} + +function* ensureRuleView(inspector) { + if (!inspector.sidebar.getWindowForTab("ruleview")) { + info("Waiting for ruleview initialization to complete."); + yield inspector.sidebar.once("ruleview-ready"); + } + + info("Selecting the ruleview sidebar"); + inspector.sidebar.select("ruleview"); + + return inspector.sidebar.getWindowForTab("ruleview")["ruleview"].view; +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_pseudoclass-menu.js b/toolkit/devtools/inspector/test/browser_inspector_pseudoclass-menu.js new file mode 100644 index 000000000..98ba80d2b --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_pseudoclass-menu.js @@ -0,0 +1,59 @@ +/* vim: set 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 that the inspector has the correct pseudo-class locking menu items and +// that these items actually work + +const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); +const PSEUDOS = ["hover", "active", "focus"]; + +add_task(function*() { + yield addTab("data:text/html,pseudo-class lock node menu tests"); + + info("Creating the test element"); + let div = content.document.createElement("div"); + div.textContent = "test div"; + content.document.body.appendChild(div); + + let {inspector} = yield openInspector(); + yield selectNode("div", inspector); + + info("Getting the inspector ctx menu and opening it"); + let menu = inspector.panelDoc.getElementById("inspector-node-popup"); + yield openMenu(menu); + + yield testMenuItems(div, menu, inspector); +}); + +function openMenu(menu) { + let promise = once(menu, "popupshowing", true); + menu.openPopup(); + return promise; +} + +function* testMenuItems(div, menu, inspector) { + for (let pseudo of PSEUDOS) { + let menuitem = inspector.panelDoc.getElementById("node-menu-pseudo-" + pseudo); + ok(menuitem, ":" + pseudo + " menuitem exists"); + + // Give the inspector panels a chance to update when the pseudoclass changes + let onPseudo = inspector.selection.once("pseudoclass"); + let onRefresh = inspector.once("rule-view-refreshed"); + + // Walker uses SDK-events so calling walker.once does not return a promise. + let onMutations = once(inspector.walker, "mutations"); + + menuitem.doCommand(); + + yield onPseudo; + yield onRefresh; + yield onMutations; + + let {data: hasLock} = yield executeInContent("Test:HasPseudoClassLock", + {pseudo: ":" + pseudo}, + {node: div}); + ok(hasLock, "pseudo-class lock has been applied"); + } +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_reload-01.js b/toolkit/devtools/inspector/test/browser_inspector_reload-01.js new file mode 100644 index 000000000..f39ed81f5 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_reload-01.js @@ -0,0 +1,32 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// A test to ensure reloading a page doesn't break the inspector. + +// Reload should reselect the currently selected markup view element. +// This should work even when an element whose selector needs escaping +// is selected (bug 1002280). +const TEST_URI = "data:text/html,<p id='1'>p</p>"; + +add_task(function* () { + let { inspector, toolbox } = yield openInspectorForURL(TEST_URI); + yield selectNode("p", inspector); + + let markupLoaded = inspector.once("markuploaded"); + + info("Reloading page."); + content.location.reload(); + + info("Waiting for markupview to load after reload."); + yield markupLoaded; + + let nodeFront = yield getNodeFront("p", inspector); + is(inspector.selection.nodeFront, nodeFront, "<p> selected after reload."); + + info("Selecting a node to see that inspector still works."); + yield selectNode("body", inspector); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_reload-02.js b/toolkit/devtools/inspector/test/browser_inspector_reload-02.js new file mode 100644 index 000000000..19a9eb5ec --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_reload-02.js @@ -0,0 +1,48 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// A test to ensure reloading a page doesn't break the inspector. + +// Reload should reselect the currently selected markup view element. +// This should work even when an element whose selector is inaccessible +// is selected (bug 1038651). +const TEST_URI = 'data:text/xml,<?xml version="1.0" standalone="no"?>' + +'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"' + +' "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' + +'<svg width="4cm" height="4cm" viewBox="0 0 400 400"' + +' xmlns="http://www.w3.org/2000/svg" version="1.1">' + +' <title>Example triangle01- simple example of a path</title>' + +' <desc>A path that draws a triangle</desc>' + +' <rect x="1" y="1" width="398" height="398"' + +' fill="none" stroke="blue" />' + +' <path d="M 100 100 L 300 100 L 200 300 z"' + +' fill="red" stroke="blue" stroke-width="3" />' + +'</svg>'; + +add_task(function* () { + let { inspector, toolbox } = yield openInspectorForURL(TEST_URI); + + let markupLoaded = inspector.once("markuploaded"); + + info("Reloading page."); + content.location.reload(); + + info("Waiting for markupview to load after reload."); + yield markupLoaded; + + let svgFront = yield getNodeFront("svg", inspector); + is(inspector.selection.nodeFront, svgFront, "<svg> selected after reload."); + + info("Selecting a node to see that inspector still works."); + yield selectNode("rect", inspector); + + info("Reloading page."); + content.location.reload(); + + let rectFront = yield getNodeFront("rect", inspector); + is(inspector.selection.nodeFront, rectFront, "<rect> selected after reload."); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_remove-iframe-during-load.js b/toolkit/devtools/inspector/test/browser_inspector_remove-iframe-during-load.js new file mode 100644 index 000000000..c995f7ae3 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_remove-iframe-during-load.js @@ -0,0 +1,46 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Testing that the inspector doesn't go blank when navigating to a page that +// deletes an iframe while loading. + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_remove-iframe-during-load.html"; + +add_task(function* () { + let {inspector, toolbox} = yield openInspectorForURL("about:blank"); + yield selectNode("body", inspector); + + // We do not want to wait for the inspector to be fully ready before testing + // so we load TEST_URL and just wait for the content window to be done loading. + let done = waitForContentMessage("Test:TestPageProcessingDone"); + content.location = TEST_URL; + yield done; + + // The content doc contains a script that creates iframes and deletes them + // immediately after. It does this before the load event, after + // DOMContentLoaded and after load. This is what used to make the inspector go + // blank when navigating to that page. + // At this stage, there should be no iframes in the page anymore. + ok(!getNode("iframe", {expectNoMatch: true}), + "Iframes added by the content page should have been removed"); + + // Create/remove an extra one now, after the load event. + info("Creating and removing an iframe."); + let iframe = content.document.createElement("iframe"); + content.document.body.appendChild(iframe); + iframe.remove(); + + ok(!getNode("iframe", {expectNoMatch: true}), + "The after-load iframe should have been removed."); + + info("Waiting for markup-view to load."); + yield inspector.once("markuploaded"); + + // Assert that the markup-view is displayed and works + ok(!getNode("iframe", {expectNoMatch: true}), "Iframe has been removed."); + is(getNode("#yay").textContent, "load", "Load event fired."); + + yield selectNode("#yay", inspector); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_scrolling.js b/toolkit/devtools/inspector/test/browser_inspector_scrolling.js new file mode 100644 index 000000000..15e23e666 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_scrolling.js @@ -0,0 +1,45 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Test that highlighted nodes can be scrolled. +// TODO: This doesn't test anything useful. See b.m.o 1035661. +const IFRAME_SRC = "data:text/html;charset=utf-8," + + "<div style='height:500px; width:500px; border:1px solid gray;'>" + + "big div" + + "</div>"; + +const TEST_URI = "data:text/html;charset=utf-8," + + "<p>browser_inspector_scrolling.js</p>" + + "<iframe src=\"" + IFRAME_SRC + "\" />"; + +add_task(function* () { + let { inspector, toolbox } = yield openInspectorForURL(TEST_URI); + + let iframe = getNode("iframe"); + let div = getNode("div", { document: iframe.contentDocument }); + let divFront = yield getNodeFrontInFrame("div", "iframe", inspector); + + info("Waiting for highlighter box model to appear."); + yield toolbox.highlighter.showBoxModel(divFront); + + let scrolled = once(gBrowser.selectedBrowser, "scroll"); + + info("Scrolling iframe."); + EventUtils.synthesizeWheel(div, 10, 10, + { deltaY: 50.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL }, + iframe.contentWindow); + + info("Waiting for scroll event"); + yield scrolled; + + let isRetina = devicePixelRatio === 2; + is(iframe.contentDocument.body.scrollTop, + isRetina ? 25 : 50, "inspected iframe scrolled"); + + info("Hiding box model."); + yield toolbox.highlighter.hideBoxModel(); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_search-01.js b/toolkit/devtools/inspector/test/browser_inspector_search-01.js new file mode 100644 index 000000000..1e74be3ab --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_search-01.js @@ -0,0 +1,84 @@ +/* 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 that searching for nodes in the search field actually selects those +// nodes. + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_search.html"; + +// Indexes of the keys in the KEY_STATES array that should listen to "keypress" +// event instead of "command". These are keys that don't change the content of +// the search field and thus don't trigger command event. +const LISTEN_KEYPRESS = [3,4,8,18,19,20,21,22]; + +// The various states of the inspector: [key, id, isValid] +// [ +// what key to press, +// what id should be selected after the keypress, +// is the searched text valid selector +// ] +const KEY_STATES = [ + ["d", "b1", false], + ["i", "b1", false], + ["v", "d1", true], + ["VK_DOWN", "d2", true], // keypress + ["VK_RETURN", "d1", true], //keypress + [".", "d1", false], + ["c", "d1", false], + ["1", "d2", true], + ["VK_DOWN", "d2", true], // keypress + ["VK_BACK_SPACE", "d2", false], + ["VK_BACK_SPACE", "d2", false], + ["VK_BACK_SPACE", "d1", true], + ["VK_BACK_SPACE", "d1", false], + ["VK_BACK_SPACE", "d1", false], + ["VK_BACK_SPACE", "d1", true], + [".", "d1", false], + ["c", "d1", false], + ["1", "d2", true], + ["VK_DOWN", "s2", true], // keypress + ["VK_DOWN", "p1", true], // kepress + ["VK_UP", "s2", true], // keypress + ["VK_UP", "d2", true], // keypress + ["VK_UP", "p1", true], + ["VK_BACK_SPACE", "p1", false], + ["2", "p3", true], + ["VK_BACK_SPACE", "p3", false], + ["VK_BACK_SPACE", "p3", false], + ["VK_BACK_SPACE", "p3", true], + ["r", "p3", false], +]; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URL); + let { searchBox } = inspector; + + yield selectNode("#b1", inspector); + yield focusSearchBoxUsingShortcut(inspector.panelWin); + + let index = 0; + for (let [ key, id, isValid ] of KEY_STATES) { + let event = (LISTEN_KEYPRESS.indexOf(index) !== -1) ? "keypress" : "command"; + let eventHandled = once(searchBox, event, true); + + info(index + ": Pressing key " + key + " to get id " + id); + EventUtils.synthesizeKey(key, {}, inspector.panelWin); + yield eventHandled; + + info("Got " + event + " event. Waiting for search query to complete"); + yield inspector.searchSuggestions._lastQuery; + + info(inspector.selection.nodeFront.id + " is selected with text " + + searchBox.value); + let nodeFront = yield getNodeFront("#" + id, inspector); + is(inspector.selection.nodeFront, nodeFront, + "Correct node is selected for state " + index); + + is(!searchBox.classList.contains("devtools-no-search-result"), isValid, + "Correct searchbox result state for state " + index); + + index++; + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_search-02.js b/toolkit/devtools/inspector/test/browser_inspector_search-02.js new file mode 100644 index 000000000..179a33d23 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_search-02.js @@ -0,0 +1,157 @@ +/* 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"; + +// Testing that searching for combining selectors using the inspector search +// field produces correct suggestions. + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_search-suggestions.html"; + +// An array of (key, suggestions) pairs where key is a key to press and +// suggestions is an array of suggestions that should be shown in the popup. +// Suggestion is an object with label of the entry and optional count +// (defaults to 1) +const TEST_DATA = [ + { + key: "d", + suggestions: [{label: "div", count: 4}] + }, + { + key: "i", + suggestions: [{label: "div", count: 4}] + }, + { + key: "v", + suggestions: [] + }, + { + key: " ", + suggestions: [ + {label: "div div", count: 2}, + {label: "div span", count: 2} + ] + }, + { + key: ">", + suggestions: [ + {label: "div >div", count: 2}, + {label: "div >span", count: 2} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: "div div", count: 2 }, + {label: "div span", count: 2} + ] + }, + { + key: "+", + suggestions: [{label: "div +span"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: "div div", count: 2 }, + {label: "div span", count: 2} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: "VK_BACK_SPACE", + suggestions: [{label: "div", count: 4}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [{label: "div", count: 4}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: "p", + suggestions: [] + }, + { + key: " ", + suggestions: [{label: "p strong"}] + }, + { + key: "+", + suggestions: [ + {label: "p +button" }, + {label: "p +p"} + ] + }, + { + key: "b", + suggestions: [{label: "p +button"}] + }, + { + key: "u", + suggestions: [{label: "p +button"}] + }, + { + key: "t", + suggestions: [{label: "p +button"}] + }, + { + key: "t", + suggestions: [{label: "p +button"}] + }, + { + key: "o", + suggestions: [{label: "p +button"}] + }, + { + key: "n", + suggestions: [] + }, + { + key: "+", + suggestions: [{label: "p +button+p"}] + } +]; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URL); + let searchBox = inspector.searchBox; + let popup = inspector.searchSuggestions.searchPopup; + + yield focusSearchBoxUsingShortcut(inspector.panelWin); + + for (let { key, suggestions } of TEST_DATA) { + info("Pressing " + key + " to get " + formatSuggestions(suggestions)); + + let command = once(searchBox, "command"); + EventUtils.synthesizeKey(key, {}, inspector.panelWin); + yield command; + + info("Waiting for search query to complete"); + yield inspector.searchSuggestions._lastQuery; + + info("Query completed. Performing checks for input '" + searchBox.value + "'"); + let actualSuggestions = popup.getItems().reverse(); + + is(popup.isOpen ? actualSuggestions.length: 0, suggestions.length, + "There are expected number of suggestions."); + + for (let i = 0; i < suggestions.length; i++) { + is(suggestions[i].label, actualSuggestions[i].label, + "The suggestion at " + i + "th index is correct."); + is(suggestions[i].count || 1, actualSuggestions[i].count, + "The count for suggestion at " + i + "th index is correct."); + } + } +}); + +function formatSuggestions(suggestions) { + return "[" + suggestions + .map(s => "'" + s.label + "' (" + s.count || 1 + ")") + .join(", ") + "]"; +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_search-03.js b/toolkit/devtools/inspector/test/browser_inspector_search-03.js new file mode 100644 index 000000000..fc7bce62d --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_search-03.js @@ -0,0 +1,194 @@ +/* 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"; + +// Testing that searching for elements using the inspector search field +// produces correct suggestions. + +const TEST_URL = TEST_URL_ROOT + "doc_inspector_search.html"; + +// An array of (key, suggestions) pairs where key is a key to press and +// suggestions is an array of suggestions that should be shown in the popup. +// Suggestion is an object with label of the entry and optional count +// (defaults to 1) +let TEST_DATA = [ + { + key: "d", + suggestions: [{label: "div", count: 2}] + }, + { + key: "i", + suggestions: [{label: "div", count: 2}] + }, + { + key: "v", + suggestions: [] + }, + { + key: ".", + suggestions: [{label: "div.c1"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: "#", + suggestions: [ + {label: "div#d1"}, + {label: "div#d2"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: "VK_BACK_SPACE", + suggestions: [{label: "div", count: 2}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [{label: "div", count: 2}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: ".", + suggestions: [ + {label: ".c1", count: 3}, + {label: ".c2"} + ] + }, + { + key: "c", + suggestions: [ + {label: ".c1", count: 3}, + {label: ".c2"} + ] + }, + { + key: "2", + suggestions: [] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: ".c1", count: 3}, + {label: ".c2"} + ] + }, + { + key: "1", + suggestions: [] + }, + { + key: "#", + suggestions: [ + {label: "#d2"}, + {label: "#p1"}, + {label: "#s2"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: ".c1", count: 3}, + {label: ".c2"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: ".c1", count: 3}, + {label: ".c2"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: "#", + suggestions: [ + {label: "#b1"}, + {label: "#d1"}, + {label: "#d2"}, + {label: "#p1"}, + {label: "#p2"}, + {label: "#p3"}, + {label: "#s1"}, + {label: "#s2"} + ] + }, + { + key: "p", + suggestions: [ + {label: "#p1"}, + {label: "#p2"}, + {label: "#p3"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: "#b1"}, + {label: "#d1"}, + {label: "#d2"}, + {label: "#p1"}, + {label: "#p2"}, + {label: "#p3"}, + {label: "#s1"}, + {label: "#s2"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + } +]; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URL); + let searchBox = inspector.searchBox; + let popup = inspector.searchSuggestions.searchPopup; + + yield focusSearchBoxUsingShortcut(inspector.panelWin); + + for (let { key, suggestions } of TEST_DATA) { + info("Pressing " + key + " to get " + formatSuggestions(suggestions)); + + let command = once(searchBox, "command"); + EventUtils.synthesizeKey(key, {}, inspector.panelWin); + yield command; + + info("Waiting for search query to complete"); + yield inspector.searchSuggestions._lastQuery; + + info("Query completed. Performing checks for input '" + searchBox.value + "'"); + let actualSuggestions = popup.getItems().reverse(); + + is(popup.isOpen ? actualSuggestions.length: 0, suggestions.length, + "There are expected number of suggestions."); + + for (let i = 0; i < suggestions.length; i++) { + is(suggestions[i].label, actualSuggestions[i].label, + "The suggestion at " + i + "th index is correct."); + is(suggestions[i].count || 1, actualSuggestions[i].count, + "The count for suggestion at " + i + "th index is correct."); + } + } +}); + +function formatSuggestions(suggestions) { + return "[" + suggestions + .map(s => "'" + s.label + "' (" + s.count || 1 + ")") + .join(", ") + "]"; +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_search-navigation.js b/toolkit/devtools/inspector/test/browser_inspector_search-navigation.js new file mode 100644 index 000000000..978731039 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_search-navigation.js @@ -0,0 +1,73 @@ +/* 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"; + +// Check that searchbox value is correct when suggestions popup is navigated +// with keyboard. + +// Test data as pairs of [key to press, expected content of searchbox]. +const KEY_STATES = [ + ["d", "d"], + ["i", "di"], + ["v", "div"], + [".", "div."], + ["VK_UP", "div.c1"], + ["VK_DOWN", "div.l1"], + ["VK_DOWN", "div.l1"], + ["VK_BACK_SPACE", "div.l"], + ["VK_TAB", "div.l1"], + [" ", "div.l1 "], + ["VK_UP", "div.l1 div"], + ["VK_UP", "div.l1 div"], + [".", "div.l1 div."], + ["VK_TAB", "div.l1 div.c1"], + ["VK_BACK_SPACE", "div.l1 div.c"], + ["VK_BACK_SPACE", "div.l1 div."], + ["VK_BACK_SPACE", "div.l1 div"], + ["VK_BACK_SPACE", "div.l1 di"], + ["VK_BACK_SPACE", "div.l1 d"], + ["VK_BACK_SPACE", "div.l1 "], + ["VK_UP", "div.l1 div"], + ["VK_BACK_SPACE", "div.l1 di"], + ["VK_BACK_SPACE", "div.l1 d"], + ["VK_BACK_SPACE", "div.l1 "], + ["VK_UP", "div.l1 div"], + ["VK_UP", "div.l1 div"], + ["VK_TAB", "div.l1 div"], + ["VK_BACK_SPACE", "div.l1 di"], + ["VK_BACK_SPACE", "div.l1 d"], + ["VK_BACK_SPACE", "div.l1 "], + ["VK_DOWN", "div.l1 div"], + ["VK_DOWN", "div.l1 span"], + ["VK_DOWN", "div.l1 span"], + ["VK_BACK_SPACE", "div.l1 spa"], + ["VK_BACK_SPACE", "div.l1 sp"], + ["VK_BACK_SPACE", "div.l1 s"], + ["VK_BACK_SPACE", "div.l1 "], + ["VK_BACK_SPACE", "div.l1"], + ["VK_BACK_SPACE", "div.l"], + ["VK_BACK_SPACE", "div."], + ["VK_BACK_SPACE", "div"], + ["VK_BACK_SPACE", "di"], + ["VK_BACK_SPACE", "d"], + ["VK_BACK_SPACE", ""], +]; + +const TEST_URL = TEST_URL_ROOT + + "doc_inspector_search-suggestions.html"; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URL); + yield focusSearchBoxUsingShortcut(inspector.panelWin); + + for (let [key, query] of KEY_STATES) { + info("Pressing key " + key + " to get searchbox value as " + query); + + let done = inspector.searchSuggestions.once("processing-done"); + EventUtils.synthesizeKey(key, {}, inspector.panelWin); + yield done; + + is(inspector.searchBox.value, query, "The searchbox value is correct."); + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_select-docshell.js b/toolkit/devtools/inspector/test/browser_inspector_select-docshell.js new file mode 100644 index 000000000..58ca50943 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_select-docshell.js @@ -0,0 +1,77 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Test frame selection switching at toolbox level +// when using the inspector + +const FrameURL = "data:text/html;charset=UTF-8," + + encodeURI("<div id=\"frame\">frame</div>"); +const URL = "data:text/html;charset=UTF-8," + + encodeURI("<iframe src=\"" + FrameURL + "\"></iframe><div id=\"top\">top</div>"); + +add_task(function*() { + Services.prefs.setBoolPref("devtools.command-button-frames.enabled", true); + + let {toolbox, inspector} = yield openInspectorForURL(URL); + + // Verify we are on the top level document + let testNode = content.document.querySelector("#top"); + ok(testNode, "We have the test node on the top level document"); + + assertMarkupViewIsLoaded(inspector); + + // Verify that the frame list button is visible and populated + let btn = toolbox.doc.getElementById("command-button-frames"); + ok(!btn.firstChild.getAttribute("hidden"), "The frame list button is visible"); + let frameBtns = Array.slice(btn.firstChild.querySelectorAll("[data-window-id]")); + is(frameBtns.length, 2, "We have both frames in the list"); + frameBtns.sort(function (a, b) { + return a.getAttribute("label").localeCompare(b.getAttribute("label")); + }); + is(frameBtns[0].getAttribute("label"), FrameURL, "Got top level document in the list"); + is(frameBtns[1].getAttribute("label"), URL, "Got iframe document in the list"); + + // Listen to will-navigate to check if the view is empty + let willNavigate = toolbox.target.once("will-navigate").then(() => { + info("Navigation to the iframe has started, the inspector should be empty"); + assertMarkupViewIsEmpty(inspector); + }); + + let newRoot = inspector.once("new-root").then(() => { + info("Navigation to the iframe is done, the inspector should be back up"); + + // Verify we are on page one + //let testNode = content.frames[0].document.querySelector("#frame"); + let testNode = getNode("#frame", { document: content.frames[0].document}); + ok(testNode, "We have the test node on the iframe"); + + // On page 2 load, verify we have the right content + assertMarkupViewIsLoaded(inspector); + + return selectNode("#frame", inspector); + }); + + // Only select the iframe after we are able to select an element from the top + // level document. + yield selectNode("#top", inspector); + info("Select the iframe"); + frameBtns[0].click(); + + yield willNavigate; + yield newRoot; + + Services.prefs.clearUserPref("devtools.command-button-frames.enabled"); +}); + +function assertMarkupViewIsLoaded(inspector) { + let markupViewBox = inspector.panelDoc.getElementById("markup-box"); + is(markupViewBox.childNodes.length, 1, "The markup-view is loaded"); +} + +function assertMarkupViewIsEmpty(inspector) { + let markupViewBox = inspector.panelDoc.getElementById("markup-box"); + is(markupViewBox.childNodes.length, 0, "The markup-view is unloaded"); +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_select-last-selected.js b/toolkit/devtools/inspector/test/browser_inspector_select-last-selected.js new file mode 100644 index 000000000..0d8dd4bcb --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_select-last-selected.js @@ -0,0 +1,88 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Checks that the expected default node is selected after a page navigation or +// a reload. +let PAGE_1 = TEST_URL_ROOT + "doc_inspector_select-last-selected-01.html"; +let PAGE_2 = TEST_URL_ROOT + "doc_inspector_select-last-selected-02.html"; + +// An array of test cases with following properties: +// - url: URL to navigate to. If URL == content.location, reload instead. +// - nodeToSelect: a selector for a node to select before navigation. If null, +// whatever is selected stays selected. +// - selectedNode: a selector for a node that is selected after navigation. +let TEST_DATA = [ + { + url: PAGE_1, + nodeToSelect: "#id1", + selectedNode: "#id1" + }, + { + url: PAGE_1, + nodeToSelect: "#id2", + selectedNode: "#id2" + }, + { + url: PAGE_1, + nodeToSelect: "#id3", + selectedNode: "#id3" + }, + { + url: PAGE_1, + nodeToSelect: "#id4", + selectedNode: "#id4" + }, + { + url: PAGE_2, + nodeToSelect: null, + selectedNode: "body" + }, + { + url: PAGE_1, + nodeToSelect: "#id5", + selectedNode: "body" + }, + { + url: PAGE_2, + nodeToSelect: null, + selectedNode: "body" + } +]; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(PAGE_1); + + for (let { url, nodeToSelect, selectedNode } of TEST_DATA) { + if (nodeToSelect) { + info("Selecting node " + nodeToSelect + " before navigation."); + yield selectNode(nodeToSelect, inspector); + } + + yield navigateToAndWaitForNewRoot(url); + + info("Waiting for inspector to update after new-root event."); + yield inspector.once("inspector-updated"); + + let nodeFront = yield getNodeFront(selectedNode, inspector); + is(inspector.selection.nodeFront, nodeFront, + selectedNode + " is selected after navigation."); + } + + function navigateToAndWaitForNewRoot(url) { + info("Navigating and waiting for new-root event after navigation."); + let newRoot = inspector.once("new-root"); + + if (url == content.location) { + info("Reloading page."); + content.location.reload(); + } else { + info("Navigating to " + url); + content.location = url; + } + + return newRoot; + } +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_sidebarstate.js b/toolkit/devtools/inspector/test/browser_inspector_sidebarstate.js new file mode 100644 index 000000000..144ee41b2 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_sidebarstate.js @@ -0,0 +1,34 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const TEST_URI = "data:text/html;charset=UTF-8," + + "<h1>browser_inspector_sidebarstate.js</h1>"; + +add_task(function* () { + let { inspector, toolbox } = yield openInspectorForURL(TEST_URI); + + info("Selecting ruleview."); + inspector.sidebar.select("ruleview"); + + is(inspector.sidebar.getCurrentTabID(), "ruleview", + "Rule View is selected by default"); + + info("Selecting computed view."); + inspector.sidebar.select("computedview"); + + info("Closing inspector."); + yield toolbox.destroy(); + + info("Re-opening inspector."); + inspector = (yield openInspector()).inspector; + + if (!inspector.sidebar.getCurrentTabID()) { + info("Default sidebar still to be selected, adding select listener."); + yield inspector.sidebar.once("select"); + } + + is(inspector.sidebar.getCurrentTabID(), "computedview", + "Computed view is selected by default."); +}); diff --git a/toolkit/devtools/inspector/test/browser_inspector_switch-to-inspector-on-pick.js b/toolkit/devtools/inspector/test/browser_inspector_switch-to-inspector-on-pick.js new file mode 100644 index 000000000..53b2892ac --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_switch-to-inspector-on-pick.js @@ -0,0 +1,39 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Testing that clicking the pick button switches the toolbox to the inspector +// panel. + +const TEST_URI = "data:text/html;charset=UTF-8," + + "<p>Switch to inspector on pick</p>"; + +add_task(function* () { + let tab = yield addTab(TEST_URI); + let toolbox = yield openToolbox(tab); + + yield startPickerAndAssertSwitchToInspector(toolbox); + + info("Stoppping element picker."); + yield toolbox.highlighterUtils.stopPicker(); +}); + +function openToolbox(tab) { + info("Opening webconsole."); + let target = TargetFactory.forTab(tab); + return gDevTools.showToolbox(target, "webconsole"); +} + +function* startPickerAndAssertSwitchToInspector(toolbox) { + info("Clicking element picker button."); + let pickButton = toolbox.doc.querySelector("#command-button-pick"); + pickButton.click(); + + info("Waiting for inspector to be selected."); + yield toolbox.once("inspector-selected"); + is(toolbox.currentToolId, "inspector", "Switched to the inspector"); + + info("Waiting for inspector to update."); + yield toolbox.getCurrentPanel().once("inspector-updated"); +} diff --git a/toolkit/devtools/inspector/test/browser_inspector_update-on-navigation.js b/toolkit/devtools/inspector/test/browser_inspector_update-on-navigation.js new file mode 100644 index 000000000..038ec5a95 --- /dev/null +++ b/toolkit/devtools/inspector/test/browser_inspector_update-on-navigation.js @@ -0,0 +1,44 @@ +/* vim: set 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 that markup view handles page navigation correctly. + +const SCHEMA = "data:text/html;charset=UTF-8,"; +const URL_1 = SCHEMA + "<div id='one' style='color:red;'>ONE</div>"; +const URL_2 = SCHEMA + "<div id='two' style='color:green;'>TWO</div>"; + +add_task(function* () { + let { inspector, toolbox } = yield openInspectorForURL(URL_1); + + assertMarkupViewIsLoaded(); + yield selectNode("#one", inspector); + + let willNavigate = toolbox.target.once("will-navigate"); + content.location = URL_2; + + info("Waiting for will-navigate"); + yield willNavigate; + + info("Navigation to page 2 has started, the inspector should be empty"); + assertMarkupViewIsEmpty(); + + info("Waiting for new-root"); + yield inspector.once("new-root"); + + info("Navigation to page 2 was done, the inspector should be back up"); + assertMarkupViewIsLoaded(); + + yield selectNode("#two", inspector); + + function assertMarkupViewIsLoaded() { + let markupViewBox = inspector.panelDoc.getElementById("markup-box"); + is(markupViewBox.childNodes.length, 1, "The markup-view is loaded"); + } + + function assertMarkupViewIsEmpty() { + let markupViewBox = inspector.panelDoc.getElementById("markup-box"); + is(markupViewBox.childNodes.length, 0, "The markup-view is unloaded"); + } +}); diff --git a/toolkit/devtools/inspector/test/doc_frame_script.js b/toolkit/devtools/inspector/test/doc_frame_script.js new file mode 100644 index 000000000..3eb534900 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_frame_script.js @@ -0,0 +1,308 @@ +/* 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"; + +// A helper frame-script for brower/devtools/inspector tests. +// +// Most listeners in the script expect "Test:"-namespaced messages from chrome, +// then execute code upon receiving, and immediately send back a message. +// This is so that chrome test code can execute code in content and wait for a +// response. +// Some listeners do not send a response message back. + +let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +let {LayoutHelpers} = Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", {}); +let DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); +let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader); +let EventUtils = {}; +loader.loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils); + +/** + * If the test page creates and triggeres the custom event + * "test-page-processing-done", then the Test:TestPageProcessingDone message + * will be sent to the parent process for tests to wait for this event if needed. + */ +addEventListener("DOMWindowCreated", () => { + content.addEventListener("test-page-processing-done", () => { + sendAsyncMessage("Test:TestPageProcessingDone"); + }, false); +}); + +/** + * Given an actorID and connection prefix, get the corresponding actor from the + * debugger-server connection. + * @param {String} actorID + * @param {String} connPrefix + */ +function getHighlighterActor(actorID, connPrefix) { + let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); + if (!DebuggerServer.initialized) { + return; + } + + let conn = DebuggerServer._connections[connPrefix]; + if (!conn) { + return; + } + + return conn.getActor(actorID); +} + +/** + * Get the instance of CanvasFrameAnonymousContentHelper used by a given + * highlighter actor. + * The instance provides methods to get/set attributes/text/style on nodes of + * the highlighter, inserted into the nsCanvasFrame. + * @see /toolkit/devtools/server/actors/highlighter.js + * @param {String} actorID + * @param {String} connPrefix + */ +function getHighlighterCanvasFrameHelper(actorID, connPrefix) { + let actor = getHighlighterActor(actorID, connPrefix); + if (actor && actor._highlighter) { + return actor._highlighter.markup; + } +} + +/** + * Get a value for a given attribute name, on one of the elements of the box + * model highlighter, given its ID. + * @param {Object} msg The msg.data part expects the following properties + * - {String} nodeID The full ID of the element to get the attribute for + * - {String} name The name of the attribute to get + * - {String} actorID The highlighter actor ID + * - {String} connPrefix The highlighter actor ID's connection prefix + * @return {String} The value, if found, null otherwise + */ +addMessageListener("Test:GetHighlighterAttribute", function(msg) { + let {nodeID, name, actorID, connPrefix} = msg.data; + + let value; + let helper = getHighlighterCanvasFrameHelper(actorID, connPrefix); + if (helper) { + value = helper.getAttributeForElement(nodeID, name); + } + + sendAsyncMessage("Test:GetHighlighterAttribute", value); +}); + +/** + * Get the textcontent of one of the elements of the box model highlighter, + * given its ID. + * @param {Object} msg The msg.data part expects the following properties + * - {String} nodeID The full ID of the element to get the attribute for + * - {String} actorID The highlighter actor ID + * - {String} connPrefix The highlighter connection prefix + * @return {String} The textcontent value + */ +addMessageListener("Test:GetHighlighterTextContent", function(msg) { + let {nodeID, actorID, connPrefix} = msg.data; + + let value; + let helper = getHighlighterCanvasFrameHelper(actorID, connPrefix); + if (helper) { + value = helper.getTextContentForElement(nodeID); + } + + sendAsyncMessage("Test:GetHighlighterTextContent", value); +}); + +/** + * Get the number of box-model highlighters created by the SelectorHighlighter + * @param {Object} msg The msg.data part expects the following properties: + * - {String} actorID The highlighter actor ID + * - {String} connPrefix The highlighter connection prefix + * @return {Number} The number of box-model highlighters created, or null if the + * SelectorHighlighter was not found. + */ +addMessageListener("Test:GetSelectorHighlighterBoxNb", function(msg) { + let {actorID, connPrefix} = msg.data; + let {_highlighter: h} = getHighlighterActor(actorID, connPrefix); + if (!h || !h._highlighters) { + sendAsyncMessage("Test:GetSelectorHighlighterBoxNb", null); + } else { + sendAsyncMessage("Test:GetSelectorHighlighterBoxNb", h._highlighters.length); + } +}); + +/** + * Subscribe to the box-model highlighter's update event, modify an attribute of + * the currently highlighted node and send a message when the highlighter has + * updated. + * @param {Object} msg The msg.data part expects the following properties + * - {String} the name of the attribute to be changed + * - {String} the new value for the attribute + * - {String} actorID The highlighter actor ID + * - {String} connPrefix The highlighter connection prefix + */ +addMessageListener("Test:ChangeHighlightedNodeWaitForUpdate", function(msg) { + // The name and value of the attribute to be changed + let {name, value, actorID, connPrefix} = msg.data; + let {_highlighter: h} = getHighlighterActor(actorID, connPrefix); + + h.once("updated", () => { + sendAsyncMessage("Test:ChangeHighlightedNodeWaitForUpdate"); + }); + + h.currentNode.setAttribute(name, value); +}); + +/** + * Subscribe to a given highlighter event and respond when the event is received. + * @param {Object} msg The msg.data part expects the following properties + * - {String} event The name of the highlighter event to listen to + * - {String} actorID The highlighter actor ID + * - {String} connPrefix The highlighter connection prefix + */ +addMessageListener("Test:WaitForHighlighterEvent", function(msg) { + let {event, actorID, connPrefix} = msg.data; + let {_highlighter: h} = getHighlighterActor(actorID, connPrefix); + + h.once(event, () => { + sendAsyncMessage("Test:WaitForHighlighterEvent"); + }); +}); + +/** + * Change the zoom level of the page. + * Optionally subscribe to the box-model highlighter's update event and waiting + * for it to refresh before responding. + * @param {Object} msg The msg.data part expects the following properties + * - {Number} level The new zoom level + * - {String} actorID Optional. The highlighter actor ID + * - {String} connPrefix Optional. The highlighter connection prefix + */ +addMessageListener("Test:ChangeZoomLevel", function(msg) { + let {level, actorID, connPrefix} = msg.data; + dumpn("Zooming page to " + level); + + if (actorID) { + let {_highlighter: h} = getHighlighterActor(actorID, connPrefix); + h.once("updated", () => { + sendAsyncMessage("Test:ChangeZoomLevel"); + }); + } + + let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + docShell.contentViewer.fullZoom = level; + + if (!actorID) { + sendAsyncMessage("Test:ChangeZoomLevel"); + } +}); + +/** + * Get the element at the given x/y coordinates. + * @param {Object} msg The msg.data part expects the following properties + * - {Number} x + * - {Number} y + * @return {DOMNode} The CPOW of the element + */ +addMessageListener("Test:ElementFromPoint", function(msg) { + let {x, y} = msg.data; + dumpn("Getting the element at " + x + "/" + y); + + let helper = new LayoutHelpers(content); + let element = helper.getElementFromPoint(content.document, x, y); + sendAsyncMessage("Test:ElementFromPoint", null, {element}); +}); + +/** + * Get all box-model regions' adjusted boxquads for the given element + * @param {Object} msg The msg.data part should contain the node selector. + * @return {Object} An object with each property being a box-model region, each + * of them being an array of objects with the p1/p2/p3/p4 properties. + */ +addMessageListener("Test:GetAllAdjustedQuads", function(msg) { + let {selector} = msg.data; + let node = superQuerySelector(selector); + + let regions = {}; + + let helper = new LayoutHelpers(content); + for (let boxType of ["content", "padding", "border", "margin"]) { + regions[boxType] = helper.getAdjustedQuads(node, boxType); + } + + sendAsyncMessage("Test:GetAllAdjustedQuads", regions); +}); + +/** + * Synthesize a mouse event on an element. This handler doesn't send a message + * back. Consumers should listen to specific events on the inspector/highlighter + * to know when the event got synthesized. + * @param {Object} msg The msg.data part expects the following properties: + * - {Number} x + * - {Number} y + * - {Boolean} center If set to true, x/y will be ignored and + * synthesizeMouseAtCenter will be used instead + * - {Object} options Other event options + * The msg.objects part should be the element. + * @param {Object} data Event detail properties: + */ +addMessageListener("Test:SynthesizeMouse", function(msg) { + let {node} = msg.objects; + let {x, y, center, options, selector} = msg.data; + + if (!node && selector) { + node = superQuerySelector(selector); + } + + if (center) { + EventUtils.synthesizeMouseAtCenter(node, options, node.ownerDocument.defaultView); + } else { + EventUtils.synthesizeMouse(node, x, y, options, node.ownerDocument.defaultView); + } + + // Most consumers won't need to listen to this message, unless they want to + // wait for the mouse event to be synthesized and don't have another event + // to listen to instead. + sendAsyncMessage("Test:SynthesizeMouse"); +}); + +/** + * Check that an element currently has a pseudo-class lock. + * @param {Object} msg The msg.data part expects the following properties: + * - {String} pseudo The pseudoclass to check for + * The msg.objects part should be the element. + * @param {Object} + * @return {Boolean} + */ +addMessageListener("Test:HasPseudoClassLock", function(msg) { + let {node} = msg.objects; + let {pseudo} = msg.data + sendAsyncMessage("Test:HasPseudoClassLock", DOMUtils.hasPseudoClassLock(node, pseudo)); +}); + +/** + * Like document.querySelector but can go into iframes too. + * ".container iframe || .sub-container div" will first try to find the node + * matched by ".container iframe" in the root document, then try to get the + * content document inside it, and then try to match ".sub-container div" inside + * this document. + * Any selector coming before the || separator *MUST* match a frame node. + * @param {String} superSelector. + * @return {DOMNode} The node, or null if not found. + */ +function superQuerySelector(superSelector, root=content.document) { + let frameIndex = superSelector.indexOf("||"); + if (frameIndex === -1) { + return root.querySelector(superSelector); + } else { + let rootSelector = superSelector.substring(0, frameIndex).trim(); + let childSelector = superSelector.substring(frameIndex+2).trim(); + root = root.querySelector(rootSelector); + if (!root || !root.contentWindow) { + return null; + } + + return superQuerySelector(childSelector, root.contentWindow.document); + } +} + +let dumpn = msg => dump(msg + "\n"); diff --git a/toolkit/devtools/inspector/test/doc_inspector_breadcrumbs.html b/toolkit/devtools/inspector/test/doc_inspector_breadcrumbs.html new file mode 100644 index 000000000..ce306d5f6 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_breadcrumbs.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html> + <head> + <style> + div { + min-height: 10px; min-width: 10px; + border: 1px solid red; + margin: 10px; + } + #pseudo-container::before { + content: 'before'; + } + #pseudo-container::after { + content: 'after'; + } + </style> + </head> + <body> + <article id="i1"> + <div id="i11"> + <div id="i111"> + <div id="i1111"> + </div> + </div> + </div> + </article> + <article id="i2"> + <div id="i21"> + <div id="i211"> + <div id="i2111"> + </div> + </div> + </div> + <div id="i22"> + <div id="i221"> + </div> + <div id="i222"> + <div id="i2221"> + <div id="i22211"> + </div> + </div> + </div> + </div> + </article> + <div id='pseudo-container'></div> + </body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_delete-selected-node-01.html b/toolkit/devtools/inspector/test/doc_inspector_delete-selected-node-01.html new file mode 100644 index 000000000..70edbd936 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_delete-selected-node-01.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> + +<h1>mop</h1> +<iframe src="data:text/html;charset=utf-8,<!DOCTYPE HTML>%0D%0A<h1>kill me<span>.</span><%2Fh1>"></iframe> diff --git a/toolkit/devtools/inspector/test/doc_inspector_delete-selected-node-02.html b/toolkit/devtools/inspector/test/doc_inspector_delete-selected-node-02.html new file mode 100644 index 000000000..4d1ed5173 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_delete-selected-node-02.html @@ -0,0 +1,15 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>node delete - reset selection - test</title> +</head> +<body> + <ul id="deleteChildren"> + <li id="deleteManually">Delete me via the inspector</li> + <li id="selectedAfterDelete">This node is selected after manual delete</li> + <li id="deleteAutomatically">Delete me via javascript</li> + </ul> + <iframe id="deleteIframe" src="data:text/html,%3C!DOCTYPE%20html%3E%3Chtml%20lang%3D%22en%22%3E%3Cbody%3E%3Cp%20id%3D%22deleteInIframe%22%3EDelete my container iframe%3C%2Fp%3E%3C%2Fbody%3E%3C%2Fhtml%3E"></iframe> +</body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_gcli-inspect-command.html b/toolkit/devtools/inspector/test/doc_inspector_gcli-inspect-command.html new file mode 100644 index 000000000..a7d28828c --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_gcli-inspect-command.html @@ -0,0 +1,25 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>GCLI inspect command test</title> +</head> +<body> + + <!-- This is a list of 0 h1 elements --> + + <!-- This is a list of 1 div elements --> + <div>Hello, I'm a div</div> + + <!-- This is a list of 2 span elements --> + <span>Hello, I'm a span</span> + <span>And me</span> + + <!-- This is a collection of various things that match only once --> + <p class="someclass">.someclass</p> + <p id="someid">#someid</p> + <button disabled>button[disabled]</button> + <p><strong>p>strong</strong></p> + +</body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_highlight_after_transition.html b/toolkit/devtools/inspector/test/doc_inspector_highlight_after_transition.html new file mode 100644 index 000000000..b2ba0b066 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_highlight_after_transition.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + + <style> + div { + opacity: 0; + height: 0; + background: red; + border-top: 1px solid #888; + transition-property: height, opacity; + transition-duration: 3000ms; + transition-timing-function: ease-in-out, ease-in-out, linear; + } + + div[visible] { + opacity: 1; + height: 200px; + } + </style> +</head> +<body> + <div></div> +</body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_highlighter-comments.html b/toolkit/devtools/inspector/test/doc_inspector_highlighter-comments.html new file mode 100644 index 000000000..b7cd6c517 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_highlighter-comments.html @@ -0,0 +1,16 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Inspector Highlighter Test</title> +</head> +<body> + <p></p> + <div id="id1">Visible div 1</div> + <!-- Invisible comment node --> + <div id="id2">Visible div 2</div> + <script type="text/javascript">/*Invisible script node*/</script> + <div id="id3">Visible div 3</div> + <div id="id4" style="display:none;">Invisible div node</div> +</body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_highlighter.html b/toolkit/devtools/inspector/test/doc_inspector_highlighter.html new file mode 100644 index 000000000..376a9c714 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_highlighter.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <style> + div { + position:absolute; + } + + #simple-div { + padding: 5px; + border: 7px solid red; + margin: 9px; + top: 30px; + left: 150px; + } + + #rotated-div { + padding: 5px; + border: 7px solid red; + margin: 9px; + transform: rotate(45deg); + top: 30px; + left: 80px; + } + + #widthHeightZero-div { + top: 30px; + left: 10px; + width: 0; + height: 0; + } + </style> + </head> + <body> + <div id="simple-div">Gort! Klaatu barada nikto!</div> + <div id="rotated-div"></div> + <div id="widthHeightZero-div">Width & height = 0</div> + </body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_highlighter_csstransform.html b/toolkit/devtools/inspector/test/doc_inspector_highlighter_csstransform.html new file mode 100644 index 000000000..cfa2761d7 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_highlighter_csstransform.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>css transform highlighter test</title> + <style type="text/css"> + #test-node { + position: absolute; + top: 0; + left: 0; + + width: 300px; + height: 300px; + + transform: rotate(90deg) skew(13deg) scale(.8) translateX(50px); + transform-origin: 50%; + + background: linear-gradient(green, yellow); + } + </style> +</head> +<body> + <div id="test-node"></div> +</body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_highlighter_inline.html b/toolkit/devtools/inspector/test/doc_inspector_highlighter_inline.html new file mode 100644 index 000000000..e1aa5bb1f --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_highlighter_inline.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <style> + html { + height: 100%; + background: #eee; + } + body { + margin: 0 auto; + padding: 1em; + box-sizing: border-box; + width: 500px; + height: 100%; + background: white; + font-family: Arial; + font-size: 15px; + line-height: 40px; + } + p span { + padding: 5px 0; + margin: 0 5px; + border: 5px solid #eee; + } + </style> + </head> + <body> + <h1>Lorem Ipsum</h1> + <h2>Lorem ipsum <em>dolor sit amet</em></h2> + <p><span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, nisl eget semper maximus, dui tellus tempor leo, at pharetra eros tortor sed odio. Nullam sagittis ex nec mi sagittis pulvinar. Pellentesque dapibus feugiat fermentum. Curabitur lacinia quis enim et tristique. Aliquam in semper massa. In ac vulputate nunc, at rutrum neque. Fusce condimentum, tellus quis placerat imperdiet, dolor tortor mattis erat, nec luctus magna diam pharetra mauris.</span></p> + <div dir="rtl"> + <span><span></span>some ltr text in an rtl container</span> + </div> + </body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_highlighter_rect.html b/toolkit/devtools/inspector/test/doc_inspector_highlighter_rect.html new file mode 100644 index 000000000..4d23d52fd --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_highlighter_rect.html @@ -0,0 +1,22 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>rect highlighter parent test page</title> + <style type="text/css"> + body { + margin: 50px; + border: 10px solid red; + } + + iframe { + border: 10px solid yellow; + padding: 0; + margin: 50px; + } + </style> +</head> +<body> + <iframe src="doc_inspector_highlighter_rect_iframe.html"></iframe> +</body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_highlighter_rect_iframe.html b/toolkit/devtools/inspector/test/doc_inspector_highlighter_rect_iframe.html new file mode 100644 index 000000000..d59050f69 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_highlighter_rect_iframe.html @@ -0,0 +1,15 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>rect highlighter child test page</title> + <style type="text/css"> + body { + margin: 0; + } + </style> +</head> +<body> + +</body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_infobar.html b/toolkit/devtools/inspector/test/doc_inspector_infobar.html new file mode 100644 index 000000000..137b3487f --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_infobar.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + + <style> + body { + width: 100%; + height: 100%; + } + + div { + position: absolute; + height: 100px; + width: 500px; + } + + #bottom { + bottom: 0px; + } + + #vertical { + height: 100%; + } + + #farbottom { + top: 2000px; + background: red; + } + + #abovetop { + top: -123px; + }"; + </style> +</head> +<body> + <div id="abovetop"></div> + <div id="vertical"></div> + <div id="top" class="class1 class2"></div> + <div id="bottom"></div> + <div id="farbottom"></div> +</body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_infobar_01.html b/toolkit/devtools/inspector/test/doc_inspector_infobar_01.html new file mode 100644 index 000000000..3decad179 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_infobar_01.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + + <style> + body { + width: 100%; + height: 100%; + } + div { + position: absolute; + height: 100px; + width: 500px; + } + + #bottom { + bottom: 0px; + background: blue; + } + + #vertical { + height: 100%; + background: green; + } + </style> + </head> + <body> + <div id="vertical">Vertical</div> + <div id="top" class="class1 class2">Top</div> + <div id="bottom">Bottom</div> + </body> + </html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_infobar_02.html b/toolkit/devtools/inspector/test/doc_inspector_infobar_02.html new file mode 100644 index 000000000..ed1843f8d --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_infobar_02.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + + <style> + body { + width: 100%; + height: 100%; + } + + div { + position: absolute; + height: 100px; + width: 500px; + } + + #below-bottom { + bottom: -200px; + background: red; + } + + #above-top { + top: -200px; + background: black; + color: white; + }"; + </style> +</head> +<body> + <div id="above-top">Above top</div> + <div id="below-bottom">Far bottom</div> +</body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_menu.html b/toolkit/devtools/inspector/test/doc_inspector_menu.html new file mode 100644 index 000000000..13dbed313 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_menu.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> + <head> + <title>Inspector Tree Menu Test</title> + <meta charset="utf-8"> + </head> + <body> + <div> + <div id="paste-area"> + <h1>Inspector Tree Menu Test</h1> + <p class="inner">Unset</p> + <p class="adjacent"> + <span class="ref">3</span> + </p> + </div> + <p data-id="copy">Paragraph for testing copy</p> + <p id="sensitivity">Paragraph for sensitivity</p> + <p id="delete">This has to be deleted</p> + <img id="copyimage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==" /> + </div> + </body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_remove-iframe-during-load.html b/toolkit/devtools/inspector/test/doc_inspector_remove-iframe-during-load.html new file mode 100644 index 000000000..f6c2fd67c --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_remove-iframe-during-load.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>iframe creation/deletion test</title> +</head> +<body> + <div id="yay"></div> + <script type="text/javascript"> + var yay = document.querySelector("#yay"); + yay.textContent = "nothing"; + + // Create a custom event to let the test know when the window has finished + // loading. + var event = new Event("test-page-processing-done"); + + // Create/remove an iframe before load. + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + iframe.remove(); + yay.textContent = "before events"; + + // Create/remove an iframe on DOMContentLoaded. + document.addEventListener("DOMContentLoaded", function() { + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + iframe.remove(); + yay.textContent = "DOMContentLoaded"; + }); + + // Create/remove an iframe on window load. + window.addEventListener("load", function() { + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + iframe.remove(); + yay.textContent = "load"; + + // Dispatch the done event. + window.dispatchEvent(event); + }); + </script> +</body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_search-suggestions.html b/toolkit/devtools/inspector/test/doc_inspector_search-suggestions.html new file mode 100644 index 000000000..a84a2e3d4 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_search-suggestions.html @@ -0,0 +1,27 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Inspector Search Box Test</title> +</head> +<body> + <div id="d1"> + <div class="l1"> + <div id="d2" class="c1">Hello, I'm nested div</div> + </div> + </div> + <span id="s1">Hello, I'm a span + <div class="l1"> + <span>Hi I am a nested span</span> + <span class="s4">Hi I am a nested classed span</span> + </div> + </span> + <span class="c1" id="s2">And me</span> + + <p class="c1" id="p1">.someclass</p> + <p id="p2">#someid</p> + <button id="b1" disabled>button[disabled]</button> + <p id="p3" class="c2"><strong>p>strong</strong></p> + +</body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_search.html b/toolkit/devtools/inspector/test/doc_inspector_search.html new file mode 100644 index 000000000..262eb0be6 --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_search.html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Inspector Search Box Test</title> +</head> +<body> + + <!-- This is a list of 0 h1 elements --> + + <!-- This is a list of 2 div elements --> + <div id="d1">Hello, I'm a div</div> + <div id="d2" class="c1">Hello, I'm another div</div> + + <!-- This is a list of 2 span elements --> + <span id="s1">Hello, I'm a span</span> + <span class="c1" id="s2">And me</span> + + <!-- This is a collection of various things that match only once --> + <p class="c1" id="p1">.someclass</p> + <p id="p2">#someid</p> + <button id="b1" disabled>button[disabled]</button> + <p id="p3" class="c2"><strong>p>strong</strong></p> + +</body> +</html> diff --git a/toolkit/devtools/inspector/test/doc_inspector_select-last-selected-01.html b/toolkit/devtools/inspector/test/doc_inspector_select-last-selected-01.html new file mode 100644 index 000000000..fbe1251cb --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_select-last-selected-01.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <title>select last selected test</title> + </head> + <body> + <div id="id1"></div> + <div id="id2"></div> + <div id="id3"> + <ul class="aList"> + <li class="item"></li> + <li class="item"></li> + <li class="item"></li> + <li class="item"> + <span id="id4"></span> + </li> + </ul> + </div> + </body> +</html>
\ No newline at end of file diff --git a/toolkit/devtools/inspector/test/doc_inspector_select-last-selected-02.html b/toolkit/devtools/inspector/test/doc_inspector_select-last-selected-02.html new file mode 100644 index 000000000..2fbef312c --- /dev/null +++ b/toolkit/devtools/inspector/test/doc_inspector_select-last-selected-02.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <title>select last selected test</title> + </head> + <body> + <div id="id5"></div> + </body> +</html>
\ No newline at end of file diff --git a/toolkit/devtools/inspector/test/head.js b/toolkit/devtools/inspector/test/head.js new file mode 100644 index 000000000..6b76f776b --- /dev/null +++ b/toolkit/devtools/inspector/test/head.js @@ -0,0 +1,784 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const Cu = Components.utils; +const Ci = Components.interfaces; +const Cc = Components.classes; + +// Services.prefs.setBoolPref("devtools.debugger.log", true); +// SimpleTest.registerCleanupFunction(() => { +// Services.prefs.clearUserPref("devtools.debugger.log"); +// }); + +// Uncomment this pref to dump all devtools emitted events to the console. +// Services.prefs.setBoolPref("devtools.dump.emit", true); + +const TEST_URL_ROOT = "http://example.com/browser/browser/devtools/inspector/test/"; +const ROOT_TEST_DIR = getRootDirectory(gTestPath); +const FRAME_SCRIPT_URL = ROOT_TEST_DIR + "doc_frame_script.js"; +const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); + +// All test are asynchronous +waitForExplicitFinish(); + +let {TargetFactory, require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools; +let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {}); + +// Import the GCLI test helper +let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); +Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this); + +gDevTools.testing = true; +registerCleanupFunction(() => { + gDevTools.testing = false; +}); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.dump.emit"); + Services.prefs.clearUserPref("devtools.inspector.activeSidebar"); +}); + +registerCleanupFunction(function*() { + let target = TargetFactory.forTab(gBrowser.selectedTab); + yield gDevTools.closeToolbox(target); + + // Move the mouse outside inspector. If the test happened fake a mouse event + // somewhere over inspector the pointer is considered to be there when the + // next test begins. This might cause unexpected events to be emitted when + // another test moves the mouse. + EventUtils.synthesizeMouseAtPoint(1, 1, {type: "mousemove"}, window); + + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } +}); + +/** + * 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 + */ +let addTab = Task.async(function* (url) { + info("Adding a new tab with URL: '" + url + "'"); + + window.focus(); + + let tab = gBrowser.selectedTab = gBrowser.addTab(url); + let browser = tab.linkedBrowser; + + info("Loading the helper frame script " + FRAME_SCRIPT_URL); + browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false); + + yield once(browser, "load", true); + info("URL '" + url + "' loading complete"); + + return tab; +}); + +/** + * Simple DOM node accesor function that takes either a node or a string css + * selector as argument and returns the corresponding node + * @param {String|DOMNode} nodeOrSelector + * @param {Object} options + * An object containing any of the following options: + * - document: HTMLDocument that should be queried for the selector. + * Default: content.document. + * - expectNoMatch: If true and a node matches the given selector, a + * failure is logged for an unexpected match. + * If false and nothing matches the given selector, a + * failure is logged for a missing match. + * Default: false. + * @return {DOMNode} + */ +function getNode(nodeOrSelector, options = {}) { + let document = options.document || content.document; + let noMatches = !!options.expectNoMatch; + + if (typeof nodeOrSelector === "string") { + info("Looking for a node that matches selector " + nodeOrSelector); + let node = document.querySelector(nodeOrSelector); + if (noMatches) { + ok(!node, "Selector " + nodeOrSelector + " didn't match any nodes."); + } + else { + ok(node, "Selector " + nodeOrSelector + " matched a node."); + } + + return node; + } + + info("Looking for a node but selector was not a string."); + return nodeOrSelector; +} + +/** + * Highlight a node and set the inspector's current selection to the node or + * the first match of the given css selector. + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector + * The instance of InspectorPanel currently loaded in the toolbox + * @return a promise that resolves when the inspector is updated with the new + * node + */ +function selectAndHighlightNode(selector, inspector) { + info("Highlighting and selecting the node " + selector); + return selectNode(selector, inspector, "test-highlight"); +} + +/** + * Set the inspector's current selection to the first match of the given css + * selector + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @param {String} reason Defaults to "test" which instructs the inspector not + * to highlight the node upon selection + * @return {Promise} Resolves when the inspector is updated with the new node + */ +let selectNode = Task.async(function*(selector, inspector, reason="test") { + info("Selecting the node for '" + selector + "'"); + let nodeFront = yield getNodeFront(selector, inspector); + let updated = inspector.once("inspector-updated"); + inspector.selection.setNodeFront(nodeFront, reason); + yield updated; +}); + +/** + * Open the inspector in a tab with given URL. + * @param {string} url The URL to open. + * @return A promise that is resolved once the tab and inspector have loaded + * with an object: { tab, toolbox, inspector }. + */ +let openInspectorForURL = Task.async(function* (url) { + let tab = yield addTab(url); + let { inspector, toolbox } = yield openInspector(); + return { tab, inspector, toolbox }; +}); + +/** + * Open the toolbox, with the inspector tool visible. + * @param {Function} cb Optional callback, if you don't want to use the returned + * promise + * @return a promise that resolves when the inspector is ready + */ +let openInspector = Task.async(function*(cb) { + info("Opening the inspector"); + let target = TargetFactory.forTab(gBrowser.selectedTab); + + let inspector, toolbox; + + // Checking if the toolbox and the inspector are already loaded + // The inspector-updated event should only be waited for if the inspector + // isn't loaded yet + toolbox = gDevTools.getToolbox(target); + if (toolbox) { + inspector = toolbox.getPanel("inspector"); + if (inspector) { + info("Toolbox and inspector already open"); + if (cb) { + return cb(inspector, toolbox); + } else { + return { + toolbox: toolbox, + inspector: inspector + }; + } + } + } + + info("Opening the toolbox"); + toolbox = yield gDevTools.showToolbox(target, "inspector"); + yield waitForToolboxFrameFocus(toolbox); + inspector = toolbox.getPanel("inspector"); + + info("Waiting for the inspector to update"); + yield inspector.once("inspector-updated"); + + if (cb) { + return cb(inspector, toolbox); + } else { + return { + toolbox: toolbox, + inspector: inspector + }; + } +}); + +/** + * Wait for the toolbox frame to receive focus after it loads + * @param {Toolbox} toolbox + * @return a promise that resolves when focus has been received + */ +function waitForToolboxFrameFocus(toolbox) { + info("Making sure that the toolbox's frame is focused"); + let def = promise.defer(); + let win = toolbox.frame.contentWindow; + waitForFocus(def.resolve, win); + return def.promise; +} + +function getActiveInspector() { + let target = TargetFactory.forTab(gBrowser.selectedTab); + return gDevTools.getToolbox(target).getPanel("inspector"); +} + +/** + * Get the NodeFront for a node that matches a given css selector, via the + * protocol. + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @return {Promise} Resolves to the NodeFront instance + */ +function getNodeFront(selector, {walker}) { + if (selector._form) { + return selector; + } + return walker.querySelector(walker.rootNode, selector); +} + +/** + * Get the NodeFront for a node that matches a given css selector inside a + * given iframe. + * @param {String|NodeFront} selector + * @param {String|NodeFront} frameSelector A selector that matches the iframe + * the node is in + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @param {String} reason Defaults to "test" which instructs the inspector not + * to highlight the node upon selection + * @return {Promise} Resolves when the inspector is updated with the new node + */ +let getNodeFrontInFrame = Task.async(function*(selector, frameSelector, + inspector, reason="test") { + let iframe = yield getNodeFront(frameSelector, inspector); + let {nodes} = yield inspector.walker.children(iframe); + return inspector.walker.querySelector(nodes[0], selector); +}); + +/** + * Get the current rect of the border region of the box-model highlighter + */ +let getSimpleBorderRect = Task.async(function*(toolbox) { + let {border} = yield getBoxModelStatus(toolbox); + let {p1, p2, p3, p4} = border.points; + + return { + top: p1.y, + left: p1.x, + width: p2.x - p1.x, + height: p4.y - p1.y + }; +}); + +function getHighlighterActorID(highlighter) { + let actorID = highlighter.actorID; + let connPrefix = actorID.substring(0, actorID.indexOf(highlighter.typeName)); + + return {actorID, connPrefix}; +} + +/** + * Get the current positions and visibility of the various box-model highlighter + * elements. + */ +let getBoxModelStatus = Task.async(function*(toolbox) { + let isVisible = yield isHighlighting(toolbox); + + let ret = { + visible: isVisible + }; + + for (let region of ["margin", "border", "padding", "content"]) { + let points = yield getPointsForRegion(region, toolbox); + let visible = yield isRegionHidden(region, toolbox); + ret[region] = {points, visible}; + } + + ret.guides = {}; + for (let guide of ["top", "right", "bottom", "left"]) { + ret.guides[guide] = yield getGuideStatus(guide, toolbox); + } + + return ret; +}); + +/** + * Get data about one of the toolbox box-model highlighter's guides. + * @param {String} location One of top, right, bottom, left. + * @param {Toolbox} toolbox The toolbox instance, used to retrieve the highlighter. + * @return {Object} The returned object has the following form: + * - visible {Boolean} Whether that guide is visible. + * - x1/y1/x2/y2 {String} The <line>'s coordinates. + */ +let getGuideStatus = Task.async(function*(location, {highlighter}) { + let id = "box-model-guide-" + location; + + let hidden = yield getHighlighterNodeAttribute(highlighter, id, "hidden"); + let x1 = yield getHighlighterNodeAttribute(highlighter, id, "x1"); + let y1 = yield getHighlighterNodeAttribute(highlighter, id, "y1"); + let x2 = yield getHighlighterNodeAttribute(highlighter, id, "x2"); + let y2 = yield getHighlighterNodeAttribute(highlighter, id, "y2"); + + return { + visible: !hidden, + x1: x1, + y1: y1, + x2: x2, + y2: y2 + }; +}); + +/** + * Get the coordinates of the rectangle that is defined by the 4 guides displayed + * in the toolbox box-model highlighter. + * @param {Toolbox} toolbox The toolbox instance, used to retrieve the highlighter. + * @return {Object} Null if at least one guide is hidden. Otherwise an object + * with p1, p2, p3, p4 properties being {x, y} objects. + */ +let getGuidesRectangle = Task.async(function*(toolbox) { + let tGuide = yield getGuideStatus("top", toolbox); + let rGuide = yield getGuideStatus("right", toolbox); + let bGuide = yield getGuideStatus("bottom", toolbox); + let lGuide = yield getGuideStatus("left", toolbox); + + if (!tGuide.visible || !rGuide.visible || !bGuide.visible || !lGuide.visible) { + return null; + } + + return { + p1: {x: lGuide.x1, y: tGuide.y1}, + p2: {x: rGuide.x1, y: tGuide. y1}, + p3: {x: rGuide.x1, y: bGuide.y1}, + p4: {x: lGuide.x1, y: bGuide.y1} + }; +}); + +/** + * Get the coordinate (points defined by the d attribute) from one of the path + * elements in the box model highlighter. + */ +let getPointsForRegion = Task.async(function*(region, toolbox) { + let d = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-" + region, "d"); + let polygons = d.match(/M[^M]+/g); + if (!polygons) { + return null; + } + + let points = polygons[0].trim().split(" ").map(i => { + return i.replace(/M|L/, "").split(",") + }); + + return { + p1: { + x: parseFloat(points[0][0]), + y: parseFloat(points[0][1]) + }, + p2: { + x: parseFloat(points[1][0]), + y: parseFloat(points[1][1]) + }, + p3: { + x: parseFloat(points[2][0]), + y: parseFloat(points[2][1]) + }, + p4: { + x: parseFloat(points[3][0]), + y: parseFloat(points[3][1]) + } + }; +}); + +/** + * Is a given region path element of the box-model highlighter currently + * hidden? + */ +let isRegionHidden = Task.async(function*(region, toolbox) { + let value = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-" + region, "hidden"); + return value !== null; +}); + +/** + * Is the highlighter currently visible on the page? + */ +let isHighlighting = Task.async(function*(toolbox) { + let value = yield getHighlighterNodeAttribute(toolbox.highlighter, + "box-model-elements", "hidden"); + return value === null; +}); + +let getHighlitNode = Task.async(function*(toolbox) { + let {visible, content} = yield getBoxModelStatus(toolbox); + let points = content.points; + if (visible) { + let x = (points.p1.x + points.p2.x + points.p3.x + points.p4.x) / 4; + let y = (points.p1.y + points.p2.y + points.p3.y + points.p4.y) / 4; + + let {objects} = yield executeInContent("Test:ElementFromPoint", {x, y}); + return objects.element; + } +}); + +/** + * Assert that the box-model highlighter's current position corresponds to the + * given node boxquads. + * @param {String} selector The selector for the node to get the boxQuads from + * @param {String} prefix An optional prefix for logging information to the + * console. + */ +let isNodeCorrectlyHighlighted = Task.async(function*(selector, toolbox, prefix="") { + let boxModel = yield getBoxModelStatus(toolbox); + let {data: regions} = yield executeInContent("Test:GetAllAdjustedQuads", + {selector}); + + for (let boxType of ["content", "padding", "border", "margin"]) { + let [quad] = regions[boxType]; + for (let point in boxModel[boxType].points) { + is(boxModel[boxType].points[point].x, quad[point].x, + "Node " + selector + " " + boxType + " point " + point + + " x coordinate is correct"); + is(boxModel[boxType].points[point].y, quad[point].y, + "Node " + selector + " " + boxType + " point " + point + + " y coordinate is correct"); + } + } +}); + +function synthesizeKeyFromKeyTag(aKeyId, aDocument = null) { + let document = aDocument || document; + let key = document.getElementById(aKeyId); + isnot(key, null, "Successfully retrieved the <key> node"); + + let modifiersAttr = key.getAttribute("modifiers"); + + let name = null; + + if (key.getAttribute("keycode")) + name = key.getAttribute("keycode"); + else if (key.getAttribute("key")) + name = key.getAttribute("key"); + + isnot(name, null, "Successfully retrieved keycode/key"); + + let modifiers = { + shiftKey: modifiersAttr.match("shift"), + ctrlKey: modifiersAttr.match("ctrl"), + altKey: modifiersAttr.match("alt"), + metaKey: modifiersAttr.match("meta"), + accelKey: modifiersAttr.match("accel") + } + + EventUtils.synthesizeKey(name, modifiers); +} + +let focusSearchBoxUsingShortcut = Task.async(function* (panelWin, callback) { + info("Focusing search box"); + let searchBox = panelWin.document.getElementById("inspector-searchbox"); + let focused = once(searchBox, "focus"); + + panelWin.focus(); + synthesizeKeyFromKeyTag("nodeSearchKey", panelWin.document); + + yield focused; + + if (callback) { + callback(); + } +}); + +/** + * Get the MarkupContainer object instance that corresponds to the given + * NodeFront + * @param {NodeFront} nodeFront + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @return {MarkupContainer} + */ +function getContainerForNodeFront(nodeFront, {markup}) { + return markup.getContainer(nodeFront); +} + +/** + * Get the MarkupContainer object instance that corresponds to the given + * selector + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @return {MarkupContainer} + */ +let getContainerForSelector = Task.async(function*(selector, inspector) { + info("Getting the markup-container for node " + selector); + let nodeFront = yield getNodeFront(selector, inspector); + let container = getContainerForNodeFront(nodeFront, inspector); + info("Found markup-container " + container); + return container; +}); + +/** + * Simulate a mouse-over on the markup-container (a line in the markup-view) + * that corresponds to the selector passed. + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @return {Promise} Resolves when the container is hovered and the higlighter + * is shown on the corresponding node + */ +let hoverContainer = Task.async(function*(selector, inspector) { + info("Hovering over the markup-container for node " + selector); + + let nodeFront = yield getNodeFront(selector, inspector); + let container = getContainerForNodeFront(nodeFront, inspector); + + let highlit = inspector.toolbox.once("node-highlight"); + EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousemove"}, + inspector.markup.doc.defaultView); + return highlit; +}); + +/** + * Simulate a click on the markup-container (a line in the markup-view) + * that corresponds to the selector passed. + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @return {Promise} Resolves when the node has been selected. + */ +let clickContainer = Task.async(function*(selector, inspector) { + info("Clicking on the markup-container for node " + selector); + + let nodeFront = yield getNodeFront(selector, inspector); + let container = getContainerForNodeFront(nodeFront, inspector); + + let updated = inspector.once("inspector-updated"); + EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousedown"}, + inspector.markup.doc.defaultView); + EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mouseup"}, + inspector.markup.doc.defaultView); + return updated; +}); + +/** + * Zoom the current page to a given level. + * @param {Number} level The new zoom level. + * @param {String} actorID Optional highlighter actor ID. If provided, the + * returned promise will only resolve when the highlighter has updated to the + * new zoom level. + * @return {Promise} + */ +let zoomPageTo = Task.async(function*(level, actorID, connPrefix) { + yield executeInContent("Test:ChangeZoomLevel", + {level, actorID, connPrefix}); +}); + +/** + * Get the value of an attribute on one of the highlighter's node. + * @param {Front} highlighter The front of the highlighter. + * @param {String} nodeID The Id of the node in the highlighter. + * @param {String} name The name of the attribute. + * @return {String} value + */ +let getHighlighterNodeAttribute = Task.async(function*(highlighter, nodeID, name) { + let {actorID, connPrefix} = getHighlighterActorID(highlighter); + let {data: value} = yield executeInContent("Test:GetHighlighterAttribute", + {nodeID, name, actorID, connPrefix}); + return value; +}); + +/** + * Get the "d" attribute value for one of the box-model highlighter's region + * <path> elements, and parse it to a list of points. + * @param {String} region The box model region name. + * @param {Front} highlighter The front of the highlighter. + * @return {Object} The object returned has the following form: + * - d {String} the d attribute value + * - points {Array} an array of all the polygons defined by the path. Each box + * is itself an Array of points, themselves being [x,y] coordinates arrays. + */ +let getHighlighterRegionPath = Task.async(function*(region, highlighter) { + let d = yield getHighlighterNodeAttribute(highlighter, "box-model-" + region, "d"); + if (!d) { + return {d: null}; + } + + let polygons = d.match(/M[^M]+/g); + if (!polygons) { + return {d}; + } + + let points = []; + for (let polygon of polygons) { + points.push(polygon.trim().split(" ").map(i => { + return i.replace(/M|L/, "").split(",") + })); + } + + return {d, points}; +}); + +/** + * Get the textContent value of one of the highlighter's node. + * @param {Front} highlighter The front of the highlighter. + * @param {String} nodeID The Id of the node in the highlighter. + * @return {String} value + */ +let getHighlighterNodeTextContent = Task.async(function*(highlighter, nodeID) { + let {actorID, connPrefix} = getHighlighterActorID(highlighter); + let {data: value} = yield executeInContent("Test:GetHighlighterTextContent", + {nodeID, actorID, connPrefix}); + return value; +}); + +/** + * Subscribe to a given highlighter event and return a promise that resolves + * when the event is received. + * @param {String} event The name of the highlighter event to listen to. + * @param {Front} highlighter The front of the highlighter. + * @return {Promise} + */ +function waitForHighlighterEvent(event, highlighter) { + let {actorID, connPrefix} = getHighlighterActorID(highlighter); + return executeInContent("Test:WaitForHighlighterEvent", + {event, actorID, connPrefix}); +} + +/** + * Simulate the mouse leaving the markup-view area + * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox + * @return a promise when done + */ +function mouseLeaveMarkupView(inspector) { + info("Leaving the markup-view area"); + let def = promise.defer(); + + // Find another element to mouseover over in order to leave the markup-view + let btn = inspector.toolbox.doc.querySelector(".toolbox-dock-button"); + + EventUtils.synthesizeMouseAtCenter(btn, {type: "mousemove"}, + inspector.toolbox.doc.defaultView); + executeSoon(def.resolve); + + return def.promise; +} + +/** + * Wait for eventName on target. + * @param {Object} target An observable object that either supports on/off or + * addEventListener/removeEventListener + * @param {String} eventName + * @param {Boolean} useCapture Optional, for addEventListener/removeEventListener + * @return A promise that resolves when the event has been handled + */ +function once(target, eventName, useCapture=false) { + info("Waiting for event: '" + eventName + "' on " + target + "."); + + let deferred = promise.defer(); + + for (let [add, remove] of [ + ["addEventListener", "removeEventListener"], + ["addListener", "removeListener"], + ["on", "off"] + ]) { + if ((add in target) && (remove in target)) { + target[add](eventName, function onEvent(...aArgs) { + info("Got event: '" + eventName + "' on " + target + "."); + target[remove](eventName, onEvent, useCapture); + deferred.resolve.apply(deferred, aArgs); + }, useCapture); + break; + } + } + + return deferred.promise; +} + +/** + * Wait for a content -> chrome message on the message manager (the window + * messagemanager is used). + * @param {String} name The message name + * @return {Promise} A promise that resolves to the response data when the + * message has been received + */ +function waitForContentMessage(name) { + let mm = gBrowser.selectedBrowser.messageManager; + + let def = promise.defer(); + mm.addMessageListener(name, function onMessage(msg) { + mm.removeMessageListener(name, onMessage); + def.resolve(msg); + }); + return def.promise; +} + +function wait(ms) { + let def = promise.defer(); + setTimeout(def.resolve, ms); + return def.promise; +} + +/** + * Send an async message to the frame script (chrome -> content) and wait for a + * response message with the same name (content -> chrome). + * @param {String} name The message name. Should be one of the messages defined + * in doc_frame_script.js + * @param {Object} data Optional data to send along + * @param {Object} objects Optional CPOW objects to send along + * @param {Boolean} expectResponse If set to false, don't wait for a response + * with the same name from the content script. Defaults to true. + * @return {Promise} Resolves to the response data if a response is expected, + * immediately resolves otherwise + */ +function executeInContent(name, data={}, objects={}, expectResponse=true) { + let mm = gBrowser.selectedBrowser.messageManager; + + mm.sendAsyncMessage(name, data, objects); + if (expectResponse) { + return waitForContentMessage(name); + } else { + return promise.resolve(); + } +} + +/** + * Undo the last markup-view action and wait for the corresponding mutation to + * occur + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @return a promise that resolves when the markup-mutation has been treated or + * rejects if no undo action is possible + */ +function undoChange(inspector) { + let canUndo = inspector.markup.undo.canUndo(); + ok(canUndo, "The last change in the markup-view can be undone"); + if (!canUndo) { + return promise.reject(); + } + + let mutated = inspector.once("markupmutation"); + inspector.markup.undo.undo(); + return mutated; +} + +/** + * Redo the last markup-view action and wait for the corresponding mutation to + * occur + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @return a promise that resolves when the markup-mutation has been treated or + * rejects if no redo action is possible + */ +function redoChange(inspector) { + let canRedo = inspector.markup.undo.canRedo(); + ok(canRedo, "The last change in the markup-view can be redone"); + if (!canRedo) { + return promise.reject(); + } + + let mutated = inspector.once("markupmutation"); + inspector.markup.undo.redo(); + return mutated; +} |