diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /devtools/client/inspector/test | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/client/inspector/test')
157 files changed, 10913 insertions, 0 deletions
diff --git a/devtools/client/inspector/test/.eslintrc.js b/devtools/client/inspector/test/.eslintrc.js new file mode 100644 index 000000000..8d15a76d9 --- /dev/null +++ b/devtools/client/inspector/test/.eslintrc.js @@ -0,0 +1,6 @@ +"use strict"; + +module.exports = { + // Extend from the shared list of defined globals for mochitests. + "extends": "../../../.eslintrc.mochitests.js" +}; diff --git a/devtools/client/inspector/test/browser.ini b/devtools/client/inspector/test/browser.ini new file mode 100644 index 000000000..65ad71c0c --- /dev/null +++ b/devtools/client/inspector/test/browser.ini @@ -0,0 +1,172 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + doc_inspector_add_node.html + doc_inspector_breadcrumbs.html + doc_inspector_breadcrumbs_visibility.html + doc_inspector_csp.html + doc_inspector_csp.html^headers^ + doc_inspector_delete-selected-node-01.html + doc_inspector_delete-selected-node-02.html + doc_inspector_embed.html + doc_inspector_gcli-inspect-command.html + doc_inspector_highlight_after_transition.html + doc_inspector_highlighter-comments.html + doc_inspector_highlighter-geometry_01.html + doc_inspector_highlighter-geometry_02.html + doc_inspector_highlighter_csstransform.html + doc_inspector_highlighter_dom.html + doc_inspector_highlighter_inline.html + doc_inspector_highlighter.html + doc_inspector_highlighter_rect.html + doc_inspector_highlighter_rect_iframe.html + doc_inspector_highlighter_xbl.xul + doc_inspector_infobar_01.html + doc_inspector_infobar_02.html + doc_inspector_infobar_03.html + doc_inspector_infobar_textnode.html + doc_inspector_long-divs.html + doc_inspector_menu.html + doc_inspector_outerhtml.html + doc_inspector_remove-iframe-during-load.html + doc_inspector_search.html + doc_inspector_search-reserved.html + doc_inspector_search-suggestions.html + doc_inspector_search-svg.html + doc_inspector_select-last-selected-01.html + doc_inspector_select-last-selected-02.html + doc_inspector_svg.svg + head.js + shared-head.js + !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js + !/devtools/client/shared/test/test-actor.js + !/devtools/client/shared/test/test-actor-registry.js + +[browser_inspector_addNode_01.js] +[browser_inspector_addNode_02.js] +[browser_inspector_addNode_03.js] +[browser_inspector_addSidebarTab.js] +[browser_inspector_breadcrumbs.js] +[browser_inspector_breadcrumbs_highlight_hover.js] +[browser_inspector_breadcrumbs_keybinding.js] +[browser_inspector_breadcrumbs_keyboard_trap.js] +skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences +[browser_inspector_breadcrumbs_mutations.js] +[browser_inspector_breadcrumbs_namespaced.js] +[browser_inspector_breadcrumbs_visibility.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_expand-collapse.js] +[browser_inspector_gcli-inspect-command.js] +[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-cancel.js] +[browser_inspector_highlighter-comments.js] +[browser_inspector_highlighter-cssgrid_01.js] +[browser_inspector_highlighter-csstransform_01.js] +[browser_inspector_highlighter-csstransform_02.js] +[browser_inspector_highlighter-embed.js] +[browser_inspector_highlighter-eyedropper-clipboard.js] +subsuite = clipboard +[browser_inspector_highlighter-eyedropper-csp.js] +[browser_inspector_highlighter-eyedropper-events.js] +[browser_inspector_highlighter-eyedropper-label.js] +[browser_inspector_highlighter-eyedropper-show-hide.js] +[browser_inspector_highlighter-eyedropper-xul.js] +[browser_inspector_highlighter-geometry_01.js] +[browser_inspector_highlighter-geometry_02.js] +[browser_inspector_highlighter-geometry_03.js] +[browser_inspector_highlighter-geometry_04.js] +[browser_inspector_highlighter-geometry_05.js] +[browser_inspector_highlighter-geometry_06.js] +[browser_inspector_highlighter-hover_01.js] +[browser_inspector_highlighter-hover_02.js] +[browser_inspector_highlighter-hover_03.js] +[browser_inspector_highlighter-iframes_01.js] +[browser_inspector_highlighter-iframes_02.js] +[browser_inspector_highlighter-inline.js] +[browser_inspector_highlighter-keybinding_01.js] +[browser_inspector_highlighter-keybinding_02.js] +[browser_inspector_highlighter-keybinding_03.js] +[browser_inspector_highlighter-keybinding_04.js] +[browser_inspector_highlighter-measure_01.js] +[browser_inspector_highlighter-measure_02.js] +[browser_inspector_highlighter-options.js] +[browser_inspector_highlighter-preview.js] +[browser_inspector_highlighter-rect_01.js] +[browser_inspector_highlighter-rect_02.js] +[browser_inspector_highlighter-rulers_01.js] +[browser_inspector_highlighter-rulers_02.js] +[browser_inspector_highlighter-selector_01.js] +[browser_inspector_highlighter-selector_02.js] +[browser_inspector_highlighter-xbl.js] +[browser_inspector_highlighter-zoom.js] +[browser_inspector_iframe-navigation.js] +[browser_inspector_infobar_01.js] +[browser_inspector_infobar_02.js] +[browser_inspector_infobar_03.js] +[browser_inspector_infobar_textnode.js] +[browser_inspector_initialization.js] +skip-if = (e10s && debug) # Bug 1250058 - Docshell leak on debug e10s +[browser_inspector_inspect-object-element.js] +[browser_inspector_invalidate.js] +[browser_inspector_keyboard-shortcuts-copy-outerhtml.js] +subsuite = clipboard +[browser_inspector_keyboard-shortcuts.js] +[browser_inspector_menu-01-sensitivity.js] +subsuite = clipboard +[browser_inspector_menu-02-copy-items.js] +subsuite = clipboard +[browser_inspector_menu-03-paste-items.js] +subsuite = clipboard +[browser_inspector_menu-03-paste-items-svg.js] +subsuite = clipboard +[browser_inspector_menu-04-use-in-console.js] +[browser_inspector_menu-05-attribute-items.js] +[browser_inspector_menu-06-other.js] +[browser_inspector_navigation.js] +[browser_inspector_navigate_to_errors.js] +[browser_inspector_open_on_neterror.js] +[browser_inspector_pane-toggle-01.js] +[browser_inspector_pane-toggle-02.js] +[browser_inspector_pane-toggle-03.js] +[browser_inspector_pane-toggle-05.js] +skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard +[browser_inspector_picker-stop-on-destroy.js] +[browser_inspector_picker-stop-on-tool-change.js] +[browser_inspector_portrait_mode.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_search-01.js] +[browser_inspector_search-02.js] +[browser_inspector_search-03.js] +[browser_inspector_search-04.js] +[browser_inspector_search-05.js] +[browser_inspector_search-06.js] +[browser_inspector_search-07.js] +[browser_inspector_search-08.js] +[browser_inspector_search-clear.js] +[browser_inspector_search-filter_context-menu.js] +subsuite = clipboard +[browser_inspector_search_keyboard_trap.js] +[browser_inspector_search-label.js] +[browser_inspector_search-reserved.js] +[browser_inspector_search-selection.js] +[browser_inspector_search-sidebar.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_textbox-menu.js] diff --git a/devtools/client/inspector/test/browser_inspector_addNode_01.js b/devtools/client/inspector/test/browser_inspector_addNode_01.js new file mode 100644 index 000000000..f90cb6c5c --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_addNode_01.js @@ -0,0 +1,22 @@ +/* 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 add node button and context menu items are present in the UI. + +const TEST_URL = "data:text/html;charset=utf-8,<h1>Add node</h1>"; + +add_task(function* () { + let {inspector} = yield openInspectorForURL(TEST_URL); + let {panelDoc} = inspector; + + let allMenuItems = openContextMenuAndGetAllItems(inspector); + let menuItem = allMenuItems.find(item => item.id === "node-menu-add"); + ok(menuItem, "The item is in the menu"); + + let toolbarButton = + panelDoc.querySelector("#inspector-toolbar #inspector-element-add-button"); + ok(toolbarButton, "The add button is in the toolbar"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_addNode_02.js b/devtools/client/inspector/test/browser_inspector_addNode_02.js new file mode 100644 index 000000000..2421f9df3 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_addNode_02.js @@ -0,0 +1,63 @@ +/* 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 add node button and context menu items have the right state +// depending on the current selection. + +const TEST_URL = URL_ROOT + "doc_inspector_add_node.html"; + +add_task(function* () { + let {inspector} = yield openInspectorForURL(TEST_URL); + + info("Select the DOCTYPE element"); + let {nodes} = yield inspector.walker.children(inspector.walker.rootNode); + yield selectNode(nodes[0], inspector); + assertState(false, inspector, + "The button and item are disabled on DOCTYPE"); + + info("Select the ::before pseudo-element"); + let body = yield getNodeFront("body", inspector); + ({nodes} = yield inspector.walker.children(body)); + yield selectNode(nodes[0], inspector); + assertState(false, inspector, + "The button and item are disabled on a pseudo-element"); + + info("Select the svg element"); + yield selectNode("svg", inspector); + assertState(false, inspector, + "The button and item are disabled on a SVG element"); + + info("Select the div#foo element"); + yield selectNode("#foo", inspector); + assertState(true, inspector, + "The button and item are enabled on a DIV element"); + + info("Select the documentElement element (html)"); + yield selectNode("html", inspector); + assertState(false, inspector, + "The button and item are disabled on the documentElement"); + + info("Select the iframe element"); + yield selectNode("iframe", inspector); + assertState(false, inspector, + "The button and item are disabled on an IFRAME element"); +}); + +function assertState(isEnabled, inspector, desc) { + let doc = inspector.panelDoc; + let btn = doc.querySelector("#inspector-element-add-button"); + + // Force an update of the context menu to make sure menu items are updated + // according to the current selection. This normally happens when the menu is + // opened, but for the sake of this test's simplicity, we directly call the + // private update function instead. + let allMenuItems = openContextMenuAndGetAllItems(inspector); + let menuItem = allMenuItems.find(item => item.id === "node-menu-add"); + ok(menuItem, "The item is in the menu"); + is(!menuItem.disabled, isEnabled, desc); + + is(!btn.hasAttribute("disabled"), isEnabled, desc); +} diff --git a/devtools/client/inspector/test/browser_inspector_addNode_03.js b/devtools/client/inspector/test/browser_inspector_addNode_03.js new file mode 100644 index 000000000..38a8369ec --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_addNode_03.js @@ -0,0 +1,84 @@ +/* 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 adding nodes does work as expected: the parent gets expanded, the +// new node gets selected. + +const TEST_URL = URL_ROOT + "doc_inspector_add_node.html"; +const PARENT_TREE_LEVEL = 3; + +add_task(function* () { + let {inspector} = yield openInspectorForURL(TEST_URL); + + info("Adding in element that has no children and is collapsed"); + let parentNode = yield getNodeFront("#foo", inspector); + yield selectNode(parentNode, inspector); + yield testAddNode(parentNode, inspector); + + info("Adding in element with children but that has not been expanded yet"); + parentNode = yield getNodeFront("#bar", inspector); + yield selectNode(parentNode, inspector); + yield testAddNode(parentNode, inspector); + + info("Adding in element with children that has been expanded then collapsed"); + // Select again #bar and collapse it. + parentNode = yield getNodeFront("#bar", inspector); + yield selectNode(parentNode, inspector); + collapseNode(parentNode, inspector); + yield testAddNode(parentNode, inspector); + + info("Adding in element with children that is expanded"); + parentNode = yield getNodeFront("#bar", inspector); + yield selectNode(parentNode, inspector); + yield testAddNode(parentNode, inspector); +}); + +function* testAddNode(parentNode, inspector) { + let btn = inspector.panelDoc.querySelector("#inspector-element-add-button"); + let markupWindow = inspector.markup.win; + let parentContainer = inspector.markup.getContainer(parentNode); + + is(parentContainer.tagLine.getAttribute("aria-level"), PARENT_TREE_LEVEL, + "Parent level should be up to date."); + + info("Clicking 'add node' and expecting a markup mutation and focus event"); + let onMutation = inspector.once("markupmutation"); + btn.click(); + let mutations = yield onMutation; + + info("Expecting an inspector-updated event right after the mutation event " + + "to wait for the new node selection"); + yield inspector.once("inspector-updated"); + + is(mutations.length, 1, "There is one mutation only"); + is(mutations[0].added.length, 1, "There is one new node only"); + + let newNode = mutations[0].added[0]; + + is(newNode, inspector.selection.nodeFront, + "The new node is selected"); + + ok(parentContainer.expanded, "The parent node is now expanded"); + + is(inspector.selection.nodeFront.parentNode(), parentNode, + "The new node is inside the right parent"); + + let focusedElement = markupWindow.document.activeElement; + let focusedContainer = focusedElement.container; + let selectedContainer = inspector.markup._selectedContainer; + is(selectedContainer.tagLine.getAttribute("aria-level"), + PARENT_TREE_LEVEL + 1, "Added container level should be up to date."); + is(selectedContainer.node, inspector.selection.nodeFront, + "The right container is selected in the markup-view"); + ok(selectedContainer.selected, "Selected container is set to selected"); + is(focusedContainer.toString(), "[root container]", + "Root container is focused"); +} + +function collapseNode(node, inspector) { + let container = inspector.markup.getContainer(node); + container.setExpanded(false); +} diff --git a/devtools/client/inspector/test/browser_inspector_addSidebarTab.js b/devtools/client/inspector/test/browser_inspector_addSidebarTab.js new file mode 100644 index 000000000..77dc2632e --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_addSidebarTab.js @@ -0,0 +1,62 @@ +/* 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_addtabbar.js</h1>"; + +const CONTENT_TEXT = "Hello World!"; + +/** + * Verify InspectorPanel.addSidebarTab() API that can be consumed + * by DevTools extensions as well as DevTools code base. + */ +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URI); + + const React = inspector.React; + const { div } = React.DOM; + + info("Adding custom panel."); + + // Define custom side-panel. + let tabPanel = React.createFactory(React.createClass({ + displayName: "myTabPanel", + render: function () { + return ( + div({className: "my-tab-panel"}, + CONTENT_TEXT + ) + ); + } + })); + + // Append custom panel (tab) into the Inspector panel and + // make sure it's selected by default (the last arg = true). + inspector.addSidebarTab("myPanel", "My Panel", tabPanel, true); + is(inspector.sidebar.getCurrentTabID(), "myPanel", + "My Panel is selected by default"); + + // Define another custom side-panel. + tabPanel = React.createFactory(React.createClass({ + displayName: "myTabPanel2", + render: function () { + return ( + div({className: "my-tab-panel2"}, + "Another Content" + ) + ); + } + })); + + // Append second panel, but don't select it by default. + inspector.addSidebarTab("myPanel", "My Panel", tabPanel, false); + is(inspector.sidebar.getCurrentTabID(), "myPanel", + "My Panel is selected by default"); + + // Check the the panel content is properly rendered. + let tabPanelNode = inspector.panelDoc.querySelector(".my-tab-panel"); + is(tabPanelNode.textContent, CONTENT_TEXT, + "Side panel content has been rendered."); +}); diff --git a/devtools/client/inspector/test/browser_inspector_breadcrumbs.js b/devtools/client/inspector/test/browser_inspector_breadcrumbs.js new file mode 100644 index 000000000..e5befff9e --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs.js @@ -0,0 +1,132 @@ +/* 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 = URL_ROOT + "doc_inspector_breadcrumbs.html"; +const NODES = [ + {selector: "#i1111", ids: "i1 i11 i111 i1111", nodeName: "div", + title: "div#i1111"}, + {selector: "#i22", ids: "i2 i22", nodeName: "div", + title: "div#i22"}, + {selector: "#i2111", ids: "i2 i21 i211 i2111", nodeName: "div", + title: "div#i2111"}, + {selector: "#i21", ids: "i2 i21 i211 i2111", nodeName: "div", + title: "div#i21"}, + {selector: "#i22211", ids: "i2 i22 i222 i2221 i22211", nodeName: "div", + title: "div#i22211"}, + {selector: "#i22", ids: "i2 i22 i222 i2221 i22211", nodeName: "div", + title: "div#i22"}, + {selector: "#i3", ids: "i3", nodeName: "article", + title: "article#i3"}, + {selector: "clipPath", ids: "vector clip", nodeName: "clipPath", + title: "clipPath#clip"}, +]; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URI); + let breadcrumbs = inspector.panelDoc.getElementById("inspector-breadcrumbs"); + let container = breadcrumbs.querySelector(".html-arrowscrollbox-inner"); + + 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.ids.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"); + + let labelTag = checkedButton.querySelector(".breadcrumbs-widget-item-tag"); + is(labelTag.textContent, node.nodeName, + "Node " + node.selector + " has the expected tag name"); + + is(checkedButton.getAttribute("title"), node.title, + "Node " + node.selector + " has the expected tooltip"); + } + + yield testPseudoElements(inspector, container); + yield testComments(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"); +} + +function* testComments(inspector, container) { + info("Checking for comment elements"); + + let breadcrumbs = inspector.breadcrumbs; + let checkedButtonIndex = 2; + let button = container.childNodes[checkedButtonIndex]; + + let onBreadcrumbsUpdated = inspector.once("breadcrumbs-updated"); + button.click(); + yield onBreadcrumbsUpdated; + + is(breadcrumbs.currentIndex, checkedButtonIndex, "New button is selected"); + ok(breadcrumbs.outer.hasAttribute("aria-activedescendant"), + "Active descendant must be set"); + + let comment = [...inspector.markup._containers].find(([node]) => + node.nodeType === Ci.nsIDOMNode.COMMENT_NODE)[0]; + + let onInspectorUpdated = inspector.once("inspector-updated"); + inspector.selection.setNodeFront(comment); + yield onInspectorUpdated; + + is(breadcrumbs.currentIndex, -1, + "When comment is selected no breadcrumb should be checked"); + ok(!breadcrumbs.outer.hasAttribute("aria-activedescendant"), + "Active descendant must not be set"); + + onInspectorUpdated = inspector.once("inspector-updated"); + onBreadcrumbsUpdated = inspector.once("breadcrumbs-updated"); + button.click(); + yield Promise.all([onInspectorUpdated, onBreadcrumbsUpdated]); + + is(breadcrumbs.currentIndex, checkedButtonIndex, + "Same button is selected again"); + ok(breadcrumbs.outer.hasAttribute("aria-activedescendant"), + "Active descendant must be set again"); +} diff --git a/devtools/client/inspector/test/browser_inspector_breadcrumbs_highlight_hover.js b/devtools/client/inspector/test/browser_inspector_breadcrumbs_highlight_hover.js new file mode 100644 index 000000000..6714ea35e --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_highlight_hover.js @@ -0,0 +1,47 @@ +/* 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"); + let {toolbox, inspector, testActor} = yield openInspectorForURL( + "data:text/html;charset=utf-8,<h1>foo</h1><span>bar</span>"); + 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 testActor.isHighlighting(); + ok(isVisible, "The highlighter is shown on a markup container hover"); + + ok((yield testActor.assertHighlightedNode("body")), + "The highlighter highlights the right node"); + + let onNodeUnhighlighted = toolbox.once("node-unhighlight"); + // move outside of the breadcrumb trail to trigger unhighlight + EventUtils.synthesizeMouseAtCenter(inspector.addNodeButton, + {type: "mousemove"}, + inspector.addNodeButton.ownerDocument.defaultView); + yield onNodeUnhighlighted; + + onNodeHighlighted = toolbox.once("node-highlight"); + button = bcButtons.childNodes[2]; + EventUtils.synthesizeMouseAtCenter(button, {type: "mousemove"}, + button.ownerDocument.defaultView); + yield onNodeHighlighted; + + isVisible = yield testActor.isHighlighting(); + ok(isVisible, "The highlighter is shown on a markup container hover"); + + ok((yield testActor.assertHighlightedNode("span")), + "The highlighter highlights the right node"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_breadcrumbs_keybinding.js b/devtools/client/inspector/test/browser_inspector_breadcrumbs_keybinding.js new file mode 100644 index 000000000..8e72a8bab --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_keybinding.js @@ -0,0 +1,71 @@ +/* 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 keybindings work. + +const TEST_URI = URL_ROOT + "doc_inspector_breadcrumbs.html"; +const TEST_DATA = [{ + desc: "Pressing left should select the parent <body>", + key: "VK_LEFT", + newSelection: "body" +}, { + desc: "Pressing left again should select the parent <html>", + key: "VK_LEFT", + newSelection: "html" +}, { + desc: "Pressing left again should stay on <html>, it's the first element", + key: "VK_LEFT", + newSelection: "html" +}, { + desc: "Pressing right should go to <body>", + key: "VK_RIGHT", + newSelection: "body" +}, { + desc: "Pressing right again should go to #i2", + key: "VK_RIGHT", + newSelection: "#i2" +}, { + desc: "Pressing right again should stay on #i2, it's the last element", + key: "VK_RIGHT", + newSelection: "#i2" +}]; + +add_task(function* () { + let {inspector} = yield openInspectorForURL(TEST_URI); + + info("Selecting the test node"); + yield selectNode("#i2", inspector); + + info("Clicking on the corresponding breadcrumbs node to focus it"); + let container = inspector.panelDoc.getElementById("inspector-breadcrumbs"); + + let button = container.querySelector("button[checked]"); + button.click(); + + let currentSelection = "#id2"; + for (let {desc, key, newSelection} of TEST_DATA) { + info(desc); + + // If the selection will change, wait for the breadcrumb to update, + // otherwise continue. + let onUpdated = null; + if (newSelection !== currentSelection) { + info("Expecting a new node to be selected"); + onUpdated = inspector.once("breadcrumbs-updated"); + } + + EventUtils.synthesizeKey(key, {}); + yield onUpdated; + + let newNodeFront = yield getNodeFront(newSelection, inspector); + is(newNodeFront, inspector.selection.nodeFront, + "The current selection is correct"); + is(container.getAttribute("aria-activedescendant"), + container.querySelector("button[checked]").id, + "aria-activedescendant is set correctly"); + + currentSelection = newSelection; + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_breadcrumbs_keyboard_trap.js b/devtools/client/inspector/test/browser_inspector_breadcrumbs_keyboard_trap.js new file mode 100644 index 000000000..16c70650b --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_keyboard_trap.js @@ -0,0 +1,83 @@ +/* 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 ability to tab to and away from breadcrumbs using keyboard. + +const TEST_URL = URL_ROOT + "doc_inspector_breadcrumbs.html"; + +/** + * Test data has the format of: + * { + * desc {String} description for better logging + * focused {Boolean} flag, indicating if breadcrumbs contain focus + * key {String} key event's key + * options {?Object} optional event data such as shiftKey, etc + * } + */ +const TEST_DATA = [ + { + desc: "Move the focus away from breadcrumbs to a next focusable element", + focused: false, + key: "VK_TAB", + options: { } + }, + { + desc: "Move the focus back to the breadcrumbs", + focused: true, + key: "VK_TAB", + options: { shiftKey: true } + }, + { + desc: "Move the focus back away from breadcrumbs to a previous focusable " + + "element", + focused: false, + key: "VK_TAB", + options: { shiftKey: true } + }, + { + desc: "Move the focus back to the breadcrumbs", + focused: true, + key: "VK_TAB", + options: { } + } +]; + +add_task(function* () { + let { toolbox, inspector } = yield openInspectorForURL(TEST_URL); + let doc = inspector.panelDoc; + let {breadcrumbs} = inspector; + + yield selectNode("#i2", inspector); + + info("Clicking on the corresponding breadcrumbs node to focus it"); + let container = doc.getElementById("inspector-breadcrumbs"); + + let button = container.querySelector("button[checked]"); + let onHighlight = toolbox.once("node-highlight"); + button.click(); + yield onHighlight; + + // Ensure a breadcrumb is focused. + is(doc.activeElement, container, "Focus is on selected breadcrumb"); + is(container.getAttribute("aria-activedescendant"), button.id, + "aria-activedescendant is set correctly"); + + for (let { desc, focused, key, options } of TEST_DATA) { + info(desc); + + EventUtils.synthesizeKey(key, options); + // Wait until the keyPromise promise resolves. + yield breadcrumbs.keyPromise; + + if (focused) { + is(doc.activeElement, container, "Focus is on selected breadcrumb"); + } else { + ok(!containsFocus(doc, container), "Focus is outside of breadcrumbs"); + } + is(container.getAttribute("aria-activedescendant"), button.id, + "aria-activedescendant is set correctly"); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_breadcrumbs_mutations.js b/devtools/client/inspector/test/browser_inspector_breadcrumbs_mutations.js new file mode 100644 index 000000000..100ee275a --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_mutations.js @@ -0,0 +1,212 @@ +/* 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 refreshes correctly when there are markup +// mutations (and that it doesn't refresh when those mutations don't change its +// output). + +const TEST_URI = URL_ROOT + "doc_inspector_breadcrumbs.html"; + +// Each item in the TEST_DATA array is a test case that should contain the +// following properties: +// - desc {String} A description of this test case (will be logged). +// - setup {Function*} A generator function (can yield promises) that sets up +// the test case. Useful for selecting a node before starting the test. +// - run {Function*} A generator function (can yield promises) that runs the +// actual test case, i.e, mutates the content DOM to cause the breadcrumbs +// to refresh, or not. +// - shouldRefresh {Boolean} Once the `run` function has completed, and the test +// has detected that the page has changed, this boolean instructs the test to +// verify if the breadcrumbs has refreshed or not. +// - output {Array} A list of strings for the text that should be found in each +// button after the test has run. +const TEST_DATA = [{ + desc: "Adding a child at the end of the chain shouldn't change anything", + setup: function* (inspector) { + yield selectNode("#i1111", inspector); + }, + run: function* ({walker, selection}) { + yield walker.setInnerHTML(selection.nodeFront, "<b>test</b>"); + }, + shouldRefresh: false, + output: ["html", "body", "article#i1", "div#i11", "div#i111", "div#i1111"] +}, { + desc: "Updating an ID to an displayed element should refresh", + setup: function* () {}, + run: function* ({walker}) { + let node = yield walker.querySelector(walker.rootNode, "#i1"); + yield node.modifyAttributes([{ + attributeName: "id", + newValue: "i1-changed" + }]); + }, + shouldRefresh: true, + output: ["html", "body", "article#i1-changed", "div#i11", "div#i111", + "div#i1111"] +}, { + desc: "Updating an class to a displayed element should refresh", + setup: function* () {}, + run: function* ({walker}) { + let node = yield walker.querySelector(walker.rootNode, "body"); + yield node.modifyAttributes([{ + attributeName: "class", + newValue: "test-class" + }]); + }, + shouldRefresh: true, + output: ["html", "body.test-class", "article#i1-changed", "div#i11", + "div#i111", "div#i1111"] +}, { + desc: "Updating a non id/class attribute to a displayed element should not " + + "refresh", + setup: function* () {}, + run: function* ({walker}) { + let node = yield walker.querySelector(walker.rootNode, "#i11"); + yield node.modifyAttributes([{ + attributeName: "name", + newValue: "value" + }]); + }, + shouldRefresh: false, + output: ["html", "body.test-class", "article#i1-changed", "div#i11", + "div#i111", "div#i1111"] +}, { + desc: "Moving a child in an element that's not displayed should not refresh", + setup: function* () {}, + run: function* ({walker}) { + // Re-append #i1211 as a last child of #i2. + let parent = yield walker.querySelector(walker.rootNode, "#i2"); + let child = yield walker.querySelector(walker.rootNode, "#i211"); + yield walker.insertBefore(child, parent); + }, + shouldRefresh: false, + output: ["html", "body.test-class", "article#i1-changed", "div#i11", + "div#i111", "div#i1111"] +}, { + desc: "Moving an undisplayed child in a displayed element should not refresh", + setup: function* () {}, + run: function* ({walker}) { + // Re-append #i2 in body (move it to the end). + let parent = yield walker.querySelector(walker.rootNode, "body"); + let child = yield walker.querySelector(walker.rootNode, "#i2"); + yield walker.insertBefore(child, parent); + }, + shouldRefresh: false, + output: ["html", "body.test-class", "article#i1-changed", "div#i11", + "div#i111", "div#i1111"] +}, { + desc: "Updating attributes on an element that's not displayed should not " + + "refresh", + setup: function* () {}, + run: function* ({walker}) { + let node = yield walker.querySelector(walker.rootNode, "#i2"); + yield node.modifyAttributes([{ + attributeName: "id", + newValue: "i2-changed" + }, { + attributeName: "class", + newValue: "test-class" + }]); + }, + shouldRefresh: false, + output: ["html", "body.test-class", "article#i1-changed", "div#i11", + "div#i111", "div#i1111"] +}, { + desc: "Removing the currently selected node should refresh", + setup: function* (inspector) { + yield selectNode("#i2-changed", inspector); + }, + run: function* ({walker, selection}) { + yield walker.removeNode(selection.nodeFront); + }, + shouldRefresh: true, + output: ["html", "body.test-class"] +}, { + desc: "Changing the class of the currently selected node should refresh", + setup: function* () {}, + run: function* ({selection}) { + yield selection.nodeFront.modifyAttributes([{ + attributeName: "class", + newValue: "test-class-changed" + }]); + }, + shouldRefresh: true, + output: ["html", "body.test-class-changed"] +}, { + desc: "Changing the id of the currently selected node should refresh", + setup: function* () {}, + run: function* ({selection}) { + yield selection.nodeFront.modifyAttributes([{ + attributeName: "id", + newValue: "new-id" + }]); + }, + shouldRefresh: true, + output: ["html", "body#new-id.test-class-changed"] +}]; + +add_task(function* () { + let {inspector} = yield openInspectorForURL(TEST_URI); + let breadcrumbs = inspector.panelDoc.getElementById("inspector-breadcrumbs"); + let container = breadcrumbs.querySelector(".html-arrowscrollbox-inner"); + let win = container.ownerDocument.defaultView; + + for (let {desc, setup, run, shouldRefresh, output} of TEST_DATA) { + info("Running test case: " + desc); + + info("Listen to markupmutation events from the inspector to know when a " + + "test case has completed"); + let onContentMutation = inspector.once("markupmutation"); + + info("Running setup"); + yield setup(inspector); + + info("Listen to mutations on the breadcrumbs container"); + let hasBreadcrumbsMutated = false; + let observer = new win.MutationObserver(mutations => { + // Only consider childList changes or tooltiptext/checked attributes + // changes. The rest may be mutations caused by the overflowing arrowbox. + for (let {type, attributeName} of mutations) { + let isChildList = type === "childList"; + let isAttributes = type === "attributes" && + (attributeName === "checked" || + attributeName === "tooltiptext"); + if (isChildList || isAttributes) { + hasBreadcrumbsMutated = true; + break; + } + } + }); + observer.observe(container, { + attributes: true, + childList: true, + subtree: true + }); + + info("Running the test case"); + yield run(inspector); + + info("Wait until the page has mutated"); + yield onContentMutation; + + if (shouldRefresh) { + info("The breadcrumbs is expected to refresh, so wait for it"); + yield inspector.once("inspector-updated"); + } else { + ok(!inspector._updateProgress, + "The breadcrumbs widget is not currently updating"); + } + + is(shouldRefresh, hasBreadcrumbsMutated, "Has the breadcrumbs refreshed?"); + observer.disconnect(); + + info("Check the output of the breadcrumbs widget"); + is(container.childNodes.length, output.length, "Correct number of buttons"); + for (let i = 0; i < container.childNodes.length; i++) { + is(output[i], container.childNodes[i].textContent, + "Text content for button " + i + " is correct"); + } + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_breadcrumbs_namespaced.js b/devtools/client/inspector/test/browser_inspector_breadcrumbs_namespaced.js new file mode 100644 index 000000000..0b14ef1b0 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_namespaced.js @@ -0,0 +1,55 @@ +/* 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 for namespaced elements is correct. + +const XHTML = ` + <!DOCTYPE html> + <html xmlns="http://www.w3.org/1999/xhtml" + xmlns:svg="http://www.w3.org/2000/svg"> + <body> + <svg:svg width="100" height="100"> + <svg:clipPath id="clip"> + <svg:rect id="rectangle" x="0" y="0" width="10" height="5"></svg:rect> + </svg:clipPath> + <svg:circle cx="0" cy="0" r="5"></svg:circle> + </svg:svg> + </body> + </html> +`; + +const TEST_URI = "data:application/xhtml+xml;charset=utf-8," + encodeURI(XHTML); + +const NODES = [ + {selector: "clipPath", nodes: ["svg:svg", "svg:clipPath"], + nodeName: "svg:clipPath", title: "svg:clipPath#clip"}, + {selector: "circle", nodes: ["svg:svg", "svg:circle"], + nodeName: "svg:circle", title: "svg:circle"}, +]; + +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 checkedButton = container.querySelector("button[checked]"); + + let labelTag = checkedButton.querySelector(".breadcrumbs-widget-item-tag"); + is(labelTag.textContent, node.nodeName, + "Node " + node.selector + " has the expected tag name"); + + is(checkedButton.getAttribute("title"), node.title, + "Node " + node.selector + " has the expected tooltip"); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_breadcrumbs_visibility.js b/devtools/client/inspector/test/browser_inspector_breadcrumbs_visibility.js new file mode 100644 index 000000000..caee745c9 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_visibility.js @@ -0,0 +1,110 @@ +/* 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 start and end buttons on the breadcrumb trail bring the right +// crumbs into the visible area, for both LTR and RTL + +let { Toolbox } = require("devtools/client/framework/toolbox"); + +const TEST_URI = URL_ROOT + "doc_inspector_breadcrumbs_visibility.html"; +const NODE_ONE = "div#aVeryLongIdToExceedTheBreadcrumbTruncationLimit"; +const NODE_TWO = "div#anotherVeryLongIdToExceedTheBreadcrumbTruncationLimit"; +const NODE_THREE = "div#aThirdVeryLongIdToExceedTheTruncationLimit"; +const NODE_FOUR = "div#aFourthOneToExceedTheTruncationLimit"; +const NODE_FIVE = "div#aFifthOneToExceedTheTruncationLimit"; +const NODE_SIX = "div#aSixthOneToExceedTheTruncationLimit"; +const NODE_SEVEN = "div#aSeventhOneToExceedTheTruncationLimit"; + +const NODES = [ + { action: "start", title: NODE_SIX }, + { action: "start", title: NODE_FIVE }, + { action: "start", title: NODE_FOUR }, + { action: "start", title: NODE_THREE }, + { action: "start", title: NODE_TWO }, + { action: "start", title: NODE_ONE }, + { action: "end", title: NODE_TWO }, + { action: "end", title: NODE_THREE }, + { action: "end", title: NODE_FOUR }, + { action: "end", title: NODE_FIVE }, + { action: "end", title: NODE_SIX } +]; + +add_task(function* () { + // This test needs specific initial size of the sidebar. + yield pushPref("devtools.toolsidebar-width.inspector", 350); + yield pushPref("devtools.toolsidebar-height.inspector", 150); + + let { inspector, toolbox } = yield openInspectorForURL(TEST_URI); + + // No way to wait for scrolling to end (Bug 1172171) + // Rather than wait a max time; limit test to instant scroll behavior + inspector.breadcrumbs.arrowScrollBox.scrollBehavior = "instant"; + + yield toolbox.switchHost(Toolbox.HostType.WINDOW); + let hostWindow = toolbox.win.parent; + let originalWidth = hostWindow.outerWidth; + let originalHeight = hostWindow.outerHeight; + hostWindow.resizeTo(640, 300); + + info("Testing transitions ltr"); + yield pushPref("intl.uidirection.en-US", "ltr"); + yield testBreadcrumbTransitions(hostWindow, inspector); + + info("Testing transitions rtl"); + yield pushPref("intl.uidirection.en-US", "rtl"); + yield testBreadcrumbTransitions(hostWindow, inspector); + + hostWindow.resizeTo(originalWidth, originalHeight); +}); + +function* testBreadcrumbTransitions(hostWindow, inspector) { + let breadcrumbs = inspector.panelDoc.getElementById("inspector-breadcrumbs"); + let startBtn = breadcrumbs.querySelector(".scrollbutton-up"); + let endBtn = breadcrumbs.querySelector(".scrollbutton-down"); + let container = breadcrumbs.querySelector(".html-arrowscrollbox-inner"); + let breadcrumbsUpdated = inspector.once("breadcrumbs-updated"); + + info("Selecting initial node"); + yield selectNode(NODE_SEVEN, inspector); + + // So just need to wait for a duration + yield breadcrumbsUpdated; + let initialCrumb = container.querySelector("button[checked]"); + is(isElementInViewport(hostWindow, initialCrumb), true, + "initial element was visible"); + + for (let node of NODES) { + info("Checking for visibility of crumb " + node.title); + if (node.action === "end") { + info("Simulating click of end button"); + EventUtils.synthesizeMouseAtCenter(endBtn, {}, inspector.panelWin); + } else if (node.action === "start") { + info("Simulating click of start button"); + EventUtils.synthesizeMouseAtCenter(startBtn, {}, inspector.panelWin); + } + + yield breadcrumbsUpdated; + let selector = "button[title=\"" + node.title + "\"]"; + let relevantCrumb = container.querySelector(selector); + is(isElementInViewport(hostWindow, relevantCrumb), true, + node.title + " crumb is visible"); + } +} + +function isElementInViewport(window, el) { + let rect = el.getBoundingClientRect(); + + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= window.innerHeight && + rect.right <= window.innerWidth + ); +} + +registerCleanupFunction(function () { + // Restore the host type for other tests. + Services.prefs.clearUserPref("devtools.toolbox.host"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_delete-selected-node-01.js b/devtools/client/inspector/test/browser_inspector_delete-selected-node-01.js new file mode 100644 index 000000000..3b5049e25 --- /dev/null +++ b/devtools/client/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 = 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/devtools/client/inspector/test/browser_inspector_delete-selected-node-02.js b/devtools/client/inspector/test/browser_inspector_delete-selected-node-02.js new file mode 100644 index 000000000..fbd008a89 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_delete-selected-node-02.js @@ -0,0 +1,154 @@ +/* -*- 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 = URL_ROOT + + "doc_inspector_delete-selected-node-02.html"; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_PAGE); + + yield testManuallyDeleteSelectedNode(); + yield testAutomaticallyDeleteSelectedNode(); + yield testDeleteSelectedNodeContainerFrame(); + yield testDeleteWithNonElementNode(); + + function* testManuallyDeleteSelectedNode() { + info("Selecting a node, deleting it via context menu and checking that " + + "its parent node is selected and breadcrumbs are updated."); + + yield deleteNodeWithContextMenu("#deleteManually"); + + info("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* testDeleteWithNonElementNode() { + info("Selecting a node, deleting it via context menu and checking that " + + "its parent node is selected and breadcrumbs are updated " + + "when the node is followed by a non-element node"); + + yield deleteNodeWithContextMenu("#deleteWithNonElement"); + + let expectedCrumbs = ["html", "body", "div#deleteToMakeSingleTextNode"]; + yield assertNodeSelectedAndCrumbsUpdated(expectedCrumbs, + Node.TEXT_NODE); + + // Delete node with key, as cannot delete text node with + // context menu at this time. + inspector.markup._frame.focus(); + EventUtils.synthesizeKey("VK_DELETE", {}); + yield inspector.once("inspector-updated"); + + expectedCrumbs = ["html", "body", "div#deleteToMakeSingleTextNode"]; + yield assertNodeSelectedAndCrumbsUpdated(expectedCrumbs, + Node.ELEMENT_NODE); + } + + function* deleteNodeWithContextMenu(selector) { + yield selectNode(selector, inspector); + let nodeToBeDeleted = inspector.selection.nodeFront; + + info("Getting the node container in the markup view."); + let container = yield getContainerForSelector(selector, inspector); + + let allMenuItems = openContextMenuAndGetAllItems(inspector, { + target: container.tagLine, + }); + let menuItem = allMenuItems.find(item => item.id === "node-menu-delete"); + + info("Clicking 'Delete Node' in the context menu."); + is(menuItem.disabled, false, "delete menu item is enabled"); + menuItem.click(); + + // close the open context menu + EventUtils.synthesizeKey("VK_ESCAPE", {}); + + info("Waiting for inspector to update."); + yield inspector.once("inspector-updated"); + + // Since the mutations are sent asynchronously from the server, the + // inspector-updated event triggered by the deletion might happen before + // the mutation is received and the element is removed from the + // breadcrumbs. See bug 1284125. + if (inspector.breadcrumbs.indexOf(nodeToBeDeleted) > -1) { + info("Crumbs haven't seen deletion. Waiting for breadcrumbs-updated."); + yield inspector.once("breadcrumbs-updated"); + } + + return menuItem; + } + + function* assertNodeSelectedAndCrumbsUpdated(expectedCrumbs, + expectedNodeType) { + info("Performing checks"); + let actualNodeType = inspector.selection.nodeFront.nodeType; + is(actualNodeType, expectedNodeType, "The node has the right type"); + + let breadcrumbs = inspector.panelDoc.querySelectorAll( + "#inspector-breadcrumbs .html-arrowscrollbox-inner > *"); + is(breadcrumbs.length, expectedCrumbs.length, + "Have the correct number of breadcrumbs"); + for (let i = 0; i < breadcrumbs.length; i++) { + is(breadcrumbs[i].textContent, expectedCrumbs[i], + "Text content for button " + i + " is correct"); + } + } + + function* assertNodeSelectedAndPanelsUpdated(selector, crumbLabel) { + let nodeFront = yield getNodeFront(selector, inspector); + is(inspector.selection.nodeFront, nodeFront, "The right node is selected"); + + let breadcrumbs = inspector.panelDoc.querySelector( + "#inspector-breadcrumbs .html-arrowscrollbox-inner"); + is(breadcrumbs.querySelector("button[checked=true]").textContent, + crumbLabel, + "The right breadcrumb is selected"); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_delete-selected-node-03.js b/devtools/client/inspector/test/browser_inspector_delete-selected-node-03.js new file mode 100644 index 000000000..21057cdb6 --- /dev/null +++ b/devtools/client/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 = 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/devtools/client/inspector/test/browser_inspector_destroy-after-navigation.js b/devtools/client/inspector/test/browser_inspector_destroy-after-navigation.js new file mode 100644 index 000000000..5fcd5538b --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_destroy-after-navigation.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"; + +// 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 { inspector, toolbox } = yield openInspectorForURL(URL_1); + + yield navigateTo(inspector, URL_2); + + 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/devtools/client/inspector/test/browser_inspector_destroy-before-ready.js b/devtools/client/inspector/test/browser_inspector_destroy-before-ready.js new file mode 100644 index 000000000..ac8ad5d37 --- /dev/null +++ b/devtools/client/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"); + yield onInspectorSelected; +}); diff --git a/devtools/client/inspector/test/browser_inspector_expand-collapse.js b/devtools/client/inspector/test/browser_inspector_expand-collapse.js new file mode 100644 index 000000000..3b1dcb6b2 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_expand-collapse.js @@ -0,0 +1,64 @@ +/* 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 that context menu items exapnd all and collapse are shown properly. + +const TEST_URL = "data:text/html;charset=utf-8," + + "<div id='parent-node'><div id='child-node'></div></div>"; + +add_task(function* () { + // Test is often exceeding time-out threshold, similar to Bug 1137765 + requestLongerTimeout(2); + + let {inspector} = yield openInspectorForURL(TEST_URL); + + info("Selecting the parent node"); + + let front = yield getNodeFrontForSelector("#parent-node", inspector); + + yield selectNode(front, inspector); + + info("Simulating context menu click on the selected node container."); + let allMenuItems = openContextMenuAndGetAllItems(inspector, { + target: getContainerForNodeFront(front, inspector).tagLine, + }); + let nodeMenuCollapseElement = + allMenuItems.find(item => item.id === "node-menu-collapse"); + let nodeMenuExpandElement = + allMenuItems.find(item => item.id === "node-menu-expand"); + + ok(nodeMenuCollapseElement.disabled, "Collapse option is disabled"); + ok(!nodeMenuExpandElement.disabled, "ExpandAll option is enabled"); + + info("Testing whether expansion works properly"); + nodeMenuExpandElement.click(); + + info("Waiting for expansion to occur"); + yield waitForMultipleChildrenUpdates(inspector); + let markUpContainer = getContainerForNodeFront(front, inspector); + ok(markUpContainer.expanded, "node has been successfully expanded"); + + // reselecting node after expansion + yield selectNode(front, inspector); + + info("Testing whether collapse works properly"); + info("Simulating context menu click on the selected node container."); + allMenuItems = openContextMenuAndGetAllItems(inspector, { + target: getContainerForNodeFront(front, inspector).tagLine, + }); + nodeMenuCollapseElement = + allMenuItems.find(item => item.id === "node-menu-collapse"); + nodeMenuExpandElement = + allMenuItems.find(item => item.id === "node-menu-expand"); + + ok(!nodeMenuCollapseElement.disabled, "Collapse option is enabled"); + ok(!nodeMenuExpandElement.disabled, "ExpandAll option is enabled"); + nodeMenuCollapseElement.click(); + + info("Waiting for collapse to occur"); + yield waitForMultipleChildrenUpdates(inspector); + ok(!markUpContainer.expanded, "node has been successfully collapsed"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_gcli-inspect-command.js b/devtools/client/inspector/test/browser_inspector_gcli-inspect-command.js new file mode 100644 index 000000000..dca8167c4 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_gcli-inspect-command.js @@ -0,0 +1,118 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint key-spacing: 0 */ +"use strict"; + +// Testing that the gcli 'inspect' command works as it should. + +const TEST_URI = URL_ROOT + "doc_inspector_gcli-inspect-command.html"; + +add_task(function* () { + return helpers.addTabWithToolbar(TEST_URI, Task.async(function* (options) { + let {inspector} = yield openInspector(); + + let checkSelection = Task.async(function* (selector) { + let node = yield getNodeFront(selector, inspector); + is(inspector.selection.nodeFront, node, "the current selection is correct"); + }); + + yield helpers.audit(options, [ + { + setup: "inspect", + check: { + input: "inspect", + hints: " <selector>", + markup: "VVVVVVV", + status: "ERROR", + args: { + selector: { + message: "Value required for \u2018selector\u2019." + }, + } + }, + }, + { + setup: "inspect div", + check: { + input: "inspect div", + hints: "", + markup: "VVVVVVVVVVV", + status: "VALID", + args: { + selector: { message: "" }, + } + }, + exec: {}, + post: () => checkSelection("div"), + }, + { + setup: "inspect .someclass", + check: { + input: "inspect .someclass", + hints: "", + markup: "VVVVVVVVVVVVVVVVVV", + status: "VALID", + args: { + selector: { message: "" }, + } + }, + exec: {}, + post: () => checkSelection(".someclass"), + }, + { + setup: "inspect #someid", + check: { + input: "inspect #someid", + hints: "", + markup: "VVVVVVVVVVVVVVV", + status: "VALID", + args: { + selector: { message: "" }, + } + }, + exec: {}, + post: () => checkSelection("#someid"), + }, + { + setup: "inspect button[disabled]", + check: { + input: "inspect button[disabled]", + hints: "", + markup: "VVVVVVVVVVVVVVVVVVVVVVVV", + status: "VALID", + args: { + selector: { message: "" }, + } + }, + exec: {}, + post: () => checkSelection("button[disabled]"), + }, + { + setup: "inspect p>strong", + check: { + input: "inspect p>strong", + hints: "", + markup: "VVVVVVVVVVVVVVVV", + status: "VALID", + args: { + selector: { message: "" }, + } + }, + exec: {}, + post: () => checkSelection("p>strong"), + }, + { + setup: "inspect :root", + check: { + input: "inspect :root", + hints: "", + markup: "VVVVVVVVVVVVV", + status: "VALID" + }, + exec: {}, + post: () => checkSelection(":root"), + }, + ]); + })); +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-01.js b/devtools/client/inspector/test/browser_inspector_highlighter-01.js new file mode 100644 index 000000000..946b8c3c8 --- /dev/null +++ b/devtools/client/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"); + let {toolbox, inspector, testActor} = yield openInspectorForURL( + "data:text/html;charset=utf-8,<h1>foo</h1><span>bar</span>"); + + let isVisible = yield testActor.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 testActor.isHighlighting(); + ok(isVisible, "The highlighter is shown on a markup container hover"); + + ok((yield testActor.assertHighlightedNode("h1")), + "The highlighter highlights the right node"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-02.js b/devtools/client/inspector/test/browser_inspector_highlighter-02.js new file mode 100644 index 000000000..37eb9389e --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-02.js @@ -0,0 +1,39 @@ +/* -*- 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 = URL_ROOT + "doc_inspector_highlighter.html"; + +add_task(function* () { + let {inspector, testActor} = yield openInspectorForURL(TEST_URI); + + info("Selecting the simple, non-transformed DIV"); + yield selectAndHighlightNode("#simple-div", inspector); + + let isVisible = yield testActor.isHighlighting(); + ok(isVisible, "The highlighter is shown"); + ok((yield testActor.assertHighlightedNode("#simple-div")), + "The highlighter's outline corresponds to the simple div"); + yield testActor.isNodeCorrectlyHighlighted("#simple-div", is, "non-zoomed"); + + info("Selecting the rotated DIV"); + yield selectAndHighlightNode("#rotated-div", inspector); + + isVisible = yield testActor.isHighlighting(); + ok(isVisible, "The highlighter is shown"); + yield testActor.isNodeCorrectlyHighlighted("#rotated-div", is, "rotated"); + + info("Selecting the zero width height DIV"); + yield selectAndHighlightNode("#widthHeightZero-div", inspector); + + isVisible = yield testActor.isHighlighting(); + ok(isVisible, "The highlighter is shown"); + yield testActor.isNodeCorrectlyHighlighted("#widthHeightZero-div", is, + "zero width height"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-03.js b/devtools/client/inspector/test/browser_inspector_highlighter-03.js new file mode 100644 index 000000000..344b5c6c8 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-03.js @@ -0,0 +1,70 @@ +/* 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, testActor } = yield openInspectorForURL(TEST_URI); + + info("Waiting for box mode to show."); + let body = yield getNodeFront("body", inspector); + yield inspector.highlighter.showBoxModel(body); + + info("Waiting for element picker to become active."); + yield startPicker(toolbox); + + info("Moving mouse over iframe padding."); + yield moveMouseOver("iframe", 1, 1); + + info("Performing checks"); + yield testActor.isNodeCorrectlyHighlighted("iframe", is); + + info("Scrolling the document"); + yield testActor.setProperty("iframe", "style", "margin-bottom: 2000px"); + yield testActor.eval("window.scrollBy(0, 40);"); + + // target the body within the iframe + let iframeBodySelector = ["iframe", "body"]; + + info("Moving mouse over iframe body"); + yield moveMouseOver("iframe", 40, 40); + + ok((yield testActor.assertHighlightedNode(iframeBodySelector)), + "highlighter shows the right node"); + yield testActor.isNodeCorrectlyHighlighted(iframeBodySelector, is); + + info("Waiting for the element picker to deactivate."); + yield inspector.toolbox.highlighterUtils.stopPicker(); + + function moveMouseOver(selector, x, y) { + info("Waiting for element " + selector + " to be highlighted"); + testActor.synthesizeMouse({selector, x, y, options: {type: "mousemove"}}); + return inspector.toolbox.once("picker-node-hovered"); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-04.js b/devtools/client/inspector/test/browser_inspector_highlighter-04.js new file mode 100644 index 000000000..d87f20e94 --- /dev/null +++ b/devtools/client/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-infobar-container", + "box-model-infobar-tagname", + "box-model-infobar-id", + "box-model-infobar-classes", + "box-model-infobar-pseudo-classes", + "box-model-infobar-dimensions"]; + +add_task(function* () { + let {inspector, testActor} = yield openInspectorForURL(TEST_URL); + + info("Show the box-model highlighter"); + let divFront = yield getNodeFront("div", inspector); + yield inspector.highlighter.showBoxModel(divFront); + + for (let id of ELEMENTS) { + let foundId = yield testActor.getHighlighterNodeAttribute(id, "id"); + is(foundId, id, "Element " + id + " found"); + } + + info("Hide the box-model highlighter"); + yield inspector.highlighter.hideBoxModel(); +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-by-type.js b/devtools/client/inspector/test/browser_inspector_highlighter-by-type.js new file mode 100644 index 000000000..485d9db0e --- /dev/null +++ b/devtools/client/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} = 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/devtools/client/inspector/test/browser_inspector_highlighter-cancel.js b/devtools/client/inspector/test/browser_inspector_highlighter-cancel.js new file mode 100644 index 000000000..f1022bb50 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-cancel.js @@ -0,0 +1,52 @@ +/* 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 canceling the element picker zooms back on the focused element. Bug 1224304. + +const TEST_URL = URL_ROOT + "doc_inspector_long-divs.html"; + +add_task(function* () { + let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL); + + yield selectAndHighlightNode("#focus-here", inspector); + ok((yield testActor.assertHighlightedNode("#focus-here")), + "The highlighter focuses on div#focus-here"); + ok(isSelectedMarkupNodeInView(), + "The currently selected node is on the screen."); + + // Start the picker but skip focusing manually focusing on the target, let the element + // picker do the focusing. + yield startPicker(toolbox, true); + yield moveMouseOver("#zoom-here"); + ok(!isSelectedMarkupNodeInView(), + "The currently selected node is off the screen."); + + yield cancelPickerByShortcut(); + ok(isSelectedMarkupNodeInView(), + "The currently selected node is focused back on the screen."); + + function cancelPickerByShortcut() { + info("Key pressed. Waiting for picker to be canceled."); + testActor.synthesizeKey({key: "VK_ESCAPE", options: {}}); + return inspector.toolbox.once("picker-canceled"); + } + + function moveMouseOver(selector) { + info(`Waiting for element ${selector} to be hovered in the markup view`); + testActor.synthesizeMouse({ + options: {type: "mousemove"}, + center: true, + selector: selector + }); + return inspector.markup.once("showcontainerhovered"); + } + + function isSelectedMarkupNodeInView() { + const selectedNodeContainer = inspector.markup._selectedContainer.elt; + const bounds = selectedNodeContainer.getBoundingClientRect(); + return bounds.top > 0 && bounds.bottom > 0; + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-comments.js b/devtools/client/inspector/test/browser_inspector_highlighter-comments.js new file mode 100644 index 000000000..104395227 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-comments.js @@ -0,0 +1,105 @@ +/* -*- 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 = URL_ROOT + + "doc_inspector_highlighter-comments.html"; + +add_task(function* () { + let {inspector, testActor} = 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(); + + info("Hovering over a text node and waiting for highlighter to appear."); + yield hoverTextNode("Visible text node"); + yield assertHighlighterShownOnTextNode("body", 14); + + 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); + } + } + return null; + } + + function hoverTextNode(text) { + info(`Hovering the text node "${text}" in the markup view`); + let container = [...markupView._containers].filter(([nodeFront]) => { + return nodeFront.nodeType === Ci.nsIDOMNode.TEXT_NODE && + nodeFront._form.nodeValue.trim() === text.trim(); + })[0][1]; + return hoverContainer(container); + } + + function* assertHighlighterShownOn(selector) { + ok((yield testActor.assertHighlightedNode(selector)), + "Highlighter is shown on the right node: " + selector); + } + + function* assertHighlighterShownOnTextNode(parentSelector, childNodeIndex) { + ok((yield testActor.assertHighlightedTextNode(parentSelector, childNodeIndex)), + "Highlighter is shown on the right text node"); + } + + function* assertHighlighterHidden() { + let isVisible = yield testActor.isHighlighting(); + ok(!isVisible, "Highlighter is hidden"); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-cssgrid_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-cssgrid_01.js new file mode 100644 index 000000000..ef21b88c9 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-cssgrid_01.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 the creation of the canvas highlighter element of the css grid highlighter. + +const TEST_URL = ` + <style type='text/css'> + #grid { + display: grid; + } + #cell1 { + grid-column: 1; + grid-row: 1; + } + #cell2 { + grid-column: 2; + grid-row: 1; + } + #cell3 { + grid-column: 1; + grid-row: 2; + } + #cell4 { + grid-column: 2; + grid-row: 2; + } + </style> + <div id="grid"> + <div id="cell1">cell1</div> + <div id="cell2">cell2</div> + <div id="cell3">cell3</div> + <div id="cell4">cell4</div> + </div> +`; + +const HIGHLIGHTER_TYPE = "CssGridHighlighter"; + +add_task(function* () { + let {inspector, testActor} = yield openInspectorForURL( + "data:text/html;charset=utf-8," + encodeURIComponent(TEST_URL)); + let front = inspector.inspector; + let highlighter = yield front.getHighlighterByType(HIGHLIGHTER_TYPE); + + yield isHiddenByDefault(testActor, highlighter); + yield isVisibleWhenShown(testActor, inspector, highlighter); + + yield highlighter.finalize(); +}); + +function* isHiddenByDefault(testActor, highlighterFront) { + info("Checking that the highlighter is hidden by default"); + + let hidden = yield testActor.getHighlighterNodeAttribute( + "css-grid-canvas", "hidden", highlighterFront); + ok(hidden, "The highlighter is hidden by default"); +} + +function* isVisibleWhenShown(testActor, inspector, highlighterFront) { + info("Asking to show the highlighter on the test node"); + + let node = yield getNodeFront("#grid", inspector); + yield highlighterFront.show(node); + + let hidden = yield testActor.getHighlighterNodeAttribute( + "css-grid-canvas", "hidden", highlighterFront); + ok(!hidden, "The highlighter is visible"); + + info("Hiding the highlighter"); + yield highlighterFront.hide(); + + hidden = yield testActor.getHighlighterNodeAttribute( + "css-grid-canvas", "hidden", highlighterFront); + ok(hidden, "The highlighter is hidden"); +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_01.js new file mode 100644 index 000000000..f30d1b590 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_01.js @@ -0,0 +1,152 @@ +/* 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 = ` + <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, testActor} = yield openInspectorForURL( + "data:text/html;charset=utf-8," + encodeURI(TEST_URL)); + let front = inspector.inspector; + + let highlighter = yield front.getHighlighterByType("CssTransformHighlighter"); + + yield isHiddenByDefault(testActor, highlighter); + yield has2PolygonsAnd4Lines(testActor, highlighter); + yield isNotShownForUntransformed(testActor, inspector, highlighter); + yield isNotShownForInline(testActor, inspector, highlighter); + yield isVisibleWhenShown(testActor, inspector, highlighter); + yield linesLinkThePolygons(testActor, inspector, highlighter); + + yield highlighter.finalize(); +}); + +function* isHiddenByDefault(testActor, highlighterFront) { + info("Checking that the highlighter is hidden by default"); + + let hidden = yield testActor.getHighlighterNodeAttribute( + "css-transform-elements", "hidden", highlighterFront); + ok(hidden, "The highlighter is hidden by default"); +} + +function* has2PolygonsAnd4Lines(testActor, highlighterFront) { + info("Checking that the highlighter is made up of 4 lines and 2 polygons"); + + let value = yield testActor.getHighlighterNodeAttribute( + "css-transform-untransformed", "class", highlighterFront); + is(value, "css-transform-untransformed", "The untransformed polygon exists"); + + value = yield testActor.getHighlighterNodeAttribute( + "css-transform-transformed", "class", highlighterFront); + is(value, "css-transform-transformed", "The transformed polygon exists"); + + for (let nb of ["1", "2", "3", "4"]) { + value = yield testActor.getHighlighterNodeAttribute( + "css-transform-line" + nb, "class", highlighterFront); + is(value, "css-transform-line", "The line " + nb + " exists"); + } +} + +function* isNotShownForUntransformed(testActor, inspector, highlighterFront) { + info("Asking to show the highlighter on the untransformed test node"); + + let node = yield getNodeFront("#untransformed", inspector); + yield highlighterFront.show(node); + + let hidden = yield testActor.getHighlighterNodeAttribute( + "css-transform-elements", "hidden", highlighterFront); + ok(hidden, "The highlighter is still hidden"); +} + +function* isNotShownForInline(testActor, inspector, highlighterFront) { + info("Asking to show the highlighter on the inline test node"); + + let node = yield getNodeFront("#inline", inspector); + yield highlighterFront.show(node); + + let hidden = yield testActor.getHighlighterNodeAttribute( + "css-transform-elements", "hidden", highlighterFront); + ok(hidden, "The highlighter is still hidden"); +} + +function* isVisibleWhenShown(testActor, inspector, highlighterFront) { + info("Asking to show the highlighter on the test node"); + + let node = yield getNodeFront("#transformed", inspector); + yield highlighterFront.show(node); + + let hidden = yield testActor.getHighlighterNodeAttribute( + "css-transform-elements", "hidden", highlighterFront); + ok(!hidden, "The highlighter is visible"); + + info("Hiding the highlighter"); + yield highlighterFront.hide(); + + hidden = yield testActor.getHighlighterNodeAttribute( + "css-transform-elements", "hidden", highlighterFront); + ok(hidden, "The highlighter is hidden"); +} + +function* linesLinkThePolygons(testActor, inspector, highlighterFront) { + 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 testActor.getHighlighterNodeAttribute( + "css-transform-line" + nb, "x1", highlighterFront); + let y1 = yield testActor.getHighlighterNodeAttribute( + "css-transform-line" + nb, "y1", highlighterFront); + let x2 = yield testActor.getHighlighterNodeAttribute( + "css-transform-line" + nb, "x2", highlighterFront); + let y2 = yield testActor.getHighlighterNodeAttribute( + "css-transform-line" + nb, "y2", highlighterFront); + lines.push({x1, y1, x2, y2}); + } + + let points1 = yield testActor.getHighlighterNodeAttribute( + "css-transform-untransformed", "points", highlighterFront); + points1 = points1.split(" "); + + let points2 = yield testActor.getHighlighterNodeAttribute( + "css-transform-transformed", "points", highlighterFront); + 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/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_02.js new file mode 100644 index 000000000..52e3b0146 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_02.js @@ -0,0 +1,56 @@ +/* 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 getAdjustedQuads. + +There's a separate test for checking that getAdjustedQuads actually returns +sensible values +(devtools/client/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 = URL_ROOT + "doc_inspector_highlighter_csstransform.html"; + +add_task(function* () { + let {inspector, testActor} = 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 testActor.getAllAdjustedQuads("#test-node"); + let [expected] = data.border; + + let points = yield testActor.getHighlighterNodeAttribute( + "css-transform-transformed", "points", highlighter); + 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/devtools/client/inspector/test/browser_inspector_highlighter-embed.js b/devtools/client/inspector/test/browser_inspector_highlighter-embed.js new file mode 100644 index 000000000..23cd4332a --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-embed.js @@ -0,0 +1,30 @@ +/* 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 can go inside <embed> elements + +const TEST_URL = URL_ROOT + "doc_inspector_embed.html"; + +add_task(function* () { + let {inspector} = yield openInspectorForURL(TEST_URL); + + info("Get a node inside the <embed> element and select/highlight it"); + let body = yield getEmbeddedBody(inspector); + yield selectAndHighlightNode(body, inspector); + + let selectedNode = inspector.selection.nodeFront; + is(selectedNode.tagName.toLowerCase(), "body", "The selected node is <body>"); + ok(selectedNode.baseURI.endsWith("doc_inspector_menu.html"), + "The selected node is the <body> node inside the <embed> element"); +}); + +function* getEmbeddedBody({walker}) { + let embed = yield walker.querySelector(walker.rootNode, "embed"); + let {nodes} = yield walker.children(embed); + let contentDoc = nodes[0]; + let body = yield walker.querySelector(contentDoc, "body"); + return body; +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-clipboard.js b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-clipboard.js new file mode 100644 index 000000000..2d91f81a7 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-clipboard.js @@ -0,0 +1,39 @@ +/* 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 eyedropper can copy colors to the clipboard + +const HIGHLIGHTER_TYPE = "EyeDropper"; +const ID = "eye-dropper-"; +const TEST_URI = "data:text/html;charset=utf-8,<style>html{background:red}</style>"; + +add_task(function* () { + let helper = yield openInspectorForURL(TEST_URI) + .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE)); + helper.prefix = ID; + + let {show, finalize, + waitForElementAttributeSet, waitForElementAttributeRemoved} = helper; + + info("Show the eyedropper with the copyOnSelect option"); + yield show("html", {copyOnSelect: true}); + + info("Make sure to wait until the eyedropper is done taking a screenshot of the page"); + yield waitForElementAttributeSet("root", "drawn", helper); + + yield waitForClipboardPromise(() => { + info("Activate the eyedropper so the background color is copied"); + EventUtils.synthesizeKey("VK_RETURN", {}); + }, "#FF0000"); + + ok(true, "The clipboard contains the right value"); + + yield waitForElementAttributeRemoved("root", "drawn", helper); + yield waitForElementAttributeSet("root", "hidden", helper); + ok(true, "The eyedropper is now hidden"); + + finalize(); +}); + diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-csp.js b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-csp.js new file mode 100644 index 000000000..0cd425b56 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-csp.js @@ -0,0 +1,30 @@ +/* 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 eyedropper opens correctly even when the page defines CSP headers. + +const HIGHLIGHTER_TYPE = "EyeDropper"; +const ID = "eye-dropper-"; +const TEST_URI = URL_ROOT + "doc_inspector_csp.html"; + +add_task(function* () { + let helper = yield openInspectorForURL(TEST_URI) + .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE)); + helper.prefix = ID; + let {show, hide, finalize, isElementHidden, waitForElementAttributeSet} = helper; + + info("Try to display the eyedropper"); + yield show("html"); + + let hidden = yield isElementHidden("root"); + ok(!hidden, "The eyedropper is now shown"); + + info("Wait until the eyedropper is done taking a screenshot of the page"); + yield waitForElementAttributeSet("root", "drawn", helper); + ok(true, "The image data was retrieved successfully from the window"); + + yield hide(); + finalize(); +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-events.js b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-events.js new file mode 100644 index 000000000..49543b5ce --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-events.js @@ -0,0 +1,141 @@ +/* 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 eyedropper mouse and keyboard handling. + +const HIGHLIGHTER_TYPE = "EyeDropper"; +const ID = "eye-dropper-"; +const TEST_URI = ` +<style> + html{width:100%;height:100%;} +</style> +<body>eye-dropper test</body>`; + +const MOVE_EVENTS_DATA = [ + {type: "mouse", x: 200, y: 100, expected: {x: 200, y: 100}}, + {type: "mouse", x: 100, y: 200, expected: {x: 100, y: 200}}, + {type: "keyboard", key: "VK_LEFT", expected: {x: 99, y: 200}}, + {type: "keyboard", key: "VK_LEFT", shift: true, expected: {x: 89, y: 200}}, + {type: "keyboard", key: "VK_RIGHT", expected: {x: 90, y: 200}}, + {type: "keyboard", key: "VK_RIGHT", shift: true, expected: {x: 100, y: 200}}, + {type: "keyboard", key: "VK_DOWN", expected: {x: 100, y: 201}}, + {type: "keyboard", key: "VK_DOWN", shift: true, expected: {x: 100, y: 211}}, + {type: "keyboard", key: "VK_UP", expected: {x: 100, y: 210}}, + {type: "keyboard", key: "VK_UP", shift: true, expected: {x: 100, y: 200}}, + // Mouse initialization for left and top snapping + {type: "mouse", x: 7, y: 7, expected: {x: 7, y: 7}}, + // Left Snapping + {type: "keyboard", key: "VK_LEFT", shift: true, expected: {x: 0, y: 7}, + desc: "Left Snapping to x=0"}, + // Top Snapping + {type: "keyboard", key: "VK_UP", shift: true, expected: {x: 0, y: 0}, + desc: "Top Snapping to y=0"}, + // Mouse initialization for right snapping + { + type: "mouse", + x: (width, height) => width - 5, + y: 0, + expected: { + x: (width, height) => width - 5, + y: 0 + } + }, + // Right snapping + { + type: "keyboard", + key: "VK_RIGHT", + shift: true, + expected: { + x: (width, height) => width, + y: 0 + }, + desc: "Right snapping to x=max window width available" + }, + // Mouse initialization for bottom snapping + { + type: "mouse", + x: 0, + y: (width, height) => height - 5, + expected: { + x: 0, + y: (width, height) => height - 5 + } + }, + // Bottom snapping + { + type: "keyboard", + key: "VK_DOWN", + shift: true, + expected: { + x: 0, + y: (width, height) => height + }, + desc: "Bottom snapping to y=max window height available" + }, +]; + +add_task(function* () { + let {inspector, testActor} = yield openInspectorForURL( + "data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let helper = yield getHighlighterHelperFor(HIGHLIGHTER_TYPE)({inspector, testActor}); + + helper.prefix = ID; + + yield helper.show("html"); + yield respondsToMoveEvents(helper, testActor); + yield respondsToReturnAndEscape(helper); + + helper.finalize(); +}); + +function* respondsToMoveEvents(helper, testActor) { + info("Checking that the eyedropper responds to events from the mouse and keyboard"); + let {mouse} = helper; + let {width, height} = yield testActor.getBoundingClientRect("html"); + + for (let {type, x, y, key, shift, expected, desc} of MOVE_EVENTS_DATA) { + x = typeof x === "function" ? x(width, height) : x; + y = typeof y === "function" ? y(width, height) : y; + expected.x = typeof expected.x === "function" ? + expected.x(width, height) : expected.x; + expected.y = typeof expected.y === "function" ? + expected.y(width, height) : expected.y; + + if (typeof desc === "undefined") { + info(`Simulating a ${type} event to move to ${expected.x} ${expected.y}`); + } else { + info(`Simulating ${type} event: ${desc}`); + } + + if (type === "mouse") { + yield mouse.move(x, y); + } else if (type === "keyboard") { + let options = shift ? {shiftKey: true} : {}; + yield EventUtils.synthesizeKey(key, options); + } + yield checkPosition(expected, helper); + } +} + +function* checkPosition({x, y}, {getElementAttribute}) { + let style = yield getElementAttribute("root", "style"); + is(style, `top:${y}px;left:${x}px;`, + `The eyedropper is at the expected ${x} ${y} position`); +} + +function* respondsToReturnAndEscape({isElementHidden, show}) { + info("Simulating return to select the color and hide the eyedropper"); + + yield EventUtils.synthesizeKey("VK_RETURN", {}); + let hidden = yield isElementHidden("root"); + ok(hidden, "The eyedropper has been hidden"); + + info("Showing the eyedropper again and simulating escape to hide it"); + + yield show("html"); + yield EventUtils.synthesizeKey("VK_ESCAPE", {}); + hidden = yield isElementHidden("root"); + ok(hidden, "The eyedropper has been hidden again"); +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-label.js b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-label.js new file mode 100644 index 000000000..02750761b --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-label.js @@ -0,0 +1,115 @@ +/* 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 position of the eyedropper label. +// It should move around when the eyedropper is close to the edges of the viewport so as +// to always stay visible. + +const HIGHLIGHTER_TYPE = "EyeDropper"; +const ID = "eye-dropper-"; + +const HTML = ` +<style> +html, body {height: 100%; margin: 0;} +body {background: linear-gradient(red, gold); display: flex; justify-content: center; + align-items: center;} +</style> +Eyedropper label position test +`; +const TEST_PAGE = "data:text/html;charset=utf-8," + encodeURI(HTML); + +const TEST_DATA = [{ + desc: "Move the mouse to the center of the screen", + getCoordinates: (width, height) => { + return {x: width / 2, y: height / 2}; + }, + expectedPositions: {top: false, right: false, left: false} +}, { + desc: "Move the mouse to the center left", + getCoordinates: (width, height) => { + return {x: 0, y: height / 2}; + }, + expectedPositions: {top: false, right: true, left: false} +}, { + desc: "Move the mouse to the center right", + getCoordinates: (width, height) => { + return {x: width, y: height / 2}; + }, + expectedPositions: {top: false, right: false, left: true} +}, { + desc: "Move the mouse to the bottom center", + getCoordinates: (width, height) => { + return {x: width / 2, y: height}; + }, + expectedPositions: {top: true, right: false, left: false} +}, { + desc: "Move the mouse to the bottom left", + getCoordinates: (width, height) => { + return {x: 0, y: height}; + }, + expectedPositions: {top: true, right: true, left: false} +}, { + desc: "Move the mouse to the bottom right", + getCoordinates: (width, height) => { + return {x: width, y: height}; + }, + expectedPositions: {top: true, right: false, left: true} +}, { + desc: "Move the mouse to the top left", + getCoordinates: (width, height) => { + return {x: 0, y: 0}; + }, + expectedPositions: {top: false, right: true, left: false} +}, { + desc: "Move the mouse to the top right", + getCoordinates: (width, height) => { + return {x: width, y: 0}; + }, + expectedPositions: {top: false, right: false, left: true} +}]; + +add_task(function* () { + let {inspector, testActor} = yield openInspectorForURL(TEST_PAGE); + let helper = yield getHighlighterHelperFor(HIGHLIGHTER_TYPE)({inspector, testActor}); + helper.prefix = ID; + + let {mouse, show, hide, finalize} = helper; + let {width, height} = yield testActor.getBoundingClientRect("html"); + + // This test fails in non-e10s windows if we use width and height. For some reasons, the + // mouse events can't be dispatched/handled properly when we try to move the eyedropper + // to the far right and/or bottom of the screen. So just removing 10px from each side + // fixes it. + width -= 10; + height -= 10; + + info("Show the eyedropper on the page"); + yield show("html"); + + info("Move the eyedropper around and check that the label appears at the right place"); + for (let {desc, getCoordinates, expectedPositions} of TEST_DATA) { + info(desc); + let {x, y} = getCoordinates(width, height); + info(`Moving the mouse to ${x} ${y}`); + yield mouse.move(x, y); + yield checkLabelPositionAttributes(helper, expectedPositions); + } + + info("Hide the eyedropper"); + yield hide(); + finalize(); +}); + +function* checkLabelPositionAttributes(helper, positions) { + for (let position in positions) { + is((yield hasAttribute(helper, position)), positions[position], + `The label was ${positions[position] ? "" : "not "}moved to the ${position}`); + } +} + +function* hasAttribute({getElementAttribute}, name) { + let value = yield getElementAttribute("root", name); + return value !== null; +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-show-hide.js b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-show-hide.js new file mode 100644 index 000000000..86f2ae83d --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-show-hide.js @@ -0,0 +1,42 @@ +/* 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 basic structure of the eye-dropper highlighter. + +const HIGHLIGHTER_TYPE = "EyeDropper"; +const ID = "eye-dropper-"; + +add_task(function* () { + let helper = yield openInspectorForURL("data:text/html;charset=utf-8,eye-dropper test") + .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE)); + helper.prefix = ID; + + yield isInitiallyHidden(helper); + yield canBeShownAndHidden(helper); + + helper.finalize(); +}); + +function* isInitiallyHidden({isElementHidden}) { + info("Checking that the eyedropper is hidden by default"); + + let hidden = yield isElementHidden("root"); + ok(hidden, "The eyedropper is hidden by default"); +} + +function* canBeShownAndHidden({show, hide, isElementHidden, getElementAttribute}) { + info("Asking to show and hide the highlighter actually works"); + + yield show("html"); + let hidden = yield isElementHidden("root"); + ok(!hidden, "The eyedropper is now shown"); + + let style = yield getElementAttribute("root", "style"); + is(style, "top:100px;left:100px;", "The eyedropper is correctly positioned"); + + yield hide(); + hidden = yield isElementHidden("root"); + ok(hidden, "The eyedropper is now hidden again"); +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-xul.js b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-xul.js new file mode 100644 index 000000000..7c44e7275 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-xul.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the eyedropper icons in the toolbar and in the color picker aren't displayed +// when the page isn't an HTML one. + +const TEST_URL = URL_ROOT + "doc_inspector_highlighter_xbl.xul"; +const TEST_URL_2 = + "data:text/html;charset=utf-8,<h1 style='color:red'>HTML test page</h1>"; + +add_task(function* () { + let {inspector} = yield openInspectorForURL(TEST_URL); + + info("Check the inspector toolbar"); + let button = inspector.panelDoc.querySelector("#inspector-eyedropper-toggle"); + ok(isDisabled(button), "The button is hidden in the toolbar"); + + info("Check the color picker"); + yield selectNode("#scale", inspector); + + // Find the color swatch in the rule-view. + let ruleView = inspector.ruleview.view; + let ruleViewDocument = ruleView.styleDocument; + let swatchEl = ruleViewDocument.querySelector(".ruleview-colorswatch"); + + info("Open the color picker"); + let cPicker = ruleView.tooltips.colorPicker; + let onColorPickerReady = cPicker.once("ready"); + swatchEl.click(); + yield onColorPickerReady; + + button = cPicker.tooltip.doc.querySelector("#eyedropper-button"); + ok(isDisabled(button), "The button is disabled in the color picker"); + + info("Navigate to a HTML document"); + yield navigateTo(inspector, TEST_URL_2); + + info("Check the inspector toolbar in HTML document"); + button = inspector.panelDoc.querySelector("#inspector-eyedropper-toggle"); + ok(!isDisabled(button), "The button is enabled in the toolbar"); + + info("Check the color picker in HTML document"); + // Find the color swatch in the rule-view. + yield selectNode("h1", inspector); + + ruleView = inspector.ruleview.view; + ruleViewDocument = ruleView.styleDocument; + swatchEl = ruleViewDocument.querySelector(".ruleview-colorswatch"); + + info("Open the color picker in HTML document"); + cPicker = ruleView.tooltips.colorPicker; + onColorPickerReady = cPicker.once("ready"); + swatchEl.click(); + yield onColorPickerReady; + + button = cPicker.tooltip.doc.querySelector("#eyedropper-button"); + ok(!isDisabled(button), "The button is enabled in the color picker"); +}); + +function isDisabled(button) { + return button.disabled; +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-geometry_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-geometry_01.js new file mode 100644 index 000000000..28a20998c --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-geometry_01.js @@ -0,0 +1,89 @@ +/* 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 geometry highlighter elements. + +const TEST_URL = `data:text/html;charset=utf-8, + <span id='inline'></span> + <div id='positioned' style=' + background:yellow; + position:absolute; + left:5rem; + top:30px; + right:300px; + bottom:10em;'></div> + <div id='sized' style=' + background:red; + width:5em; + height:50%;'></div>`; + +const HIGHLIGHTER_TYPE = "GeometryEditorHighlighter"; + +const ID = "geometry-editor-"; +const SIDES = ["left", "right", "top", "bottom"]; + +add_task(function* () { + let helper = yield openInspectorForURL(TEST_URL) + .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE)); + + let { finalize } = helper; + + helper.prefix = ID; + + yield hasArrowsAndLabelsAndHandlers(helper); + yield isHiddenForNonPositionedNonSizedElement(helper); + yield sideArrowsAreDisplayedForPositionedNode(helper); + + finalize(); +}); + +function* hasArrowsAndLabelsAndHandlers({getElementAttribute}) { + info("Checking that the highlighter has the expected arrows and labels"); + + for (let name of [...SIDES]) { + let value = yield getElementAttribute("arrow-" + name, "class"); + is(value, ID + "arrow " + name, "The " + name + " arrow exists"); + + value = yield getElementAttribute("label-text-" + name, "class"); + is(value, ID + "label-text", "The " + name + " label exists"); + + value = yield getElementAttribute("handler-" + name, "class"); + is(value, ID + "handler-" + name, "The " + name + " handler exists"); + } +} + +function* isHiddenForNonPositionedNonSizedElement( + {show, hide, isElementHidden}) { + info("Asking to show the highlighter on an inline, non p ositioned element"); + + yield show("#inline"); + + for (let name of [...SIDES]) { + let hidden = yield isElementHidden("arrow-" + name); + ok(hidden, "The " + name + " arrow is hidden"); + + hidden = yield isElementHidden("handler-" + name); + ok(hidden, "The " + name + " handler is hidden"); + } +} + +function* sideArrowsAreDisplayedForPositionedNode( + {show, hide, isElementHidden}) { + info("Asking to show the highlighter on the positioned node"); + + yield show("#positioned"); + + for (let name of SIDES) { + let hidden = yield isElementHidden("arrow-" + name); + ok(!hidden, "The " + name + " arrow is visible for the positioned node"); + + hidden = yield isElementHidden("handler-" + name); + ok(!hidden, "The " + name + " handler is visible for the positioned node"); + } + + info("Hiding the highlighter"); + yield hide(); +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-geometry_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-geometry_02.js new file mode 100644 index 000000000..e0681c6f9 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-geometry_02.js @@ -0,0 +1,116 @@ +/* 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/. */ + +/* Globals defined in: devtools/client/inspector/test/head.js */ + +"use strict"; + +// Test that the geometry highlighter labels are correct. + +const TEST_URL = `data:text/html;charset=utf-8, + <div id='positioned' style=' + background:yellow; + position:absolute; + left:5rem; + top:30px; + right:300px; + bottom:10em;'></div> + <div id='positioned2' style=' + background:blue; + position:absolute; + right:10%; + top:5vmin;'>test element</div> + <div id='relative' style=' + background:green; + position:relative; + top:10px; + left:20px; + bottom:30px; + right:40px; + width:100px; + height:100px;'></div> + <div id='relative2' style=' + background:grey; + position:relative; + top:0;bottom:-50px; + height:3em;'>relative</div>`; + +const ID = "geometry-editor-"; +const HIGHLIGHTER_TYPE = "GeometryEditorHighlighter"; + +const POSITIONED_ELEMENT_TESTS = [{ + selector: "#positioned", + expectedLabels: [ + {side: "left", visible: true, label: "5rem"}, + {side: "top", visible: true, label: "30px"}, + {side: "right", visible: true, label: "300px"}, + {side: "bottom", visible: true, label: "10em"} + ] +}, { + selector: "#positioned2", + expectedLabels: [ + {side: "left", visible: false}, + {side: "top", visible: true, label: "5vmin"}, + {side: "right", visible: true, label: "10%"}, + {side: "bottom", visible: false} + ] +}, { + selector: "#relative", + expectedLabels: [ + {side: "left", visible: true, label: "20px"}, + {side: "top", visible: true, label: "10px"}, + {side: "right", visible: false}, + {side: "bottom", visible: false} + ] +}, { + selector: "#relative2", + expectedLabels: [ + {side: "left", visible: false}, + {side: "top", visible: true, label: "0px"}, + {side: "right", visible: false}, + {side: "bottom", visible: false} + ] +}]; + +add_task(function* () { + let helper = yield openInspectorForURL(TEST_URL) + .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE)); + + helper.prefix = ID; + + let { finalize } = helper; + + yield positionLabelsAreCorrect(helper); + + yield finalize(); +}); + +function* positionLabelsAreCorrect( + {show, hide, isElementHidden, getElementTextContent} +) { + info("Highlight nodes and check position labels"); + + for (let {selector, expectedLabels} of POSITIONED_ELEMENT_TESTS) { + info("Testing node " + selector); + + yield show(selector); + + for (let {side, visible, label} of expectedLabels) { + let id = "label-" + side; + + let hidden = yield isElementHidden(id); + if (visible) { + ok(!hidden, "The " + side + " label is visible"); + + let value = yield getElementTextContent(id); + is(value, label, "The " + side + " label textcontent is correct"); + } else { + ok(hidden, "The " + side + " label is hidden"); + } + } + + info("Hiding the highlighter"); + yield hide(); + } +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-geometry_03.js b/devtools/client/inspector/test/browser_inspector_highlighter-geometry_03.js new file mode 100644 index 000000000..0fa7bb96b --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-geometry_03.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/. */ + +/* Globals defined in: devtools/client/inspector/test/head.js */ + +"use strict"; + +// Test that the right arrows/labels are shown even when the css properties are +// in several different css rules. + +const TEST_URL = URL_ROOT + "doc_inspector_highlighter-geometry_01.html"; +const ID = "geometry-editor-"; +const HIGHLIGHTER_TYPE = "GeometryEditorHighlighter"; +const PROPS = ["left", "right", "top", "bottom"]; + +add_task(function* () { + let helper = yield openInspectorForURL(TEST_URL) + .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE)); + + helper.prefix = ID; + + let { finalize } = helper; + + yield checkArrowsLabelsAndHandlers( + "#node2", ["top", "left", "bottom", "right"], + helper); + + yield checkArrowsLabelsAndHandlers("#node3", ["top", "left"], helper); + + yield finalize(); +}); + +function* checkArrowsLabelsAndHandlers(selector, expectedProperties, + {show, hide, isElementHidden} +) { + info("Getting node " + selector + " from the page"); + + yield show(selector); + + for (let name of expectedProperties) { + let hidden = (yield isElementHidden("arrow-" + name)) && + (yield isElementHidden("handler-" + name)); + ok(!hidden, + "The " + name + " label/arrow & handler is visible for node " + selector); + } + + // Testing that the other arrows are hidden + for (let name of PROPS) { + if (expectedProperties.indexOf(name) !== -1) { + continue; + } + let hidden = (yield isElementHidden("arrow-" + name)) && + (yield isElementHidden("handler-" + name)); + ok(hidden, + "The " + name + " arrow & handler is hidden for node " + selector); + } + + info("Hiding the highlighter"); + yield hide(); +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-geometry_04.js b/devtools/client/inspector/test/browser_inspector_highlighter-geometry_04.js new file mode 100644 index 000000000..7f198f6e3 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-geometry_04.js @@ -0,0 +1,85 @@ +/* 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/. */ + + /* Globals defined in: devtools/client/inspector/test/head.js */ + +"use strict"; + +// Test that the arrows and handlers are positioned correctly and have the right +// size. + +const TEST_URL = URL_ROOT + "doc_inspector_highlighter-geometry_01.html"; +const ID = "geometry-editor-"; +const HIGHLIGHTER_TYPE = "GeometryEditorHighlighter"; + +const handlerMap = { + "top": {"cx": "x2", "cy": "y2"}, + "bottom": {"cx": "x2", "cy": "y2"}, + "left": {"cx": "x2", "cy": "y2"}, + "right": {"cx": "x2", "cy": "y2"} +}; + +add_task(function* () { + let helper = yield openInspectorForURL(TEST_URL) + .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE)); + + helper.prefix = ID; + + let { hide, finalize } = helper; + + yield checkArrowsAndHandlers(helper, ".absolute-all-4", { + "top": {x1: 506, y1: 51, x2: 506, y2: 61}, + "bottom": {x1: 506, y1: 451, x2: 506, y2: 251}, + "left": {x1: 401, y1: 156, x2: 411, y2: 156}, + "right": {x1: 901, y1: 156, x2: 601, y2: 156} + }); + + yield checkArrowsAndHandlers(helper, ".relative", { + "top": {x1: 901, y1: 51, x2: 901, y2: 91}, + "left": {x1: 401, y1: 97, x2: 651, y2: 97} + }); + + yield checkArrowsAndHandlers(helper, ".fixed", { + "top": {x1: 25, y1: 0, x2: 25, y2: 400}, + "left": {x1: 0, y1: 425, x2: 0, y2: 425} + }); + + info("Hiding the highlighter"); + yield hide(); + yield finalize(); +}); + +function* checkArrowsAndHandlers(helper, selector, arrows) { + info("Highlighting the test node " + selector); + + yield helper.show(selector); + + for (let side in arrows) { + yield checkArrowAndHandler(helper, side, arrows[side]); + } +} + +function* checkArrowAndHandler({getElementAttribute}, name, expectedCoords) { + info("Checking " + name + "arrow and handler coordinates are correct"); + + let handlerX = yield getElementAttribute("handler-" + name, "cx"); + let handlerY = yield getElementAttribute("handler-" + name, "cy"); + + let expectedHandlerX = yield getElementAttribute("arrow-" + name, + handlerMap[name].cx); + let expectedHandlerY = yield getElementAttribute("arrow-" + name, + handlerMap[name].cy); + + is(handlerX, expectedHandlerX, + "coordinate X for handler " + name + " is correct."); + is(handlerY, expectedHandlerY, + "coordinate Y for handler " + name + " is correct."); + + for (let coordinate in expectedCoords) { + let value = yield getElementAttribute("arrow-" + name, coordinate); + + is(Math.floor(value), expectedCoords[coordinate], + coordinate + " coordinate for arrow " + name + " is correct"); + } +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-geometry_05.js b/devtools/client/inspector/test/browser_inspector_highlighter-geometry_05.js new file mode 100644 index 000000000..649a4be3b --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-geometry_05.js @@ -0,0 +1,119 @@ +/* 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/. */ + + /* Globals defined in: devtools/client/inspector/test/head.js */ + +"use strict"; + +// Test that the arrows/handlers and offsetparent and currentnode elements of +// the geometry highlighter only appear when needed. + +const TEST_URL = URL_ROOT + "doc_inspector_highlighter-geometry_02.html"; +const ID = "geometry-editor-"; +const HIGHLIGHTER_TYPE = "GeometryEditorHighlighter"; + +const TEST_DATA = [{ + selector: "body", + isOffsetParentVisible: false, + isCurrentNodeVisible: false, + hasVisibleArrowsAndHandlers: false +}, { + selector: "h1", + isOffsetParentVisible: false, + isCurrentNodeVisible: false, + hasVisibleArrowsAndHandlers: false +}, { + selector: ".absolute", + isOffsetParentVisible: false, + isCurrentNodeVisible: true, + hasVisibleArrowsAndHandlers: true +}, { + selector: "#absolute-container", + isOffsetParentVisible: false, + isCurrentNodeVisible: true, + hasVisibleArrowsAndHandlers: false +}, { + selector: ".absolute-bottom-right", + isOffsetParentVisible: true, + isCurrentNodeVisible: true, + hasVisibleArrowsAndHandlers: true +}, { + selector: ".absolute-width-margin", + isOffsetParentVisible: true, + isCurrentNodeVisible: true, + hasVisibleArrowsAndHandlers: true +}, { + selector: ".absolute-all-4", + isOffsetParentVisible: true, + isCurrentNodeVisible: true, + hasVisibleArrowsAndHandlers: true +}, { + selector: ".relative", + isOffsetParentVisible: true, + isCurrentNodeVisible: true, + hasVisibleArrowsAndHandlers: true +}, { + selector: ".static", + isOffsetParentVisible: false, + isCurrentNodeVisible: false, + hasVisibleArrowsAndHandlers: false +}, { + selector: ".static-size", + isOffsetParentVisible: false, + isCurrentNodeVisible: true, + hasVisibleArrowsAndHandlers: false +}, { + selector: ".fixed", + isOffsetParentVisible: false, + isCurrentNodeVisible: true, + hasVisibleArrowsAndHandlers: true +}]; + +add_task(function* () { + let helper = yield openInspectorForURL(TEST_URL) + .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE)); + + helper.prefix = ID; + + let { hide, finalize } = helper; + + for (let data of TEST_DATA) { + yield testNode(helper, data); + } + + info("Hiding the highlighter"); + yield hide(); + yield finalize(); +}); + +function* testNode(helper, data) { + let { selector } = data; + yield helper.show(data.selector); + + is((yield isOffsetParentVisible(helper)), data.isOffsetParentVisible, + "The offset-parent highlighter visibility is correct for node " + selector); + is((yield isCurrentNodeVisible(helper)), data.isCurrentNodeVisible, + "The current-node highlighter visibility is correct for node " + selector); + is((yield hasVisibleArrowsAndHandlers(helper)), + data.hasVisibleArrowsAndHandlers, + "The arrows visibility is correct for node " + selector); +} + +function* isOffsetParentVisible({isElementHidden}) { + return !(yield isElementHidden("offset-parent")); +} + +function* isCurrentNodeVisible({isElementHidden}) { + return !(yield isElementHidden("current-node")); +} + +function* hasVisibleArrowsAndHandlers({isElementHidden}) { + for (let side of ["top", "left", "bottom", "right"]) { + let hidden = yield isElementHidden("arrow-" + side); + if (!hidden) { + return !(yield isElementHidden("handler-" + side)); + } + } + return false; +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-geometry_06.js b/devtools/client/inspector/test/browser_inspector_highlighter-geometry_06.js new file mode 100644 index 000000000..cc22473b7 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-geometry_06.js @@ -0,0 +1,166 @@ +/* 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 geometry editor resizes properly an element on all sides, +// with different unit measures, and that arrow/handlers are updated correctly. + +const TEST_URL = URL_ROOT + "doc_inspector_highlighter-geometry_01.html"; +const ID = "geometry-editor-"; +const HIGHLIGHTER_TYPE = "GeometryEditorHighlighter"; + +const SIDES = ["top", "right", "bottom", "left"]; + +// The object below contains all the tests for this unit test. +// The property's name is the test's description, that points to an +// object contains the steps (what side of the geometry editor to drag, +// the amount of pixels) and the expectation. +const TESTS = { + "Drag top's handler along x and y, south-east direction": { + "expects": "Only y axis is used to updated the top's element value", + "drag": "top", + "by": {x: 10, y: 10} + }, + "Drag right's handler along x and y, south-east direction": { + "expects": "Only x axis is used to updated the right's element value", + "drag": "right", + "by": {x: 10, y: 10} + }, + "Drag bottom's handler along x and y, south-east direction": { + "expects": "Only y axis is used to updated the bottom's element value", + "drag": "bottom", + "by": {x: 10, y: 10} + }, + "Drag left's handler along x and y, south-east direction": { + "expects": "Only y axis is used to updated the left's element value", + "drag": "left", + "by": {x: 10, y: 10} + }, + "Drag top's handler along x and y, north-west direction": { + "expects": "Only y axis is used to updated the top's element value", + "drag": "top", + "by": {x: -20, y: -20} + }, + "Drag right's handler along x and y, north-west direction": { + "expects": "Only x axis is used to updated the right's element value", + "drag": "right", + "by": {x: -20, y: -20} + }, + "Drag bottom's handler along x and y, north-west direction": { + "expects": "Only y axis is used to updated the bottom's element value", + "drag": "bottom", + "by": {x: -20, y: -20} + }, + "Drag left's handler along x and y, north-west direction": { + "expects": "Only y axis is used to updated the left's element value", + "drag": "left", + "by": {x: -20, y: -20} + } +}; + +add_task(function* () { + let inspector = yield openInspectorForURL(TEST_URL); + let helper = yield getHighlighterHelperFor(HIGHLIGHTER_TYPE)(inspector); + + helper.prefix = ID; + + let { show, hide, finalize } = helper; + + info("Showing the highlighter"); + yield show("#node2"); + + for (let desc in TESTS) { + yield executeTest(helper, desc, TESTS[desc]); + } + + info("Hiding the highlighter"); + yield hide(); + yield finalize(); +}); + +function* executeTest(helper, desc, data) { + info(desc); + + ok((yield areElementAndHighlighterMovedCorrectly( + helper, data.drag, data.by)), data.expects); +} + +function* areElementAndHighlighterMovedCorrectly(helper, side, by) { + let { mouse, reflow, highlightedNode } = helper; + + let {x, y} = yield getHandlerCoords(helper, side); + + let dx = x + by.x; + let dy = y + by.y; + + let beforeDragStyle = yield highlightedNode.getComputedStyle(); + + // simulate drag & drop + yield mouse.down(x, y); + yield mouse.move(dx, dy); + yield mouse.up(); + + yield reflow(); + + info(`Checking ${side} handler is moved correctly`); + yield isHandlerPositionUpdated(helper, side, x, y, by); + + let delta = (side === "left" || side === "right") ? by.x : by.y; + delta = delta * ((side === "right" || side === "bottom") ? -1 : 1); + + info("Checking element's sides are correct after drag & drop"); + return yield areElementSideValuesCorrect(highlightedNode, beforeDragStyle, + side, delta); +} + +function* isHandlerPositionUpdated(helper, name, x, y, by) { + let {x: afterDragX, y: afterDragY} = yield getHandlerCoords(helper, name); + + if (name === "left" || name === "right") { + is(afterDragX, x + by.x, + `${name} handler's x axis updated.`); + is(afterDragY, y, + `${name} handler's y axis unchanged.`); + } else { + is(afterDragX, x, + `${name} handler's x axis unchanged.`); + is(afterDragY, y + by.y, + `${name} handler's y axis updated.`); + } +} + +function* areElementSideValuesCorrect(node, beforeDragStyle, name, delta) { + let afterDragStyle = yield node.getComputedStyle(); + let isSideCorrect = true; + + for (let side of SIDES) { + let afterValue = Math.round(parseFloat(afterDragStyle[side].value)); + let beforeValue = Math.round(parseFloat(beforeDragStyle[side].value)); + + if (side === name) { + // `isSideCorrect` is used only as test's return value, not to perform + // the actual test, because with `is` instead of `ok` we gather more + // information in case of failure + isSideCorrect = isSideCorrect && (afterValue === beforeValue + delta); + + is(afterValue, beforeValue + delta, + `${side} is updated.`); + } else { + isSideCorrect = isSideCorrect && (afterValue === beforeValue); + + is(afterValue, beforeValue, + `${side} is unchaged.`); + } + } + + return isSideCorrect; +} + +function* getHandlerCoords({getElementAttribute}, side) { + return { + x: Math.round(yield getElementAttribute("handler-" + side, "cx")), + y: Math.round(yield getElementAttribute("handler-" + side, "cy")) + }; +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-hover_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-hover_01.js new file mode 100644 index 000000000..85f897080 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-hover_01.js @@ -0,0 +1,41 @@ +/* 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 {inspector, testActor} = yield openInspectorForURL(TEST_URL); + + info("hovering over the <p> line in the markup-view"); + yield hoverContainer("p", inspector); + let isVisible = yield testActor.isHighlighting(); + ok(isVisible, "the highlighter is still visible"); + + info("selecting the <p> line by clicking in the markup-view"); + yield clickContainer("p", inspector); + + yield testActor.setProperty("p", "textContent", "wait for it ...."); + info("wait and see if the highlighter stays visible even after the node " + + "was selected"); + yield waitForTheBrieflyShowBoxModelTimeout(); + + yield testActor.setProperty("p", "textContent", "dary!!!!"); + isVisible = yield testActor.isHighlighting(); + ok(isVisible, "the highlighter is still visible"); +}); + +function waitForTheBrieflyShowBoxModelTimeout() { + let deferred = defer(); + // Note that the current timeout is 1 sec and is neither configurable nor + // exported anywhere we can access, so hard-coding the timeout + setTimeout(deferred.resolve, 1500); + return deferred.promise; +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-hover_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-hover_02.js new file mode 100644 index 000000000..e853b3963 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-hover_02.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 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 {inspector, testActor} = 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 testActor.isHighlighting(); + ok(isVisible, "the highlighter is shown"); + + info("listen to the highlighter's hidden event"); + let onHidden = testActor.waitForHighlighterEvent("hidden", + inspector.highlighter); + info("mouse-leave the markup-view"); + yield mouseLeaveMarkupView(inspector); + yield onHidden; + isVisible = yield testActor.isHighlighting(); + 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 testActor.isHighlighting(); + ok(isVisible, "the highlighter is visible again"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-hover_03.js b/devtools/client/inspector/test/browser_inspector_highlighter-hover_03.js new file mode 100644 index 000000000..fcd88be7f --- /dev/null +++ b/devtools/client/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", {}, inspector.panelWin); + yield onUpdated; + onUpdated = inspector.once("inspector-updated"); + EventUtils.synthesizeKey("VK_DOWN", {}, inspector.panelWin); + 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", {}, inspector.panelWin); + yield onUpdated; + yield isHighlighting("#one", "<p#one> is highlighted again"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-iframes_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-iframes_01.js new file mode 100644 index 000000000..6475937c4 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-iframes_01.js @@ -0,0 +1,64 @@ +/* -*- 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 {inspector, testActor} = yield openInspectorForURL(TEST_URI); + let outerFrameDiv = ["iframe", "div"]; + let innerFrameDiv = ["iframe", "iframe", "div"]; + + info("Waiting for element picker to activate."); + yield startPicker(inspector.toolbox); + + info("Moving mouse over outerFrameDiv"); + yield moveMouseOver(outerFrameDiv); + ok((yield testActor.assertHighlightedNode(outerFrameDiv)), + "outerFrameDiv is highlighted."); + + info("Moving mouse over innerFrameDiv"); + yield moveMouseOver(innerFrameDiv); + ok((yield testActor.assertHighlightedNode(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(selector) { + info("Waiting for element " + selector + " to be highlighted"); + testActor.synthesizeMouse({ + selector: selector, + options: {type: "mousemove"}, + center: true + }).then(() => inspector.toolbox.once("picker-node-hovered")); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-iframes_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-iframes_02.js new file mode 100644 index 000000000..12f44ce32 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-iframes_02.js @@ -0,0 +1,59 @@ +/* -*- 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 positioned when switching context +// to an iframe that has an offset from the parent viewport (eg. 100px margin) + +const TEST_URI = "data:text/html;charset=utf-8," + + "<div id=\"outer\"></div>" + + "<iframe style='margin:100px' src='data:text/html," + + "<div id=\"inner\">Look I am here!</div>'>"; + +add_task(function* () { + info("Enable command-button-frames preference setting"); + Services.prefs.setBoolPref("devtools.command-button-frames.enabled", true); + let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URI); + + info("Switch to the iframe context."); + yield switchToFrameContext(1, toolbox, inspector); + + info("Check navigation was successful."); + let hasOuterNode = yield testActor.hasNode("#outer"); + ok(!hasOuterNode, "Check testActor has no access to outer element"); + let hasTestNode = yield testActor.hasNode("#inner"); + ok(hasTestNode, "Check testActor has access to inner element"); + + info("Check highlighting is correct after switching iframe context"); + yield selectAndHighlightNode("#inner", inspector); + let isHighlightCorrect = yield testActor.assertHighlightedNode("#inner"); + ok(isHighlightCorrect, "The selected node is properly highlighted."); + + info("Cleanup command-button-frames preferences."); + Services.prefs.clearUserPref("devtools.command-button-frames.enabled"); +}); + +/** + * Helper designed to switch context to another frame at the provided index. + * Returns a promise that will resolve when the navigation is complete. + * @return {Promise} + */ +function* switchToFrameContext(frameIndex, toolbox, inspector) { + // Open frame menu and wait till it's available on the screen. + let btn = toolbox.doc.getElementById("command-button-frames"); + let menu = toolbox.showFramesMenu({target: btn}); + yield once(menu, "open"); + + info("Select the iframe in the frame list."); + let newRoot = inspector.once("new-root"); + + menu.items[frameIndex].click(); + + yield newRoot; + yield inspector.once("inspector-updated"); + + info("Navigation to the iframe is done."); +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-inline.js b/devtools/client/inspector/test/browser_inspector_highlighter-inline.js new file mode 100644 index 000000000..4e39a92f9 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-inline.js @@ -0,0 +1,76 @@ +/* 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"; + +requestLongerTimeout(2); + +// Test that highlighting various inline boxes displays the right number of +// polygons in the page. + +const TEST_URL = 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 {inspector, testActor} = 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 testActor.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 testActor.getHighlighterRegionPath(region); + 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 testActor.getGuidesRectangle(); + + 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/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_01.js new file mode 100644 index 000000000..100dd1e6e --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_01.js @@ -0,0 +1,64 @@ +/* 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 keybindings for Picker work alright + +const TEST_URL = URL_ROOT + "doc_inspector_highlighter_dom.html"; + +add_task(function* () { + let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL); + + yield startPicker(toolbox); + + info("Selecting the simple-div1 DIV"); + yield moveMouseOver("#simple-div1"); + + ok((yield testActor.assertHighlightedNode("#simple-div1")), + "The highlighter shows #simple-div1. OK."); + + // First Child selection + info("Testing first-child selection."); + + yield doKeyHover({key: "VK_RIGHT", options: {}}); + ok((yield testActor.assertHighlightedNode("#useless-para")), + "The highlighter shows #useless-para. OK."); + + info("Selecting the useful-para paragraph DIV"); + yield moveMouseOver("#useful-para"); + ok((yield testActor.assertHighlightedNode("#useful-para")), + "The highlighter shows #useful-para. OK."); + + yield doKeyHover({key: "VK_RIGHT", options: {}}); + ok((yield testActor.assertHighlightedNode("#bold")), + "The highlighter shows #bold. OK."); + + info("Going back up to the simple-div1 DIV"); + yield doKeyHover({key: "VK_LEFT", options: {}}); + yield doKeyHover({key: "VK_LEFT", options: {}}); + ok((yield testActor.assertHighlightedNode("#simple-div1")), + "The highlighter shows #simple-div1. OK."); + + info("First child selection test Passed."); + + info("Stopping the picker"); + yield toolbox.highlighterUtils.stopPicker(); + + function doKeyHover(args) { + info("Key pressed. Waiting for element to be highlighted/hovered"); + testActor.synthesizeKey(args); + return inspector.toolbox.once("picker-node-hovered"); + } + + function moveMouseOver(selector) { + info("Waiting for element " + selector + " to be highlighted"); + testActor.synthesizeMouse({ + options: {type: "mousemove"}, + center: true, + selector: selector + }); + return inspector.toolbox.once("picker-node-hovered"); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_02.js new file mode 100644 index 000000000..96d5449f9 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_02.js @@ -0,0 +1,64 @@ +/* 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 keybindings for Picker work alright + +const TEST_URL = URL_ROOT + "doc_inspector_highlighter_dom.html"; + +add_task(function* () { + let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL); + + yield startPicker(toolbox); + + // Previously chosen child memory + info("Testing whether previously chosen child is remembered"); + + info("Selecting the ahoy paragraph DIV"); + yield moveMouseOver("#ahoy"); + + yield doKeyHover({key: "VK_LEFT", options: {}}); + ok((yield testActor.assertHighlightedNode("#simple-div2")), + "The highlighter shows #simple-div2. OK."); + + yield doKeyHover({key: "VK_RIGHT", options: {}}); + ok((yield testActor.assertHighlightedNode("#ahoy")), + "The highlighter shows #ahoy. OK."); + + info("Going back up to the complex-div DIV"); + yield doKeyHover({key: "VK_LEFT", options: {}}); + yield doKeyHover({key: "VK_LEFT", options: {}}); + ok((yield testActor.assertHighlightedNode("#complex-div")), + "The highlighter shows #complex-div. OK."); + + yield doKeyHover({key: "VK_RIGHT", options: {}}); + ok((yield testActor.assertHighlightedNode("#simple-div2")), + "The highlighter shows #simple-div2. OK."); + + info("Previously chosen child is remembered. Passed."); + + info("Stopping the picker"); + yield toolbox.highlighterUtils.stopPicker(); + + function doKeyHover(args) { + info("Key pressed. Waiting for element to be highlighted/hovered"); + let onHighlighterReady = toolbox.once("highlighter-ready"); + let onPickerNodeHovered = inspector.toolbox.once("picker-node-hovered"); + testActor.synthesizeKey(args); + return promise.all([onHighlighterReady, onPickerNodeHovered]); + } + + function moveMouseOver(selector) { + info("Waiting for element " + selector + " to be highlighted"); + let onHighlighterReady = toolbox.once("highlighter-ready"); + let onPickerNodeHovered = inspector.toolbox.once("picker-node-hovered"); + testActor.synthesizeMouse({ + options: {type: "mousemove"}, + center: true, + selector: selector + }); + return promise.all([onHighlighterReady, onPickerNodeHovered]); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_03.js b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_03.js new file mode 100644 index 000000000..99a2316bb --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_03.js @@ -0,0 +1,71 @@ +/* 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 keybindings for Picker work alright + +const IS_OSX = Services.appinfo.OS === "Darwin"; +const TEST_URL = URL_ROOT + "doc_inspector_highlighter_dom.html"; + +add_task(function* () { + let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL); + + yield startPicker(toolbox); + yield moveMouseOver("#another"); + + info("Testing enter/return key as pick-node command"); + yield doKeyPick({key: "VK_RETURN", options: {}}); + is(inspector.selection.nodeFront.id, "another", + "The #another node was selected. Passed."); + + info("Testing escape key as cancel-picker command"); + yield startPicker(toolbox); + yield moveMouseOver("#ahoy"); + yield doKeyStop({key: "VK_ESCAPE", options: {}}); + is(inspector.selection.nodeFront.id, "another", + "The #another DIV is still selected. Passed."); + + info("Testing Ctrl+Shift+C shortcut as cancel-picker command"); + yield startPicker(toolbox); + yield moveMouseOver("#ahoy"); + let shortcutOpts = {key: "VK_C", options: {}}; + if (IS_OSX) { + shortcutOpts.options.metaKey = true; + shortcutOpts.options.altKey = true; + } else { + shortcutOpts.options.ctrlKey = true; + shortcutOpts.options.shiftKey = true; + } + yield doKeyStop(shortcutOpts); + is(inspector.selection.nodeFront.id, "another", + "The #another DIV is still selected. Passed."); + + function doKeyPick(args) { + info("Key pressed. Waiting for element to be picked"); + testActor.synthesizeKey(args); + return promise.all([ + inspector.selection.once("new-node-front"), + inspector.once("inspector-updated") + ]); + } + + function doKeyStop(args) { + info("Key pressed. Waiting for picker to be canceled"); + testActor.synthesizeKey(args); + return inspector.toolbox.once("picker-stopped"); + } + + function moveMouseOver(selector) { + info("Waiting for element " + selector + " to be highlighted"); + let onHighlighterReady = toolbox.once("highlighter-ready"); + let onPickerNodeHovered = inspector.toolbox.once("picker-node-hovered"); + testActor.synthesizeMouse({ + options: {type: "mousemove"}, + center: true, + selector: selector + }); + return promise.all([onHighlighterReady, onPickerNodeHovered]); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_04.js b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_04.js new file mode 100644 index 000000000..f53ca8ee6 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_04.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"; + +// Tests that pressing ESC twice while in picker mode first stops the picker and +// then opens the split-console (see bug 988278). + +const TEST_URL = "data:text/html;charset=utf8,<div></div>"; + +add_task(function* () { + let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL); + + yield startPicker(toolbox); + + info("Start using the picker by hovering over nodes"); + let onHover = toolbox.once("picker-node-hovered"); + testActor.synthesizeMouse({ + options: {type: "mousemove"}, + center: true, + selector: "div" + }); + yield onHover; + + info("Press escape and wait for the picker to stop"); + let onPickerStopped = toolbox.once("picker-stopped"); + testActor.synthesizeKey({ + key: "VK_ESCAPE", + options: {} + }); + yield onPickerStopped; + + info("Press escape again and wait for the split console to open"); + let onSplitConsole = toolbox.once("split-console"); + let onConsoleReady = toolbox.once("webconsole-ready"); + // The escape key is synthesized in the main process, which is where the focus + // should be after the picker was stopped. + EventUtils.synthesizeKey("VK_ESCAPE", {}, inspector.panelWin); + yield onSplitConsole; + yield onConsoleReady; + ok(toolbox.splitConsole, "The split console is shown."); + + // Hide the split console. + yield toolbox.toggleSplitConsole(); +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-measure_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-measure_01.js new file mode 100644 index 000000000..b7f20e2fb --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-measure_01.js @@ -0,0 +1,88 @@ +/* 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_URL = `data:text/html;charset=utf-8, + <div style=' + position:absolute; + left: 0; + top: 0; + width: 40000px; + height: 8000px'> + </div>`; + +const PREFIX = "measuring-tool-highlighter-"; +const HIGHLIGHTER_TYPE = "MeasuringToolHighlighter"; + +const X = 32; +const Y = 20; + +add_task(function* () { + let helper = yield openInspectorForURL(TEST_URL) + .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE)); + + let { finalize } = helper; + + helper.prefix = PREFIX; + + yield isHiddenByDefault(helper); + yield areLabelsHiddenByDefaultWhenShows(helper); + yield areLabelsProperlyDisplayedWhenMouseMoved(helper); + + yield finalize(); +}); + +function* isHiddenByDefault({isElementHidden}) { + info("Checking the highlighter is hidden by default"); + + let hidden = yield isElementHidden("elements"); + ok(hidden, "highlighter's root is hidden by default"); + + hidden = yield isElementHidden("label-size"); + ok(hidden, "highlighter's label size is hidden by default"); + + hidden = yield isElementHidden("label-position"); + ok(hidden, "highlighter's label position is hidden by default"); +} + +function* areLabelsHiddenByDefaultWhenShows({isElementHidden, show}) { + info("Checking the highlighter is displayed when asked"); + + yield show(); + + let hidden = yield isElementHidden("elements"); + is(hidden, false, "highlighter is visible after show"); + + hidden = yield isElementHidden("label-size"); + ok(hidden, "label's size still hidden"); + + hidden = yield isElementHidden("label-position"); + ok(hidden, "label's position still hidden"); +} + +function* areLabelsProperlyDisplayedWhenMouseMoved({isElementHidden, + synthesizeMouse, getElementTextContent}) { + info("Checking labels are properly displayed when mouse moved"); + + yield synthesizeMouse({ + selector: ":root", + options: {type: "mousemove"}, + x: X, + y: Y + }); + + let hidden = yield isElementHidden("label-position"); + is(hidden, false, "label's position is displayed after the mouse is moved"); + + hidden = yield isElementHidden("label-size"); + ok(hidden, "label's size still hidden"); + + let text = yield getElementTextContent("label-position"); + + let [x, y] = text.replace(/ /g, "").split(/\n/); + + is(+x, X, "label's position shows the proper X coord"); + is(+y, Y, "label's position shows the proper Y coord"); +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-measure_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-measure_02.js new file mode 100644 index 000000000..424cc183a --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-measure_02.js @@ -0,0 +1,130 @@ +/* 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_URL = `data:text/html;charset=utf-8, + <div style=' + position:absolute; + left: 0; + top: 0; + width: 40000px; + height: 8000px'> + </div>`; + +const PREFIX = "measuring-tool-highlighter-"; +const HIGHLIGHTER_TYPE = "MeasuringToolHighlighter"; + +const SIDES = ["top", "right", "bottom", "left"]; + +const X = 32; +const Y = 20; +const WIDTH = 160; +const HEIGHT = 100; +const HYPOTENUSE = Math.hypot(WIDTH, HEIGHT).toFixed(2); + +add_task(function* () { + let helper = yield openInspectorForURL(TEST_URL) + .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE)); + + let { show, finalize } = helper; + + helper.prefix = PREFIX; + + yield show(); + + yield hasNoLabelsWhenStarts(helper); + yield hasSizeLabelWhenMoved(helper); + yield hasCorrectSizeLabelValue(helper); + yield hasSizeLabelAndGuidesWhenStops(helper); + yield hasCorrectSizeLabelValue(helper); + + yield finalize(); +}); + +function* hasNoLabelsWhenStarts({isElementHidden, synthesizeMouse}) { + info("Checking highlighter has no labels when we start to select"); + + yield synthesizeMouse({ + selector: ":root", + options: {type: "mousedown"}, + x: X, + y: Y + }); + + let hidden = yield isElementHidden("label-size"); + ok(hidden, "label's size still hidden"); + + hidden = yield isElementHidden("label-position"); + ok(hidden, "label's position still hidden"); + + info("Checking highlighter has no guides when we start to select"); + + let guidesHidden = true; + for (let side of SIDES) { + guidesHidden = guidesHidden && (yield isElementHidden("guide-" + side)); + } + + ok(guidesHidden, "guides are hidden during dragging"); +} + +function* hasSizeLabelWhenMoved({isElementHidden, synthesizeMouse}) { + info("Checking highlighter has size label when we select the area"); + + yield synthesizeMouse({ + selector: ":root", + options: {type: "mousemove"}, + x: X + WIDTH, + y: Y + HEIGHT + }); + + let hidden = yield isElementHidden("label-size"); + is(hidden, false, "label's size is visible during selection"); + + hidden = yield isElementHidden("label-position"); + ok(hidden, "label's position still hidden"); + + info("Checking highlighter has no guides when we select the area"); + + let guidesHidden = true; + for (let side of SIDES) { + guidesHidden = guidesHidden && (yield isElementHidden("guide-" + side)); + } + + ok(guidesHidden, "guides are hidden during selection"); +} + +function* hasSizeLabelAndGuidesWhenStops({isElementHidden, synthesizeMouse}) { + info("Checking highlighter has size label and guides when we stop"); + + yield synthesizeMouse({ + selector: ":root", + options: {type: "mouseup"}, + x: X + WIDTH, + y: Y + HEIGHT + }); + + let hidden = yield isElementHidden("label-size"); + is(hidden, false, "label's size is visible when the selection is done"); + + hidden = yield isElementHidden("label-position"); + ok(hidden, "label's position still hidden"); + + let guidesVisible = true; + for (let side of SIDES) { + guidesVisible = guidesVisible && !(yield isElementHidden("guide-" + side)); + } + + ok(guidesVisible, "guides are visible when the selection is done"); +} + +function* hasCorrectSizeLabelValue({getElementTextContent}) { + let text = yield getElementTextContent("label-size"); + + let [width, height, hypot] = text.match(/\d.*px/g); + + is(parseFloat(width), WIDTH, "width on label's size is correct"); + is(parseFloat(height), HEIGHT, "height on label's size is correct"); + is(parseFloat(hypot), HYPOTENUSE, "hypotenuse on label's size is correct"); +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-options.js b/devtools/client/inspector/test/browser_inspector_highlighter-options.js new file mode 100644 index 000000000..65a6ec4b0 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-options.js @@ -0,0 +1,204 @@ +/* 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 = ` + <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* (testActor) { + let hidden = yield testActor.getHighlighterNodeAttribute( + "box-model-infobar-container", "hidden"); + ok(!hidden, "Node infobar is visible"); + + hidden = yield testActor.getHighlighterNodeAttribute( + "box-model-elements", "hidden"); + ok(!hidden, "SVG container is visible"); + + for (let side of ["top", "right", "bottom", "left"]) { + hidden = yield testActor.getHighlighterNodeAttribute( + "box-model-guide-" + side, "hidden"); + ok(!hidden, side + " guide is visible"); + } + } + }, + { + desc: "All regions should be shown by default", + options: {}, + checkHighlighter: function* (testActor) { + for (let region of ["margin", "border", "padding", "content"]) { + let {d} = yield testActor.getHighlighterRegionPath(region); + ok(d, "Region " + region + " has set coordinates"); + } + } + }, + { + desc: "Guides can be hidden", + options: {hideGuides: true}, + checkHighlighter: function* (testActor) { + for (let side of ["top", "right", "bottom", "left"]) { + let hidden = yield testActor.getHighlighterNodeAttribute( + "box-model-guide-" + side, "hidden"); + is(hidden, "true", side + " guide has been hidden"); + } + } + }, + { + desc: "Infobar can be hidden", + options: {hideInfoBar: true}, + checkHighlighter: function* (testActor) { + let hidden = yield testActor.getHighlighterNodeAttribute( + "box-model-infobar-container", "hidden"); + is(hidden, "true", "infobar has been hidden"); + } + }, + { + desc: "One region only can be shown (1)", + options: {showOnly: "content"}, + checkHighlighter: function* (testActor) { + let {d} = yield testActor.getHighlighterRegionPath("margin"); + ok(!d, "margin region is hidden"); + + ({d} = yield testActor.getHighlighterRegionPath("border")); + ok(!d, "border region is hidden"); + + ({d} = yield testActor.getHighlighterRegionPath("padding")); + ok(!d, "padding region is hidden"); + + ({d} = yield testActor.getHighlighterRegionPath("content")); + ok(d, "content region is shown"); + } + }, + { + desc: "One region only can be shown (2)", + options: {showOnly: "margin"}, + checkHighlighter: function* (testActor) { + let {d} = yield testActor.getHighlighterRegionPath("margin"); + ok(d, "margin region is shown"); + + ({d} = yield testActor.getHighlighterRegionPath("border")); + ok(!d, "border region is hidden"); + + ({d} = yield testActor.getHighlighterRegionPath("padding")); + ok(!d, "padding region is hidden"); + + ({d} = yield testActor.getHighlighterRegionPath("content")); + ok(!d, "content region is hidden"); + } + }, + { + desc: "Guides can be drawn around a given region (1)", + options: {region: "padding"}, + checkHighlighter: function* (testActor) { + let topY1 = yield testActor.getHighlighterNodeAttribute( + "box-model-guide-top", "y1"); + let rightX1 = yield testActor.getHighlighterNodeAttribute( + "box-model-guide-right", "x1"); + let bottomY1 = yield testActor.getHighlighterNodeAttribute( + "box-model-guide-bottom", "y1"); + let leftX1 = yield testActor.getHighlighterNodeAttribute( + "box-model-guide-left", "x1"); + + let {points} = yield testActor.getHighlighterRegionPath("padding"); + 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* (testActor) { + let topY1 = yield testActor.getHighlighterNodeAttribute( + "box-model-guide-top", "y1"); + let rightX1 = yield testActor.getHighlighterNodeAttribute( + "box-model-guide-right", "x1"); + let bottomY1 = yield testActor.getHighlighterNodeAttribute( + "box-model-guide-bottom", "y1"); + let leftX1 = yield testActor.getHighlighterNodeAttribute( + "box-model-guide-left", "x1"); + + let {points} = yield testActor.getHighlighterRegionPath("margin"); + 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: "When showOnly is used, other regions can be faded", + options: {showOnly: "margin", onlyRegionArea: true}, + checkHighlighter: function* (testActor) { + for (let region of ["margin", "border", "padding", "content"]) { + let {d} = yield testActor.getHighlighterRegionPath(region); + ok(d, "Region " + region + " is shown (it has a d attribute)"); + + let faded = yield testActor.getHighlighterNodeAttribute( + "box-model-" + region, "faded"); + if (region === "margin") { + ok(!faded, "The margin region is not faded"); + } else { + is(faded, "true", "Region " + region + " is faded"); + } + } + } + }, + { + desc: "When showOnly is used, other regions can be faded (2)", + options: {showOnly: "padding", onlyRegionArea: true}, + checkHighlighter: function* (testActor) { + for (let region of ["margin", "border", "padding", "content"]) { + let {d} = yield testActor.getHighlighterRegionPath(region); + ok(d, "Region " + region + " is shown (it has a d attribute)"); + + let faded = yield testActor.getHighlighterNodeAttribute( + "box-model-" + region, "faded"); + if (region === "padding") { + ok(!faded, "The padding region is not faded"); + } else { + is(faded, "true", "Region " + region + " is faded"); + } + } + } + } +]; + +add_task(function* () { + let {inspector, testActor} = yield openInspectorForURL( + "data:text/html;charset=utf-8," + encodeURI(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 inspector.highlighter.showBoxModel(divFront, options); + + yield checkHighlighter(testActor); + + info("Hide the box-model highlighter"); + yield inspector.highlighter.hideBoxModel(); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-preview.js b/devtools/client/inspector/test/browser_inspector_highlighter-preview.js new file mode 100644 index 000000000..5f525786a --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-preview.js @@ -0,0 +1,56 @@ +/* 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 and picker mode is not stopped after +// a shift-click (preview) + +const TEST_URI = `data:text/html;charset=utf-8, + <p id="one">one</p><p id="two">two</p><p id="three">three</p>`; + +add_task(function* () { + let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URI); + + let body = yield getNodeFront("body", inspector); + is(inspector.selection.nodeFront, body, "By default the body node is selected"); + + info("Start the element picker"); + yield startPicker(toolbox); + + info("Shift-clicking element #one should select it but keep the picker ON"); + yield clickElement("#one", testActor, inspector, true); + yield checkElementSelected("#one", inspector); + checkPickerMode(toolbox, true); + + info("Shift-clicking element #two should select it but keep the picker ON"); + yield clickElement("#two", testActor, inspector, true); + yield checkElementSelected("#two", inspector); + checkPickerMode(toolbox, true); + + info("Clicking element #three should select it and turn the picker OFF"); + yield clickElement("#three", testActor, inspector, false); + yield checkElementSelected("#three", inspector); + checkPickerMode(toolbox, false); +}); + +function* clickElement(selector, testActor, inspector, isShift) { + let onSelectionChanged = inspector.once("inspector-updated"); + yield testActor.synthesizeMouse({ + selector: selector, + center: true, + options: { shiftKey: isShift } + }); + yield onSelectionChanged; +} + +function* checkElementSelected(selector, inspector) { + let el = yield getNodeFront(selector, inspector); + is(inspector.selection.nodeFront, el, `The element ${selector} is now selected`); +} + +function checkPickerMode(toolbox, isOn) { + let pickerButton = toolbox.doc.querySelector("#command-button-pick"); + is(pickerButton.hasAttribute("checked"), isOn, "The picker mode is correct"); +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-rect_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-rect_01.js new file mode 100644 index 000000000..9645e25d9 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-rect_01.js @@ -0,0 +1,121 @@ +/* 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, testActor} = 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 testActor.getHighlighterNodeAttribute( + "highlighted-rect", "hidden", highlighter); + 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 testActor.getHighlighterNodeAttribute( + "highlighted-rect", "hidden", highlighter); + 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 testActor.getHighlighterNodeAttribute( + "highlighted-rect", "hidden", highlighter); + 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 testActor.getHighlighterNodeAttribute( + "highlighted-rect", "hidden", highlighter); + 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 testActor.getHighlighterNodeAttribute( + "highlighted-rect", "hidden", highlighter); + 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 testActor.getHighlighterNodeAttribute( + "highlighted-rect", "hidden", highlighter); + 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 testActor.getHighlighterNodeAttribute( + "highlighted-rect", "hidden", highlighter); + ok(!hidden, "The highlighter is displayed"); + let style = yield testActor.getHighlighterNodeAttribute( + "highlighted-rect", "style", highlighter); + 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 testActor.getHighlighterNodeAttribute( + "highlighted-rect", "hidden", highlighter); + ok(!hidden, "The highlighter is displayed when x=0 and y=0"); + style = yield testActor.getHighlighterNodeAttribute( + "highlighted-rect", "style", highlighter); + 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 testActor.getHighlighterNodeAttribute( + "highlighted-rect", "hidden", highlighter); + 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 testActor.getHighlighterNodeAttribute( + "highlighted-rect", "hidden", highlighter); + ok(!hidden, "The highlighter is displayed"); + style = yield testActor.getHighlighterNodeAttribute( + "highlighted-rect", "style", highlighter); + 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/devtools/client/inspector/test/browser_inspector_highlighter-rect_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-rect_02.js new file mode 100644 index 000000000..716a5deda --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-rect_02.js @@ -0,0 +1,37 @@ +/* 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 = URL_ROOT + "doc_inspector_highlighter_rect.html"; + +add_task(function* () { + let {inspector, testActor} = 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 testActor.getHighlighterNodeAttribute("highlighted-rect", + "style", highlighter); + + // 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/devtools/client/inspector/test/browser_inspector_highlighter-rulers_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-rulers_01.js new file mode 100644 index 000000000..f5deb0222 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-rulers_01.js @@ -0,0 +1,76 @@ +/* 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 geometry highlighter elements. + +const TEST_URL = "data:text/html;charset=utf-8," + + "<div style='position:absolute;left: 0; top: 0; " + + "width: 40000px; height: 8000px'></div>"; + +const ID = "rulers-highlighter-"; + +// Maximum size, in pixel, for the horizontal ruler and vertical ruler +// used by RulersHighlighter +const RULERS_MAX_X_AXIS = 10000; +const RULERS_MAX_Y_AXIS = 15000; +// Number of steps after we add a text in RulersHighliter; +// currently the unit is in pixel. +const RULERS_TEXT_STEP = 100; + +add_task(function* () { + let { inspector, testActor } = yield openInspectorForURL(TEST_URL); + let front = inspector.inspector; + + let highlighter = yield front.getHighlighterByType("RulersHighlighter"); + + yield isHiddenByDefault(highlighter, inspector, testActor); + yield hasRightLabelsContent(highlighter, inspector, testActor); + + yield highlighter.finalize(); +}); + +function* isHiddenByDefault(highlighterFront, inspector, testActor) { + info("Checking the highlighter is hidden by default"); + + let hidden = yield testActor.getHighlighterNodeAttribute( + ID + "elements", "hidden", highlighterFront); + + is(hidden, "true", "highlighter is hidden by default"); + + info("Checking the highlighter is displayed when asked"); + // the rulers doesn't need any node, but as highligher it seems mandatory + // ones, so the body is given + let body = yield getNodeFront("body", inspector); + yield highlighterFront.show(body); + + hidden = yield testActor.getHighlighterNodeAttribute( + ID + "elements", "hidden", highlighterFront); + + isnot(hidden, "true", "highlighter is visible after show"); +} + +function* hasRightLabelsContent(highlighterFront, inspector, testActor) { + info("Checking the rulers have the proper text, based on rulers' size"); + + let contentX = yield testActor.getHighlighterNodeTextContent( + `${ID}x-axis-text`, highlighterFront); + let contentY = yield testActor.getHighlighterNodeTextContent( + `${ID}y-axis-text`, highlighterFront); + + let expectedX = ""; + for (let i = RULERS_TEXT_STEP; i < RULERS_MAX_X_AXIS; i += RULERS_TEXT_STEP) { + expectedX += i; + } + + is(contentX, expectedX, "x axis text content is correct"); + + let expectedY = ""; + for (let i = RULERS_TEXT_STEP; i < RULERS_MAX_Y_AXIS; i += RULERS_TEXT_STEP) { + expectedY += i; + } + + is(contentY, expectedY, "y axis text content is correct"); +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-rulers_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-rulers_02.js new file mode 100644 index 000000000..fac1c801e --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-rulers_02.js @@ -0,0 +1,103 @@ +/* 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 geometry highlighter elements. + +const TEST_URL = "data:text/html;charset=utf-8," + + "<div style='position:absolute;left: 0; top: 0; " + + "width: 40000px; height: 8000px'></div>"; + +const ID = "rulers-highlighter-"; + +add_task(function* () { + let { inspector, testActor } = yield openInspectorForURL(TEST_URL); + let front = inspector.inspector; + + let highlighter = yield front.getHighlighterByType("RulersHighlighter"); + + // the rulers doesn't need any node, but as highligher it seems mandatory + // ones, so the body is given + let body = yield getNodeFront("body", inspector); + yield highlighter.show(body); + + yield isUpdatedAfterScroll(highlighter, inspector, testActor); + + yield highlighter.finalize(); +}); + +function* isUpdatedAfterScroll(highlighterFront, inspector, testActor) { + info("Check the rulers' position by default"); + + let xAxisRulerTransform = yield testActor.getHighlighterNodeAttribute( + `${ID}x-axis-ruler`, "transform", highlighterFront); + let xAxisTextTransform = yield testActor.getHighlighterNodeAttribute( + `${ID}x-axis-text`, "transform", highlighterFront); + let yAxisRulerTransform = yield testActor.getHighlighterNodeAttribute( + `${ID}y-axis-ruler`, "transform", highlighterFront); + let yAxisTextTransform = yield testActor.getHighlighterNodeAttribute( + `${ID}y-axis-text`, "transform", highlighterFront); + + is(xAxisRulerTransform, null, "x axis ruler is positioned properly"); + is(xAxisTextTransform, null, "x axis text are positioned properly"); + is(yAxisRulerTransform, null, "y axis ruler is positioned properly"); + is(yAxisTextTransform, null, "y axis text are positioned properly"); + + info("Ask the content window to scroll to specific coords"); + + let x = 200, y = 300; + + let data = yield testActor.scrollWindow(x, y); + + is(data.x, x, "window scrolled properly horizontally"); + is(data.y, y, "window scrolled properly vertically"); + + info("Check the rulers are properly positioned after the scrolling"); + + xAxisRulerTransform = yield testActor.getHighlighterNodeAttribute( + `${ID}x-axis-ruler`, "transform", highlighterFront); + xAxisTextTransform = yield testActor.getHighlighterNodeAttribute( + `${ID}x-axis-text`, "transform", highlighterFront); + yAxisRulerTransform = yield testActor.getHighlighterNodeAttribute( + `${ID}y-axis-ruler`, "transform", highlighterFront); + yAxisTextTransform = yield testActor.getHighlighterNodeAttribute( + `${ID}y-axis-text`, "transform", highlighterFront); + + is(xAxisRulerTransform, `translate(-${x})`, + "x axis ruler is positioned properly"); + is(xAxisTextTransform, `translate(-${x})`, + "x axis text are positioned properly"); + is(yAxisRulerTransform, `translate(0, -${y})`, + "y axis ruler is positioned properly"); + is(yAxisTextTransform, `translate(0, -${y})`, + "y axis text are positioned properly"); + + info("Ask the content window to scroll relative to the current position"); + + data = yield testActor.scrollWindow(-50, -60, true); + + is(data.x, x - 50, "window scrolled properly horizontally"); + is(data.y, y - 60, "window scrolled properly vertically"); + + info("Check the rulers are properly positioned after the relative scrolling"); + + xAxisRulerTransform = yield testActor.getHighlighterNodeAttribute( + `${ID}x-axis-ruler`, "transform", highlighterFront); + xAxisTextTransform = yield testActor.getHighlighterNodeAttribute( + `${ID}x-axis-text`, "transform", highlighterFront); + yAxisRulerTransform = yield testActor.getHighlighterNodeAttribute( + `${ID}y-axis-ruler`, "transform", highlighterFront); + yAxisTextTransform = yield testActor.getHighlighterNodeAttribute( + `${ID}y-axis-text`, "transform", highlighterFront); + + is(xAxisRulerTransform, `translate(-${x - 50})`, + "x axis ruler is positioned properly"); + is(xAxisTextTransform, `translate(-${x - 50})`, + "x axis text are positioned properly"); + is(yAxisRulerTransform, `translate(0, -${y - 60})`, + "y axis ruler is positioned properly"); + is(yAxisTextTransform, `translate(0, -${y - 60})`, + "y axis text are positioned properly"); +} diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-selector_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-selector_01.js new file mode 100644 index 000000000..4edfe6051 --- /dev/null +++ b/devtools/client/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 +}]; + +requestLongerTimeout(5); + +add_task(function* () { + let {inspector, testActor} = 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 nb = yield testActor.getSelectorHighlighterBoxNb(highlighter.actorID); + 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/devtools/client/inspector/test/browser_inspector_highlighter-selector_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-selector_02.js new file mode 100644 index 000000000..85fcaeb1c --- /dev/null +++ b/devtools/client/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 +}]; + +requestLongerTimeout(5); + +add_task(function* () { + let {inspector, testActor} = 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 nb = yield testActor.getSelectorHighlighterBoxNb(highlighter.actorID); + 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/devtools/client/inspector/test/browser_inspector_highlighter-xbl.js b/devtools/client/inspector/test/browser_inspector_highlighter-xbl.js new file mode 100644 index 000000000..2421fd3f3 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-xbl.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the picker works correctly with XBL anonymous nodes + +const TEST_URL = URL_ROOT + "doc_inspector_highlighter_xbl.xul"; + +add_task(function* () { + let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL); + + yield startPicker(toolbox); + + info("Selecting the scale"); + yield moveMouseOver("#scale"); + yield doKeyPick({key: "VK_RETURN", options: {}}); + is(inspector.selection.nodeFront.className, "scale-slider", + "The .scale-slider inside the scale was selected"); + + function doKeyPick(msg) { + info("Key pressed. Waiting for element to be picked"); + testActor.synthesizeKey(msg); + return promise.all([ + inspector.selection.once("new-node-front"), + inspector.once("inspector-updated") + ]); + } + + function moveMouseOver(selector) { + info("Waiting for element " + selector + " to be highlighted"); + testActor.synthesizeMouse({ + options: {type: "mousemove"}, + center: true, + selector: selector + }); + return inspector.toolbox.once("picker-node-hovered"); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-zoom.js b/devtools/client/inspector/test/browser_inspector_highlighter-zoom.js new file mode 100644 index 000000000..1919975ef --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_highlighter-zoom.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 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, testActor} = yield openInspectorForURL(TEST_URL); + + info("Highlighting the test node"); + + yield hoverElement("div", inspector); + let isVisible = yield testActor.isHighlighting(); + 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"); + + yield testActor.zoomPageTo(level); + isVisible = yield testActor.isHighlighting(); + ok(isVisible, "The highlighter is still visible at zoom level " + level); + + yield testActor.isNodeCorrectlyHighlighted("div", is); + + info("Check that the highlighter root wrapper node was scaled down"); + + let style = yield getRootNodeStyle(testActor); + 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(testActor) { + let value = yield testActor.getHighlighterNodeAttribute( + "box-model-root", "style"); + return value; +} diff --git a/devtools/client/inspector/test/browser_inspector_iframe-navigation.js b/devtools/client/inspector/test/browser_inspector_iframe-navigation.js new file mode 100644 index 000000000..df638b5cb --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_iframe-navigation.js @@ -0,0 +1,43 @@ +/* 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 { toolbox, testActor } = yield openInspectorForURL(TEST_URI); + + info("Starting element picker."); + yield startPicker(toolbox); + + info("Waiting for highlighter to activate."); + let highlighterShowing = toolbox.once("highlighter-ready"); + testActor.synthesizeMouse({ + selector: "body", + options: {type: "mousemove"}, + x: 1, + y: 1 + }); + yield highlighterShowing; + + let isVisible = yield testActor.isHighlighting(); + ok(isVisible, "Inspector is highlighting."); + + yield testActor.reloadFrame("iframe"); + info("Frame reloaded. Reloading again."); + + yield testActor.reloadFrame("iframe"); + info("Frame reloaded twice."); + + isVisible = yield testActor.isHighlighting(); + ok(isVisible, "Inspector is highlighting after iframe nav."); + + info("Stopping element picker."); + yield toolbox.highlighterUtils.stopPicker(); +}); diff --git a/devtools/client/inspector/test/browser_inspector_infobar_01.js b/devtools/client/inspector/test/browser_inspector_infobar_01.js new file mode 100644 index 000000000..441cd9e48 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_infobar_01.js @@ -0,0 +1,89 @@ +/* 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 = URL_ROOT + "doc_inspector_infobar_01.html"; + +add_task(function* () { + let {inspector, testActor} = yield openInspectorForURL(TEST_URI); + + 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 + }, + { + selector: "clipPath", + position: "bottom", + tag: "clipPath", + id: "clip", + classes: "" + // No dims as element is not displayed and we just want to test tag name + }, + ]; + + for (let currTest of testData) { + yield testPosition(currTest, inspector, testActor); + } +}); + +function* testPosition(test, inspector, testActor) { + info("Testing " + test.selector); + + yield selectAndHighlightNode(test.selector, inspector); + + let position = yield testActor.getHighlighterNodeAttribute( + "box-model-infobar-container", "position"); + is(position, test.position, "Node " + test.selector + ": position matches"); + + let tag = yield testActor.getHighlighterNodeTextContent( + "box-model-infobar-tagname"); + is(tag, test.tag, "node " + test.selector + ": tagName matches."); + + if (test.id) { + let id = yield testActor.getHighlighterNodeTextContent( + "box-model-infobar-id"); + is(id, "#" + test.id, "node " + test.selector + ": id matches."); + } + + let classes = yield testActor.getHighlighterNodeTextContent( + "box-model-infobar-classes"); + is(classes, test.classes, "node " + test.selector + ": classes match."); + + if (test.dims) { + let dims = yield testActor.getHighlighterNodeTextContent( + "box-model-infobar-dimensions"); + is(dims, test.dims, "node " + test.selector + ": dims match."); + } +} diff --git a/devtools/client/inspector/test/browser_inspector_infobar_02.js b/devtools/client/inspector/test/browser_inspector_infobar_02.js new file mode 100644 index 000000000..1b5bd5edf --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_infobar_02.js @@ -0,0 +1,50 @@ +/* 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 text content of the highlighter info bar for namespaced elements. + +const XHTML = ` + <!DOCTYPE html> + <html xmlns="http://www.w3.org/1999/xhtml" + xmlns:svg="http://www.w3.org/2000/svg"> + <body> + <svg:svg width="100" height="100"> + <svg:circle cx="0" cy="0" r="5"></svg:circle> + </svg:svg> + </body> + </html> +`; + +const TEST_URI = "data:application/xhtml+xml;charset=utf-8," + encodeURI(XHTML); + +add_task(function* () { + let {inspector, testActor} = yield openInspectorForURL(TEST_URI); + + let testData = [ + { + selector: "svg", + tag: "svg:svg" + }, + { + selector: "circle", + tag: "svg:circle" + }, + ]; + + for (let currTest of testData) { + yield testNode(currTest, inspector, testActor); + } +}); + +function* testNode(test, inspector, testActor) { + info("Testing " + test.selector); + + yield selectAndHighlightNode(test.selector, inspector); + + let tag = yield testActor.getHighlighterNodeTextContent( + "box-model-infobar-tagname"); + is(tag, test.tag, "node " + test.selector + ": tagName matches."); +} diff --git a/devtools/client/inspector/test/browser_inspector_infobar_03.js b/devtools/client/inspector/test/browser_inspector_infobar_03.js new file mode 100644 index 000000000..023d5bb38 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_infobar_03.js @@ -0,0 +1,41 @@ +/* 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 1102269 - Make sure info-bar never gets outside of visible area after scrolling + +const TEST_URI = URL_ROOT + "doc_inspector_infobar_03.html"; + +add_task(function* () { + let {inspector, testActor} = yield openInspectorForURL(TEST_URI); + + let testData = { + selector: "body", + position: "overlap", + style: "top:0px", + }; + + yield testPositionAndStyle(testData, inspector, testActor); +}); + +function* testPositionAndStyle(test, inspector, testActor) { + info("Testing " + test.selector); + + yield selectAndHighlightNode(test.selector, inspector); + + let style = yield testActor.getHighlighterNodeAttribute( + "box-model-infobar-container", "style"); + + is(style.split(";")[0], test.style, + "Infobar shows on top of the page when page isn't scrolled"); + + yield testActor.scrollWindow(0, 500); + + style = yield testActor.getHighlighterNodeAttribute( + "box-model-infobar-container", "style"); + + is(style.split(";")[0], test.style, + "Infobar shows on top of the page even if the page is scrolled"); +} diff --git a/devtools/client/inspector/test/browser_inspector_infobar_textnode.js b/devtools/client/inspector/test/browser_inspector_infobar_textnode.js new file mode 100644 index 000000000..c9315eb9b --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_infobar_textnode.js @@ -0,0 +1,46 @@ +/* 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 1309212 - Make sure info-bar is displayed with dimensions for text nodes. + +const TEST_URI = URL_ROOT + "doc_inspector_infobar_textnode.html"; + +add_task(function* () { + let { inspector, testActor } = yield openInspectorForURL(TEST_URI); + let { walker } = inspector; + + info("Retrieve the children of #textnode-container"); + let div = yield walker.querySelector(walker.rootNode, "#textnode-container"); + let { nodes } = yield inspector.walker.children(div); + + // Children 0, 2 and 4 are text nodes, for which we expect to see an infobar containing + // dimensions. + + // Regular text node. + info("Select the first text node"); + yield selectNode(nodes[0], inspector, "test-highlight"); + yield checkTextNodeInfoBar(testActor); + + // Whitespace-only text node. + info("Select the second text node"); + yield selectNode(nodes[2], inspector, "test-highlight"); + yield checkTextNodeInfoBar(testActor); + + // Regular text node. + info("Select the third text node"); + yield selectNode(nodes[4], inspector, "test-highlight"); + yield checkTextNodeInfoBar(testActor); +}); + +function* checkTextNodeInfoBar(testActor) { + let tag = yield testActor.getHighlighterNodeTextContent( + "box-model-infobar-tagname"); + is(tag, "#text", "node display name is #text"); + let dims = yield testActor.getHighlighterNodeTextContent( + "box-model-infobar-dimensions"); + // Do not assert dimensions as they might be platform specific. + ok(!!dims, "node has dims"); +} diff --git a/devtools/client/inspector/test/browser_inspector_initialization.js b/devtools/client/inspector/test/browser_inspector_initialization.js new file mode 100644 index 000000000..55db060f3 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_initialization.js @@ -0,0 +1,112 @@ +/* -*- 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/. */ +/* globals getTestActorWithoutToolbox */ +"use strict"; + +// Tests for different ways to initialize the inspector. + +const HTML = ` + <div id="first" style="margin: 10em; font-size: 14pt; + font-family: helvetica, sans-serif; color: gray"> + <h1>Some header text</h1> + <p id="salutation" style="font-size: 12pt">hi.</p> + <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> + <p>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> + <p id="closing">end transmission</p> + </div> +`; + +const TEST_URI = "data:text/html;charset=utf-8," + encodeURI(HTML); + +add_task(function* () { + let tab = yield addTab(TEST_URI); + let testActor = yield getTestActorWithoutToolbox(tab); + + yield testToolboxInitialization(testActor, tab); + yield testContextMenuInitialization(testActor); + yield testContextMenuInspectorAlreadyOpen(testActor); +}); + +function* testToolboxInitialization(testActor, 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); + + yield testActor.scrollIntoView("span"); + + 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(testActor) { + info("Opening inspector by clicking on 'Inspect Element' context menu item"); + yield clickOnInspectMenuItem(testActor, "#salutation"); + + info("Checking inspector state."); + yield testMarkupView("#salutation"); + yield testBreadcrumbs("#salutation"); +} + +function* testContextMenuInspectorAlreadyOpen(testActor) { + info("Changing node by clicking on 'Inspect Element' context menu item"); + + let inspector = getActiveInspector(); + ok(inspector, "Inspector is active"); + + yield clickOnInspectMenuItem(testActor, "#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("title"), expectedText, + "Crumb refers to the right node"); +} diff --git a/devtools/client/inspector/test/browser_inspector_inspect-object-element.js b/devtools/client/inspector/test/browser_inspector_inspect-object-element.js new file mode 100644 index 000000000..ca646c506 --- /dev/null +++ b/devtools/client/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); + + yield selectNode("object", inspector); + + ok(true, "Selected <object> without throwing"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_invalidate.js b/devtools/client/inspector/test/browser_inspector_invalidate.js new file mode 100644 index 000000000..040bd1c1c --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_invalidate.js @@ -0,0 +1,35 @@ +/* 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 {inspector, testActor} = yield openInspectorForURL(TEST_URI); + let divFront = yield getNodeFront("div", inspector); + + info("Waiting for highlighter to activate"); + yield inspector.highlighter.showBoxModel(divFront); + + let rect = yield testActor.getSimpleBorderRect(); + is(rect.width, 100, "The highlighter has the right width."); + + info("Changing the test element's size and waiting for the highlighter " + + "to update"); + yield testActor.changeHighlightedNodeWaitForUpdate( + "style", + "width: 200px; height: 100px; background:yellow;" + ); + + rect = yield testActor.getSimpleBorderRect(); + is(rect.width, 200, "The highlighter has the right width after update"); + + info("Waiting for highlighter to hide"); + yield inspector.highlighter.hideBoxModel(); +}); diff --git a/devtools/client/inspector/test/browser_inspector_keyboard-shortcuts-copy-outerhtml.js b/devtools/client/inspector/test/browser_inspector_keyboard-shortcuts-copy-outerhtml.js new file mode 100644 index 000000000..46b0ce5f5 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_keyboard-shortcuts-copy-outerhtml.js @@ -0,0 +1,52 @@ +/* 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 copy outer HTML from the keyboard/copy event + +const TEST_URL = URL_ROOT + "doc_inspector_outerhtml.html"; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URL); + let root = inspector.markup._elt; + + info("Test copy outerHTML for COMMENT node"); + let comment = getElementByType(inspector, Ci.nsIDOMNode.COMMENT_NODE); + yield setSelectionNodeFront(comment, inspector); + yield checkClipboard("<!-- Comment -->", root); + + info("Test copy outerHTML for DOCTYPE node"); + let doctype = getElementByType(inspector, Ci.nsIDOMNode.DOCUMENT_TYPE_NODE); + yield setSelectionNodeFront(doctype, inspector); + yield checkClipboard("<!DOCTYPE html>", root); + + info("Test copy outerHTML for ELEMENT node"); + yield selectAndHighlightNode("div", inspector); + yield checkClipboard("<div><p>Test copy OuterHTML</p></div>", root); +}); + +function* setSelectionNodeFront(node, inspector) { + let updated = inspector.once("inspector-updated"); + inspector.selection.setNodeFront(node); + yield updated; +} + +function* checkClipboard(expectedText, node) { + try { + yield waitForClipboardPromise(() => fireCopyEvent(node), expectedText); + ok(true, "Clipboard successfully filled with : " + expectedText); + } catch (e) { + ok(false, "Clipboard could not be filled with the expected text : " + + expectedText); + } +} + +function getElementByType(inspector, type) { + for (let [node] of inspector.markup._containers) { + if (node.nodeType === type) { + return node; + } + } + return null; +} diff --git a/devtools/client/inspector/test/browser_inspector_keyboard-shortcuts.js b/devtools/client/inspector/test/browser_inspector_keyboard-shortcuts.js new file mode 100644 index 000000000..f03a33fd1 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_keyboard-shortcuts.js @@ -0,0 +1,48 @@ +/* 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><p><strong>Greetings, earthlings!</strong>" + + " I come in peace.</p></body></html>"; + +const TEST_DATA = [ + { key: "VK_LEFT", selectedNode: "p" }, + { key: "VK_LEFT", selectedNode: "body" }, + { key: "VK_LEFT", selectedNode: "html" }, + { key: "VK_RIGHT", selectedNode: "body" }, + { key: "VK_RIGHT", selectedNode: "p" }, + { key: "VK_RIGHT", selectedNode: "strong" }, +]; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URI); + + info("Selecting the deepest element to start with"); + yield selectNode("strong", inspector); + + let nodeFront = yield getNodeFront("strong", inspector); + is(inspector.selection.nodeFront, nodeFront, + "<strong> 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/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js b/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js new file mode 100644 index 000000000..59dbbbcc0 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js @@ -0,0 +1,278 @@ +/* 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 = 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 ACTIVE_ON_DOCTYPE_ITEMS = [ + "node-menu-showdomproperties", + "node-menu-useinconsole" +]; + +const ALL_MENU_ITEMS = [ + "node-menu-edithtml", + "node-menu-copyinner", + "node-menu-copyouter", + "node-menu-copyuniqueselector", + "node-menu-copyimagedatauri", + "node-menu-delete", + "node-menu-pseudo-hover", + "node-menu-pseudo-active", + "node-menu-pseudo-focus", + "node-menu-scrollnodeintoview", + "node-menu-screenshotnode", + "node-menu-add-attribute", + "node-menu-edit-attribute", + "node-menu-remove-attribute" +].concat(PASTE_MENU_ITEMS, ACTIVE_ON_DOCTYPE_ITEMS); + +const INACTIVE_ON_DOCTYPE_ITEMS = + ALL_MENU_ITEMS.filter(item => ACTIVE_ON_DOCTYPE_ITEMS.indexOf(item) === -1); + +/** + * Test cases, each item of this array may define the following properties: + * desc: string that will be logged + * selector: selector of the node to be selected + * disabled: items that should have disabled state + * clipboardData: clipboard content + * clipboardDataType: clipboard content type + * attributeTrigger: attribute that will be used as context menu trigger + */ +const TEST_CASES = [ + { + desc: "doctype node with empty clipboard", + selector: null, + disabled: INACTIVE_ON_DOCTYPE_ITEMS, + }, + { + desc: "doctype node with html on clipboard", + clipboardData: "<p>some text</p>", + clipboardDataType: "html", + selector: null, + disabled: INACTIVE_ON_DOCTYPE_ITEMS, + }, + { + desc: "element node HTML on the clipboard", + clipboardData: "<p>some text</p>", + clipboardDataType: "html", + disabled: [ + "node-menu-copyimagedatauri", + "node-menu-edit-attribute", + "node-menu-remove-attribute" + ], + 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", + "node-menu-edit-attribute", + "node-menu-remove-attribute" + ], + }, + { + 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", + "node-menu-edit-attribute", + "node-menu-remove-attribute" + ] + }, + { + desc: "<img> with HTML on clipboard", + clipboardData: "<p>some text</p>", + clipboardDataType: "html", + selector: "img", + disabled: [ + "node-menu-edit-attribute", + "node-menu-remove-attribute" + ] + }, + { + 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", + "node-menu-screenshotnode", + "node-menu-edit-attribute", + "node-menu-remove-attribute" + ], + }, + { + desc: "<head> with no html on clipboard", + selector: "head", + disabled: PASTE_MENU_ITEMS.concat([ + "node-menu-copyimagedatauri", + "node-menu-screenshotnode", + "node-menu-edit-attribute", + "node-menu-remove-attribute" + ]), + }, + { + desc: "<element> with text on clipboard", + clipboardData: "some text", + clipboardDataType: undefined, + selector: "#paste-area", + disabled: [ + "node-menu-copyimagedatauri", + "node-menu-edit-attribute", + "node-menu-remove-attribute" + ] + }, + { + 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", + "node-menu-edit-attribute", + "node-menu-remove-attribute" + ]), + }, + { + desc: "<element> with empty string on clipboard", + clipboardData: "", + clipboardDataType: undefined, + selector: "#paste-area", + disabled: PASTE_MENU_ITEMS.concat([ + "node-menu-copyimagedatauri", + "node-menu-edit-attribute", + "node-menu-remove-attribute" + ]), + }, + { + 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", + "node-menu-edit-attribute", + "node-menu-remove-attribute" + ]), + }, + { + desc: "<element> that isn't visible on the page, empty clipboard", + selector: "#hiddenElement", + disabled: PASTE_MENU_ITEMS.concat([ + "node-menu-copyimagedatauri", + "node-menu-screenshotnode", + "node-menu-edit-attribute", + "node-menu-remove-attribute" + ]), + }, + { + desc: "<element> nested in another hidden element, empty clipboard", + selector: "#nestedHiddenElement", + disabled: PASTE_MENU_ITEMS.concat([ + "node-menu-copyimagedatauri", + "node-menu-screenshotnode", + "node-menu-edit-attribute", + "node-menu-remove-attribute" + ]), + }, + { + desc: "<element> with context menu triggered on attribute, empty clipboard", + selector: "#attributes", + disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]), + attributeTrigger: "data-edit" + } +]; + +var 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, attributeTrigger } = 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."); + let nodeFrontContainer = getContainerForNodeFront(front, inspector); + let contextMenuTrigger = attributeTrigger + ? nodeFrontContainer.tagLine.querySelector( + `[data-attr="${attributeTrigger}"]`) + : nodeFrontContainer.tagLine; + + let allMenuItems = openContextMenuAndGetAllItems(inspector, { + target: contextMenuTrigger, + }); + + for (let id of ALL_MENU_ITEMS) { + let menuItem = allMenuItems.find(item => item.id === id); + let shouldBeDisabled = disabled.indexOf(id) !== -1; + is(menuItem.disabled, shouldBeDisabled, + `#${id} 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); + } + + 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"); + } +} diff --git a/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js b/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js new file mode 100644 index 000000000..0c96e9bbe --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js @@ -0,0 +1,49 @@ +/* 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 = 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 allMenuItems = openContextMenuAndGetAllItems(inspector); + let item = allMenuItems.find(i => i.id === id); + ok(item, "The popup has a " + desc + " menu item."); + + yield waitForClipboardPromise(() => item.click(), text); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_menu-03-paste-items-svg.js b/devtools/client/inspector/test/browser_inspector_menu-03-paste-items-svg.js new file mode 100644 index 000000000..26ae3ff00 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_menu-03-paste-items-svg.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 HTML can be pasted in SVG elements. + +const TEST_URL = URL_ROOT + "doc_inspector_svg.svg"; +const PASTE_AS_FIRST_CHILD = '<circle xmlns="http://www.w3.org/2000/svg" cx="42" cy="42" r="5"/>'; +const PASTE_AS_LAST_CHILD = '<circle xmlns="http://www.w3.org/2000/svg" cx="42" cy="42" r="15"/>'; + +add_task(function* () { + let clipboard = require("sdk/clipboard"); + + let { inspector, testActor } = yield openInspectorForURL(TEST_URL); + + let refSelector = "svg"; + let oldHTML = yield testActor.getProperty(refSelector, "innerHTML"); + yield selectNode(refSelector, inspector); + let markupTagLine = getContainerForSelector(refSelector, inspector).tagLine; + + yield pasteContent("node-menu-pastefirstchild", PASTE_AS_FIRST_CHILD); + yield pasteContent("node-menu-pastelastchild", PASTE_AS_LAST_CHILD); + + let html = yield testActor.getProperty(refSelector, "innerHTML"); + let expectedHtml = PASTE_AS_FIRST_CHILD + oldHTML + PASTE_AS_LAST_CHILD; + is(html, expectedHtml, "The innerHTML of the SVG node is correct"); + + // Helpers + function* pasteContent(menuId, clipboardData) { + let allMenuItems = openContextMenuAndGetAllItems(inspector, { + target: markupTagLine, + }); + info(`Testing ${menuId} for ${clipboardData}`); + clipboard.set(clipboardData); + + let onMutation = inspector.once("markupmutation"); + allMenuItems.find(item => item.id === menuId).click(); + info("Waiting for mutation to occur"); + yield onMutation; + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_menu-03-paste-items.js b/devtools/client/inspector/test/browser_inspector_menu-03-paste-items.js new file mode 100644 index 000000000..19e5742de --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_menu-03-paste-items.js @@ -0,0 +1,128 @@ +/* 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 = 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", + }, +]; + +var clipboard = require("sdk/clipboard"); +registerCleanupFunction(() => { + clipboard = null; +}); + +add_task(function* () { + let { inspector, testActor } = 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); + + let allMenuItems = openContextMenuAndGetAllItems(inspector, { + target: getContainerForNodeFront(nodeFront, inspector).tagLine, + }); + + let onNodeReselected = inspector.markup.once("reselectedonremoved"); + allMenuItems.find(item => item.id === "node-menu-pasteouterhtml").click(); + + info("Waiting for inspector selection to update"); + yield onNodeReselected; + + let outerHTML = yield testActor.getProperty("body", "outerHTML"); + ok(outerHTML.includes(clipboard.get()), + "Clipboard content was pasted into the node's outer HTML."); + ok(!(yield testActor.hasNode(outerHTMLSelector)), + "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 = () => testActor.getProperty(innerHTMLSelector, + "innerHTML"); + let origInnerHTML = yield getInnerHTML(); + + let nodeFront = yield getNodeFront(innerHTMLSelector, inspector); + yield selectNode(nodeFront, inspector); + + let allMenuItems = openContextMenuAndGetAllItems(inspector, { + target: getContainerForNodeFront(nodeFront, inspector).tagLine, + }); + + let onMutation = inspector.once("markupmutation"); + allMenuItems.find(item => item.id === "node-menu-pasteinnerhtml").click(); + info("Waiting for mutation to occur"); + yield onMutation; + + ok((yield getInnerHTML()) === clipboard.get(), + "Clipboard content was pasted into the node's inner HTML."); + ok((yield testActor.hasNode(innerHTMLSelector)), + "The original node has been preserved."); + yield undoChange(inspector); + ok((yield getInnerHTML()) === origInnerHTML, + "Previous innerHTML has been restored after undo"); + } + + function* testPasteAdjacentHTMLMenu() { + let refSelector = "#paste-area .adjacent .ref"; + let adjacentNodeSelector = "#paste-area .adjacent"; + 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 allMenuItems = openContextMenuAndGetAllItems(inspector, { + target: markupTagLine, + }); + info(`Testing ${menuId} for ${clipboardData}`); + clipboard.set(clipboardData); + + let onMutation = inspector.once("markupmutation"); + allMenuItems.find(item => item.id === menuId).click(); + info("Waiting for mutation to occur"); + yield onMutation; + } + + let html = yield testActor.getProperty(adjacentNodeSelector, "innerHTML"); + ok(html.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); + + html = yield testActor.getProperty(adjacentNodeSelector, "innerHTML"); + ok(html.trim() === "1<span class=\"ref\">234</span>", + "Undo works for paste adjacent HTML"); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_menu-04-use-in-console.js b/devtools/client/inspector/test/browser_inspector_menu-04-use-in-console.js new file mode 100644 index 000000000..3908784f6 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_menu-04-use-in-console.js @@ -0,0 +1,61 @@ +/* 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 "Use in Console" menu item + +const TEST_URL = URL_ROOT + "doc_inspector_menu.html"; + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled"); +}); + +// Use the old webconsole since the node isn't being rendered as an HTML tag +// in the new one (Bug 1304794) +Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", false); +registerCleanupFunction(function* () { + Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled"); +}); + +add_task(function* () { + let { inspector, toolbox } = yield openInspectorForURL(TEST_URL); + + yield testUseInConsole(); + + function* testUseInConsole() { + info("Testing 'Use in Console' menu item."); + + yield selectNode("#console-var", inspector); + let container = yield getContainerForSelector("#console-var", inspector); + let allMenuItems = openContextMenuAndGetAllItems(inspector, { + target: container.tagLine, + }); + let menuItem = allMenuItems.find(i => i.id === "node-menu-useinconsole"); + menuItem.click(); + + yield inspector.once("console-var-ready"); + + let hud = toolbox.getPanel("webconsole").hud; + let jsterm = hud.jsterm; + + let jstermInput = jsterm.hud.document.querySelector(".jsterm-input-node"); + is(jstermInput.value, "temp0", "first console variable is named temp0"); + + let result = yield jsterm.execute(); + isnot(result.textContent.indexOf('<p id="console-var">'), -1, + "variable temp0 references correct node"); + + yield selectNode("#console-var-multi", inspector); + menuItem.click(); + yield inspector.once("console-var-ready"); + + is(jstermInput.value, "temp1", "second console variable is named temp1"); + + result = yield jsterm.execute(); + isnot(result.textContent.indexOf('<p id="console-var-multi">'), -1, + "variable temp1 references correct node"); + + jsterm.clearHistory(); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js b/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js new file mode 100644 index 000000000..df901f0a4 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js @@ -0,0 +1,79 @@ +/* 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 attribute items work in the context menu + +const TEST_URL = URL_ROOT + "doc_inspector_menu.html"; + +add_task(function* () { + let { inspector, testActor } = yield openInspectorForURL(TEST_URL); + yield selectNode("#attributes", inspector); + + yield testAddAttribute(); + yield testEditAttribute(); + yield testRemoveAttribute(); + + function* testAddAttribute() { + info("Triggering 'Add Attribute' and waiting for mutation to occur"); + let addAttribute = getMenuItem("node-menu-add-attribute"); + addAttribute.click(); + + EventUtils.synthesizeKey('class="u-hidden"', {}); + let onMutation = inspector.once("markupmutation"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onMutation; + + let hasAttribute = testActor.hasNode("#attributes.u-hidden"); + ok(hasAttribute, "attribute was successfully added"); + } + + function* testEditAttribute() { + info("Testing 'Edit Attribute' menu item"); + let editAttribute = getMenuItem("node-menu-edit-attribute"); + + info("Triggering 'Edit Attribute' and waiting for mutation to occur"); + inspector.nodeMenuTriggerInfo = { + type: "attribute", + name: "data-edit" + }; + editAttribute.click(); + EventUtils.synthesizeKey("data-edit='edited'", {}); + let onMutation = inspector.once("markupmutation"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onMutation; + + let isAttributeChanged = + yield testActor.hasNode("#attributes[data-edit='edited']"); + ok(isAttributeChanged, "attribute was successfully edited"); + } + + function* testRemoveAttribute() { + info("Testing 'Remove Attribute' menu item"); + let removeAttribute = getMenuItem("node-menu-remove-attribute"); + + info("Triggering 'Remove Attribute' and waiting for mutation to occur"); + inspector.nodeMenuTriggerInfo = { + type: "attribute", + name: "data-remove" + }; + let onMutation = inspector.once("markupmutation"); + removeAttribute.click(); + yield onMutation; + + let hasAttribute = yield testActor.hasNode("#attributes[data-remove]"); + ok(!hasAttribute, "attribute was successfully removed"); + } + + function getMenuItem(id) { + let allMenuItems = openContextMenuAndGetAllItems(inspector, { + target: getContainerForSelector("#attributes", inspector).tagLine, + }); + let menuItem = allMenuItems.find(i => i.id === id); + ok(menuItem, "Menu item '" + id + "' found"); + // Close the menu so synthesizing future keys won't select menu items. + EventUtils.synthesizeKey("VK_ESCAPE", {}); + return menuItem; + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_menu-06-other.js b/devtools/client/inspector/test/browser_inspector_menu-06-other.js new file mode 100644 index 000000000..9f4310121 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_menu-06-other.js @@ -0,0 +1,95 @@ +/* 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 = URL_ROOT + "doc_inspector_menu.html"; +add_task(function* () { + let { inspector, toolbox, testActor } = yield openInspectorForURL(TEST_URL); + yield testShowDOMProperties(); + yield testDuplicateNode(); + yield testDeleteNode(); + yield testDeleteRootNode(); + yield testScrollIntoView(); + function* testShowDOMProperties() { + info("Testing 'Show DOM Properties' menu item."); + let allMenuItems = openContextMenuAndGetAllItems(inspector); + let showDOMPropertiesNode = + allMenuItems.find(item => item.id === "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"); + showDOMPropertiesNode.click(); + 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* testDuplicateNode() { + info("Testing 'Duplicate Node' menu item for normal elements."); + + yield selectNode(".duplicate", inspector); + is((yield testActor.getNumberOfElementMatches(".duplicate")), 1, + "There should initially be 1 .duplicate node"); + + let allMenuItems = openContextMenuAndGetAllItems(inspector); + let menuItem = + allMenuItems.find(item => item.id === "node-menu-duplicatenode"); + ok(menuItem, "'Duplicate node' menu item should exist"); + + info("Triggering 'Duplicate Node' and waiting for inspector to update"); + let updated = inspector.once("markupmutation"); + menuItem.click(); + yield updated; + + is((yield testActor.getNumberOfElementMatches(".duplicate")), 2, + "The duplicated node should be in the markup."); + + let container = yield getContainerForSelector(".duplicate + .duplicate", + inspector); + ok(container, "A MarkupContainer should be created for the new node"); + } + + function* testDeleteNode() { + info("Testing 'Delete Node' menu item for normal elements."); + yield selectNode("#delete", inspector); + let allMenuItems = openContextMenuAndGetAllItems(inspector); + let deleteNode = allMenuItems.find(item => item.id === "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"); + deleteNode.click(); + yield updated; + + ok(!(yield testActor.hasNode("#delete")), "Node deleted"); + } + + function* testDeleteRootNode() { + info("Testing 'Delete Node' menu item does not delete root node."); + yield selectNode("html", inspector); + + let allMenuItems = openContextMenuAndGetAllItems(inspector); + let deleteNode = allMenuItems.find(item => item.id === "node-menu-delete"); + deleteNode.click(); + + let deferred = defer(); + executeSoon(deferred.resolve); + yield deferred.promise; + + ok((yield testActor.eval("!!content.document.documentElement")), + "Document element still alive."); + } + + function* testScrollIntoView() { + // Follow up bug to add this test - https://bugzilla.mozilla.org/show_bug.cgi?id=1154107 + todo(false, "Verify that node is scrolled into the viewport."); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_navigate_to_errors.js b/devtools/client/inspector/test/browser_inspector_navigate_to_errors.js new file mode 100644 index 000000000..c2266c852 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_navigate_to_errors.js @@ -0,0 +1,50 @@ +/* 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 works when navigating to error pages. + +const TEST_URL_1 = "data:text/html,<html><body id=\"test-doc-1\">page</body></html>"; +const TEST_URL_2 = "http://127.0.0.1:36325/"; +const TEST_URL_3 = "http://www.wronguri.wronguri/"; +const TEST_URL_4 = "data:text/html,<html><body>test-doc-4</body></html>"; + +add_task(function* () { + // Open the inspector on a valid URL + let { inspector, testActor } = yield openInspectorForURL(TEST_URL_1); + + info("Navigate to closed port"); + yield navigateTo(inspector, TEST_URL_2); + + let documentURI = yield testActor.eval("document.documentURI;"); + ok(documentURI.startsWith("about:neterror"), "content is correct."); + + let hasPage = yield getNodeFront("#test-doc-1", inspector); + ok(!hasPage, "Inspector actor is no longer able to reach previous page DOM node"); + + let hasNetErrorNode = yield getNodeFront("#errorShortDesc", inspector); + ok(hasNetErrorNode, "Inspector actor is able to reach error page DOM node"); + + let bundle = Services.strings.createBundle("chrome://global/locale/appstrings.properties"); + let domain = TEST_URL_2.match(/^http:\/\/(.*)\/$/)[1]; + let errorMsg = bundle.formatStringFromName("connectionFailure", + [domain], 1); + is(yield getDisplayedNodeTextContent("#errorShortDescText", inspector), errorMsg, + "Inpector really inspects the error page"); + + info("Navigate to unknown domain"); + yield navigateTo(inspector, TEST_URL_3); + + domain = TEST_URL_3.match(/^http:\/\/(.*)\/$/)[1]; + errorMsg = bundle.formatStringFromName("dnsNotFound", + [domain], 1); + is(yield getDisplayedNodeTextContent("#errorShortDescText", inspector), errorMsg, + "Inspector really inspects the new error page"); + + info("Navigate to a valid url"); + yield navigateTo(inspector, TEST_URL_4); + + is(yield getDisplayedNodeTextContent("body", inspector), "test-doc-4", + "Inspector really inspects the valid url"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_navigation.js b/devtools/client/inspector/test/browser_inspector_navigation.js new file mode 100644 index 000000000..dab6f7007 --- /dev/null +++ b/devtools/client/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/devtools/client/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, testActor } = yield openInspectorForURL(TEST_URL_1); + + yield selectNode("#i1", inspector); + + info("Navigating to a different page."); + yield navigateTo(inspector, TEST_URL_2); + + ok(true, "New page loaded"); + yield selectNode("#i1", inspector); + + let markuploaded = inspector.once("markuploaded"); + let onUpdated = inspector.once("inspector-updated"); + + info("Going back in history"); + yield testActor.eval("history.go(-1)"); + + info("Waiting for markup view to load after going back in history."); + yield markuploaded; + + info("Check that the inspector updates"); + yield onUpdated; + + ok(true, "Old page loaded"); + is((yield testActor.eval("location.href;")), TEST_URL_1, "URL is correct."); + + yield selectNode("#i1", inspector); +}); diff --git a/devtools/client/inspector/test/browser_inspector_open_on_neterror.js b/devtools/client/inspector/test/browser_inspector_open_on_neterror.js new file mode 100644 index 000000000..01e065a1a --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_open_on_neterror.js @@ -0,0 +1,37 @@ +/* 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 works correctly when opened against a net error page + +const TEST_URL_1 = "http://127.0.0.1:36325/"; +const TEST_URL_2 = "data:text/html,<html><body>test-doc-2</body></html>"; + +add_task(function* () { + // Unfortunately, net error page are not firing load event, so that we can't + // use addTab helper and have to do that: + let tab = gBrowser.selectedTab = gBrowser.addTab("data:text/html,empty"); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + yield ContentTask.spawn(tab.linkedBrowser, { url: TEST_URL_1 }, function* ({ url }) { + // Also, the neterror being privileged, the DOMContentLoaded only fires on + // the chromeEventHandler. + let { chromeEventHandler } = docShell; // eslint-disable-line no-undef + let onDOMContentLoaded = ContentTaskUtils.waitForEvent(chromeEventHandler, + "DOMContentLoaded", true); + content.location = url; + yield onDOMContentLoaded; + }); + + let { inspector, testActor } = yield openInspector(); + ok(true, "Inspector loaded on the already opened net error"); + + let documentURI = yield testActor.eval("document.documentURI;"); + ok(documentURI.startsWith("about:neterror"), "content is really a net error page."); + + info("Navigate to a valid url"); + yield navigateTo(inspector, TEST_URL_2); + + is(yield getDisplayedNodeTextContent("body", inspector), "test-doc-2", + "Inspector really inspects the valid url"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_pane-toggle-01.js b/devtools/client/inspector/test/browser_inspector_pane-toggle-01.js new file mode 100644 index 000000000..1ec95cec3 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-01.js @@ -0,0 +1,27 @@ +/* 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 inspector panel has a sidebar pane toggle button, and that +// this button is visible both in BOTTOM and SIDE hosts. + +add_task(function* () { + info("Open the inspector in a bottom toolbox host"); + let {toolbox, inspector} = yield openInspectorForURL("about:blank", "bottom"); + + let button = inspector.panelDoc.querySelector(".sidebar-toggle"); + ok(button, "The toggle button exists in the DOM"); + is(button.parentNode.id, "inspector-sidebar-toggle-box", + "The toggle button has the right parent"); + ok(button.getAttribute("title"), "The tool tip has initial state"); + ok(!button.classList.contains("pane-collapsed"), "The button is in expanded state"); + ok(!!button.getClientRects().length, "The button is visible"); + + info("Switch the host to side type"); + yield toolbox.switchHost("side"); + + ok(!!button.getClientRects().length, "The button is still visible"); + ok(!button.classList.contains("pane-collapsed"), + "The button is still in expanded state"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_pane-toggle-02.js b/devtools/client/inspector/test/browser_inspector_pane-toggle-02.js new file mode 100644 index 000000000..54b68c655 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-02.js @@ -0,0 +1,43 @@ +/* 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 inspector toggled panel is visible by default, is hidden after +// clicking on the toggle button and remains expanded/collapsed when switching +// hosts. + +add_task(function* () { + info("Open the inspector in a side toolbox host"); + let {toolbox, inspector} = yield openInspectorForURL("about:blank", "side"); + + let panel = inspector.panelDoc.querySelector("#inspector-splitter-box .controlled"); + + let button = inspector.panelDoc.querySelector(".sidebar-toggle"); + ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state"); + + info("Listen to the end of the animation on the sidebar panel"); + let onTransitionEnd = once(panel, "transitionend"); + + info("Click on the toggle button"); + EventUtils.synthesizeMouseAtCenter(button, {}, + inspector.panelDoc.defaultView); + + yield onTransitionEnd; + ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state"); + ok(!panel.hasAttribute("animated"), + "The collapsed panel will not perform unwanted animations"); + + info("Switch the host to bottom type"); + yield toolbox.switchHost("bottom"); + ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state"); + + info("Click on the toggle button to expand the panel again"); + + onTransitionEnd = once(panel, "transitionend"); + EventUtils.synthesizeMouseAtCenter(button, {}, + inspector.panelDoc.defaultView); + yield onTransitionEnd; + + ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_pane-toggle-03.js b/devtools/client/inspector/test/browser_inspector_pane-toggle-03.js new file mode 100644 index 000000000..02fffd995 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-03.js @@ -0,0 +1,38 @@ +/* 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 toggle button can collapse and expand the inspector side/bottom +// panel, and that the appropriate attributes are updated in the process. + +add_task(function* () { + let {inspector} = yield openInspectorForURL("about:blank"); + + let button = inspector.panelDoc.querySelector(".sidebar-toggle"); + let panel = inspector.panelDoc.querySelector("#inspector-splitter-box .controlled"); + + ok(!button.classList.contains("pane-collapsed"), "The button is in expanded state"); + + info("Listen to the end of the animation on the sidebar panel"); + let onTransitionEnd = once(panel, "transitionend"); + + info("Click on the toggle button"); + EventUtils.synthesizeMouseAtCenter(button, {}, + inspector.panelDoc.defaultView); + + yield onTransitionEnd; + ok(button.classList.contains("pane-collapsed"), "The button is in collapsed state"); + ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state"); + + info("Listen again to the end of the animation on the sidebar panel"); + onTransitionEnd = once(panel, "transitionend"); + + info("Click on the toggle button again"); + EventUtils.synthesizeMouseAtCenter(button, {}, + inspector.panelDoc.defaultView); + + yield onTransitionEnd; + ok(!button.classList.contains("pane-collapsed"), "The button is in expanded state"); + ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_pane-toggle-05.js b/devtools/client/inspector/test/browser_inspector_pane-toggle-05.js new file mode 100644 index 000000000..2a0c82037 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-05.js @@ -0,0 +1,33 @@ +/* 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 the keyboard navigation for the pane toggle using +* space and enter +*/ + +add_task(function* () { + let {inspector} = yield openInspectorForURL("about:blank", "side"); + let panel = inspector.panelDoc.querySelector("#inspector-splitter-box .controlled"); + + let button = inspector.panelDoc.querySelector(".sidebar-toggle"); + + ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state"); + + yield togglePane(button, "Press on the toggle button", panel, "VK_RETURN"); + ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state"); + + yield togglePane(button, "Press on the toggle button to expand the panel again", + panel, "VK_SPACE"); + ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state"); +}); + +function* togglePane(button, message, panel, keycode) { + let onTransitionEnd = once(panel, "transitionend"); + info(message); + button.focus(); + EventUtils.synthesizeKey(keycode, {}); + yield onTransitionEnd; +} diff --git a/devtools/client/inspector/test/browser_inspector_picker-stop-on-destroy.js b/devtools/client/inspector/test/browser_inspector_picker-stop-on-destroy.js new file mode 100644 index 000000000..bc81b9661 --- /dev/null +++ b/devtools/client/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 startPicker(toolbox); + + 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/devtools/client/inspector/test/browser_inspector_picker-stop-on-tool-change.js b/devtools/client/inspector/test/browser_inspector_picker-stop-on-tool-change.js new file mode 100644 index 000000000..37dc82ec1 --- /dev/null +++ b/devtools/client/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 startPicker(toolbox); + + 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/devtools/client/inspector/test/browser_inspector_portrait_mode.js b/devtools/client/inspector/test/browser_inspector_portrait_mode.js new file mode 100644 index 000000000..04fcc2b56 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_portrait_mode.js @@ -0,0 +1,78 @@ +/* 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 inspector splitter is properly initialized in horizontal mode if the +// inspector starts in portrait mode. + +add_task(function* () { + let { inspector, toolbox } = yield openInspectorForURL( + "data:text/html;charset=utf-8,<h1>foo</h1><span>bar</span>", "window"); + + let hostWindow = toolbox.win.parent; + let originalWidth = hostWindow.outerWidth; + let originalHeight = hostWindow.outerHeight; + + let splitter = inspector.panelDoc.querySelector(".inspector-sidebar-splitter"); + + // If the inspector is not already in landscape mode. + if (!splitter.classList.contains("vert")) { + info("Resize toolbox window to force inspector to landscape mode"); + let onClassnameMutation = waitForClassMutation(splitter); + hostWindow.resizeTo(800, 500); + yield onClassnameMutation; + + ok(splitter.classList.contains("vert"), "Splitter is in vertical mode"); + } + + info("Resize toolbox window to force inspector to portrait mode"); + let onClassnameMutation = waitForClassMutation(splitter); + hostWindow.resizeTo(500, 500); + yield onClassnameMutation; + + ok(splitter.classList.contains("horz"), "Splitter is in horizontal mode"); + + info("Close the inspector"); + yield gDevTools.closeToolbox(toolbox.target); + + info("Reopen inspector"); + ({ inspector, toolbox } = yield openInspector("window")); + + // Devtools window should still be 500px * 500px, inspector should still be in portrait. + splitter = inspector.panelDoc.querySelector(".inspector-sidebar-splitter"); + ok(splitter.classList.contains("horz"), "Splitter is in horizontal mode"); + + info("Restore original window size"); + toolbox.win.parent.resizeTo(originalWidth, originalHeight); +}); + +/** + * Helper waiting for a class attribute mutation on the provided target. Returns a + * promise. + * + * @param {Node} target + * Node to observe + * @return {Promise} promise that will resolve upon receiving a mutation for the class + * attribute on the target. + */ +function waitForClassMutation(target) { + return new Promise(resolve => { + let observer = new MutationObserver((mutations) => { + for (let mutation of mutations) { + if (mutation.attributeName === "class") { + observer.disconnect(); + resolve(); + return; + } + } + }); + observer.observe(target, { attributes: true }); + }); +} + +registerCleanupFunction(function () { + // Restore the host type for other tests. + Services.prefs.clearUserPref("devtools.toolbox.host"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_pseudoclass-lock.js b/devtools/client/inspector/test/browser_inspector_pseudoclass-lock.js new file mode 100644 index 000000000..bd98bd58f --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_pseudoclass-lock.js @@ -0,0 +1,160 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* globals getTestActorWithoutToolbox */ +"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, testActor} = yield openInspectorForURL(TEST_URL); + + info("Selecting the ruleview sidebar"); + inspector.sidebar.select("ruleview"); + + let view = inspector.ruleview.view; + + info("Selecting the test node"); + yield selectNode("#div-1", inspector); + + yield togglePseudoClass(inspector); + yield assertPseudoAddedToNode(inspector, testActor, view, "#div-1"); + + yield togglePseudoClass(inspector); + yield assertPseudoRemovedFromNode(testActor, "#div-1"); + yield assertPseudoRemovedFromView(inspector, testActor, view, "#div-1"); + + yield togglePseudoClass(inspector); + yield testNavigate(inspector, testActor, view); + + info("Toggle pseudo on the parent and ensure everything is toggled off"); + yield selectNode("#parent-div", inspector); + yield togglePseudoClass(inspector); + yield assertPseudoRemovedFromNode(testActor, "#div-1"); + yield assertPseudoRemovedFromView(inspector, testActor, view, "#div-1"); + + yield togglePseudoClass(inspector); + info("Assert pseudo is dismissed when toggling it on a sibling node"); + yield selectNode("#div-2", inspector); + yield togglePseudoClass(inspector); + yield assertPseudoAddedToNode(inspector, testActor, view, "#div-2"); + let hasLock = yield testActor.hasPseudoClassLock("#div-1", PSEUDO); + ok(!hasLock, "pseudo-class lock has been removed for the previous locked node"); + + info("Destroying the toolbox"); + let tab = toolbox.target.tab; + yield toolbox.destroy(); + + // As the toolbox get detroyed, we need to fetch a new test-actor + testActor = yield getTestActorWithoutToolbox(tab); + + yield assertPseudoRemovedFromNode(testActor, "#div-1"); + yield assertPseudoRemovedFromNode(testActor, "#div-2"); +}); + +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, testActor, ruleview) { + yield selectNode("#parent-div", inspector); + + info("Make sure the pseudoclass is still on after navigating to a parent"); + + ok((yield testActor.hasPseudoClassLock("#div-1", PSEUDO)), + "pseudo-class lock is still applied after inspecting ancestor"); + + yield selectNode("#div-2", inspector); + + info("Make sure the pseudoclass is still set after navigating to a " + + "non-hierarchy node"); + ok(yield testActor.hasPseudoClassLock("#div-1", PSEUDO), + "pseudo-class lock is still on after inspecting sibling node"); + + yield selectNode("#div-1", inspector); +} + +function* showPickerOn(selector, inspector) { + let nodeFront = yield getNodeFront(selector, inspector); + yield inspector.highlighter.showBoxModel(nodeFront); +} + +function* assertPseudoAddedToNode(inspector, testActor, ruleview, selector) { + info("Make sure the pseudoclass lock is applied to " + selector + " and its ancestors"); + + let hasLock = yield testActor.hasPseudoClassLock(selector, PSEUDO); + ok(hasLock, "pseudo-class lock has been applied"); + hasLock = yield testActor.hasPseudoClassLock("#parent-div", PSEUDO); + ok(hasLock, "pseudo-class lock has been applied"); + hasLock = yield testActor.hasPseudoClassLock("body", PSEUDO); + ok(hasLock, "pseudo-class lock has been applied"); + + 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 " + selector); + yield showPickerOn(selector, inspector); + + info("Check that the infobar selector contains the pseudo-class"); + let value = yield testActor.getHighlighterNodeTextContent( + "box-model-infobar-pseudo-classes"); + is(value, PSEUDO, "pseudo-class in infobar selector"); + yield inspector.highlighter.hideBoxModel(); +} + +function* assertPseudoRemovedFromNode(testActor, selector) { + info("Make sure the pseudoclass lock is removed from #div-1 and its " + + "ancestors"); + + let hasLock = yield testActor.hasPseudoClassLock(selector, PSEUDO); + ok(!hasLock, "pseudo-class lock has been removed"); + hasLock = yield testActor.hasPseudoClassLock("#parent-div", PSEUDO); + ok(!hasLock, "pseudo-class lock has been removed"); + hasLock = yield testActor.hasPseudoClassLock("body", PSEUDO); + ok(!hasLock, "pseudo-class lock has been removed"); +} + +function* assertPseudoRemovedFromView(inspector, testActor, ruleview, selector) { + 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(selector, inspector); + + let value = yield testActor.getHighlighterNodeTextContent( + "box-model-infobar-pseudo-classes"); + is(value, "", "pseudo-class removed from infobar selector"); + yield inspector.highlighter.hideBoxModel(); +} diff --git a/devtools/client/inspector/test/browser_inspector_pseudoclass-menu.js b/devtools/client/inspector/test/browser_inspector_pseudoclass-menu.js new file mode 100644 index 000000000..45bd82b76 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_pseudoclass-menu.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"; + +// Test that the inspector has the correct pseudo-class locking menu items and +// that these items actually work + +const TEST_URI = "data:text/html;charset=UTF-8," + + "pseudo-class lock node menu tests" + + "<div>test div</div>"; +const PSEUDOS = ["hover", "active", "focus"]; + +add_task(function* () { + let {inspector, testActor} = yield openInspectorForURL(TEST_URI); + yield selectNode("div", inspector); + + let allMenuItems = openContextMenuAndGetAllItems(inspector); + + yield testMenuItems(testActor, allMenuItems, inspector); +}); + +function* testMenuItems(testActor, allMenuItems, inspector) { + for (let pseudo of PSEUDOS) { + let menuItem = + allMenuItems.find(item => item.id === "node-menu-pseudo-" + pseudo); + ok(menuItem, ":" + pseudo + " menuitem exists"); + is(menuItem.disabled, false, ":" + pseudo + " menuitem is enabled"); + + // 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.click(); + + yield onPseudo; + yield onRefresh; + yield onMutations; + + let hasLock = yield testActor.hasPseudoClassLock("div", ":" + pseudo); + ok(hasLock, "pseudo-class lock has been applied"); + } +} diff --git a/devtools/client/inspector/test/browser_inspector_reload-01.js b/devtools/client/inspector/test/browser_inspector_reload-01.js new file mode 100644 index 000000000..61a1dde27 --- /dev/null +++ b/devtools/client/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, testActor } = yield openInspectorForURL(TEST_URI); + yield selectNode("p", inspector); + + let markupLoaded = inspector.once("markuploaded"); + + info("Reloading page."); + yield testActor.eval("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/devtools/client/inspector/test/browser_inspector_reload-02.js b/devtools/client/inspector/test/browser_inspector_reload-02.js new file mode 100644 index 000000000..c9940a828 --- /dev/null +++ b/devtools/client/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, testActor } = yield openInspectorForURL(TEST_URI); + + let markupLoaded = inspector.once("markuploaded"); + + info("Reloading page."); + yield testActor.eval("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."); + yield testActor.eval("location.reload"); + + let rectFront = yield getNodeFront("rect", inspector); + is(inspector.selection.nodeFront, rectFront, "<rect> selected after reload."); +}); diff --git a/devtools/client/inspector/test/browser_inspector_remove-iframe-during-load.js b/devtools/client/inspector/test/browser_inspector_remove-iframe-during-load.js new file mode 100644 index 000000000..2058b85fa --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_remove-iframe-during-load.js @@ -0,0 +1,48 @@ +/* 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 = URL_ROOT + "doc_inspector_remove-iframe-during-load.html"; + +add_task(function* () { + let {inspector, testActor} = 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 + yield testActor.loadAndWaitForCustomEvent(TEST_URL); + + // 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(!(yield testActor.hasNode("iframe")), + "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 onMarkupLoaded = inspector.once("markuploaded"); + testActor.eval("new " + function () { + let iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + iframe.remove(); + }); + + ok(!(yield testActor.hasNode("iframe")), + "The after-load iframe should have been removed."); + + info("Waiting for markup-view to load."); + yield onMarkupLoaded; + + // Assert that the markup-view is displayed and works + ok(!(yield testActor.hasNode("iframe")), "Iframe has been removed."); + is((yield testActor.getProperty("#yay", "textContent")), "load", + "Load event fired."); + + yield selectNode("#yay", inspector); +}); diff --git a/devtools/client/inspector/test/browser_inspector_search-01.js b/devtools/client/inspector/test/browser_inspector_search-01.js new file mode 100644 index 000000000..a4fd4d424 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-01.js @@ -0,0 +1,96 @@ +/* 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/ */ +/* eslint no-inline-comments: 0 */ +"use strict"; + +requestLongerTimeout(2); + +// Test that searching for nodes in the search field actually selects those +// nodes. + +const TEST_URL = URL_ROOT + "doc_inspector_search.html"; + +// 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 = [ + ["#", "b1", true], // # + ["d", "b1", true], // #d + ["1", "b1", true], // #d1 + ["VK_RETURN", "d1", true], // #d1 + ["VK_BACK_SPACE", "d1", true], // #d + ["2", "d1", true], // #d2 + ["VK_RETURN", "d2", true], // #d2 + ["2", "d2", true], // #d22 + ["VK_RETURN", "d2", false], // #d22 + ["VK_BACK_SPACE", "d2", false], // #d2 + ["VK_RETURN", "d2", true], // #d2 + ["VK_BACK_SPACE", "d2", true], // #d + ["1", "d2", true], // #d1 + ["VK_RETURN", "d1", true], // #d1 + ["VK_BACK_SPACE", "d1", true], // #d + ["VK_BACK_SPACE", "d1", true], // # + ["VK_BACK_SPACE", "d1", true], // + ["d", "d1", true], // d + ["i", "d1", true], // di + ["v", "d1", true], // div + [".", "d1", true], // div. + ["c", "d1", true], // div.c + ["VK_UP", "d1", true], // div.c1 + ["VK_TAB", "d1", true], // div.c1 + ["VK_RETURN", "d2", true], // div.c1 + ["VK_BACK_SPACE", "d2", true], // div.c + ["VK_BACK_SPACE", "d2", true], // div. + ["VK_BACK_SPACE", "d2", true], // div + ["VK_BACK_SPACE", "d2", true], // di + ["VK_BACK_SPACE", "d2", true], // d + ["VK_BACK_SPACE", "d2", true], // + [".", "d2", true], // . + ["c", "d2", true], // .c + ["1", "d2", true], // .c1 + ["VK_RETURN", "d2", true], // .c1 + ["VK_RETURN", "s2", true], // .c1 + ["VK_RETURN", "p1", true], // .c1 + ["P", "p1", true], // .c1P + ["VK_RETURN", "p1", false], // .c1P +]; + +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) { + info(index + ": Pressing key " + key + " to get id " + id + "."); + let done = inspector.searchSuggestions.once("processing-done"); + EventUtils.synthesizeKey(key, {}, inspector.panelWin); + yield done; + info("Got processing-done event"); + + if (key === "VK_RETURN") { + info("Waiting for " + (isValid ? "NO " : "") + "results"); + yield inspector.search.once("search-result"); + } + + info("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-style-searchbox-no-match"), isValid, + "Correct searchbox result state for state " + index); + + index++; + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_search-02.js b/devtools/client/inspector/test/browser_inspector_search-02.js new file mode 100644 index 000000000..5e75f5dd2 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-02.js @@ -0,0 +1,169 @@ +/* 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 = 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"}, + {label: "#d1"}, + {label: "#d2"} + ] + }, + { + key: "i", + suggestions: [{label: "div"}] + }, + { + key: "v", + suggestions: [] + }, + { + key: " ", + suggestions: [ + {label: "div div"}, + {label: "div span"} + ] + }, + { + key: ">", + suggestions: [ + {label: "div >div"}, + {label: "div >span"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: "div div"}, + {label: "div span"} + ] + }, + { + key: "+", + suggestions: [{label: "div +span"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: "div div"}, + {label: "div span"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: "VK_BACK_SPACE", + suggestions: [{label: "div"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: "div"}, + {label: "#d1"}, + {label: "#d2"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: "p", + suggestions: [ + {label: "p"}, + {label: "#p1"}, + {label: "#p2"}, + {label: "#p3"}, + ] + }, + { + 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, "input"); + 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 + + "' - key pressed: " + key); + 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(actualSuggestions[i].label, suggestions[i].label, + "The suggestion at " + i + "th index is correct."); + } + } +}); + +function formatSuggestions(suggestions) { + return "[" + suggestions + .map(s => "'" + s.label + "'") + .join(", ") + "]"; +} diff --git a/devtools/client/inspector/test/browser_inspector_search-03.js b/devtools/client/inspector/test/browser_inspector_search-03.js new file mode 100644 index 000000000..215b536a6 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-03.js @@ -0,0 +1,250 @@ +/* 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 = 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) +var TEST_DATA = [ + { + key: "d", + suggestions: [ + {label: "div"}, + {label: "#d1"}, + {label: "#d2"} + ] + }, + { + key: "i", + suggestions: [{label: "div"}] + }, + { + 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"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: "div"}, + {label: "#d1"}, + {label: "#d2"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: ".", + suggestions: [ + {label: ".c1"}, + {label: ".c2"} + ] + }, + { + key: "c", + suggestions: [ + {label: ".c1"}, + {label: ".c2"} + ] + }, + { + key: "2", + suggestions: [] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: ".c1"}, + {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"}, + {label: ".c2"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: ".c1"}, + {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: [] + }, + { + key: "p", + suggestions: [ + {label: "p"}, + {label: "#p1"}, + {label: "#p2"}, + {label: "#p3"} + ] + }, + { + key: "[", suggestions: [] + }, + { + key: "i", suggestions: [] + }, + { + key: "d", suggestions: [] + }, + { + key: "*", suggestions: [] + }, + { + key: "=", suggestions: [] + }, + { + key: "p", suggestions: [] + }, + { + key: "]", suggestions: [] + }, + { + key: ".", + suggestions: [ + {label: "p[id*=p].c1"}, + {label: "p[id*=p].c2"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: "#", + suggestions: [ + {label: "p[id*=p]#p1"}, + {label: "p[id*=p]#p2"}, + {label: "p[id*=p]#p3"} + ] + } +]; + +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, "input"); + 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(actualSuggestions[i].label, suggestions[i].label, + "The suggestion at " + i + "th index is correct."); + } + } +}); + +function formatSuggestions(suggestions) { + return "[" + suggestions + .map(s => "'" + s.label + "'") + .join(", ") + "]"; +} diff --git a/devtools/client/inspector/test/browser_inspector_search-04.js b/devtools/client/inspector/test/browser_inspector_search-04.js new file mode 100644 index 000000000..a5aee8156 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-04.js @@ -0,0 +1,112 @@ +/* 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 inside iframes does work. + +const IFRAME_SRC = "doc_inspector_search.html"; +const TEST_URL = "data:text/html;charset=utf-8," + + "<div class=\"c1 c2\">" + + "<iframe src=\"" + URL_ROOT + IFRAME_SRC + "\"></iframe>" + + "<iframe src=\"" + URL_ROOT + IFRAME_SRC + "\"></iframe>"; + +// 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) +var TEST_DATA = [ + { + key: "d", + suggestions: [ + {label: "div"}, + {label: "#d1"}, + {label: "#d2"} + ] + }, + { + key: "i", + suggestions: [{label: "div"}] + }, + { + key: "v", + suggestions: [] + }, + { + key: "VK_BACK_SPACE", + suggestions: [{label: "div"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [ + {label: "div"}, + {label: "#d1"}, + {label: "#d2"} + ] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: ".", + suggestions: [ + {label: ".c1"}, + {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"} + ] + }, +]; + +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, "input"); + 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(actualSuggestions[i].label, suggestions[i].label, + "The suggestion at " + i + "th index is correct."); + } + } +}); + +function formatSuggestions(suggestions) { + return "[" + suggestions + .map(s => "'" + s.label + "'") + .join(", ") + "]"; +} diff --git a/devtools/client/inspector/test/browser_inspector_search-05.js b/devtools/client/inspector/test/browser_inspector_search-05.js new file mode 100644 index 000000000..542d0ccc5 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-05.js @@ -0,0 +1,93 @@ +/* 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 when search results contain suggestions for nodes in other +// frames, selecting these suggestions actually selects the right nodes. + +requestLongerTimeout(2); + +const IFRAME_SRC = "doc_inspector_search.html"; +const NESTED_IFRAME_SRC = ` + <button id="b1">Nested button</button> + <iframe id="iframe-4" src="${URL_ROOT + IFRAME_SRC}"></iframe> +`; +const TEST_URL = ` + <iframe id="iframe-1" src="${URL_ROOT + IFRAME_SRC}"></iframe> + <iframe id="iframe-2" src="${URL_ROOT + IFRAME_SRC}"></iframe> + <iframe id="iframe-3" + src="data:text/html;charset=utf-8,${encodeURI(NESTED_IFRAME_SRC)}"> + </iframe> +`; + +add_task(function* () { + let {inspector} = yield openInspectorForURL( + "data:text/html;charset=utf-8," + encodeURI(TEST_URL)); + + info("Focus the search box"); + yield focusSearchBoxUsingShortcut(inspector.panelWin); + + info("Enter # to search for all ids"); + let processingDone = once(inspector.searchSuggestions, "processing-done"); + EventUtils.synthesizeKey("#", {}, inspector.panelWin); + yield processingDone; + + info("Wait for search query to complete"); + yield inspector.searchSuggestions._lastQuery; + + info("Press tab to fill the search input with the first suggestion"); + processingDone = once(inspector.searchSuggestions, "processing-done"); + EventUtils.synthesizeKey("VK_TAB", {}, inspector.panelWin); + yield processingDone; + + info("Press enter and expect a new selection"); + let onSelect = inspector.once("inspector-updated"); + EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin); + yield onSelect; + + yield checkCorrectButton(inspector, "#iframe-1"); + + info("Press enter to cycle through multiple nodes matching this suggestion"); + onSelect = inspector.once("inspector-updated"); + EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin); + yield onSelect; + + yield checkCorrectButton(inspector, "#iframe-2"); + + info("Press enter to cycle through multiple nodes matching this suggestion"); + onSelect = inspector.once("inspector-updated"); + EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin); + yield onSelect; + + yield checkCorrectButton(inspector, "#iframe-3"); + + info("Press enter to cycle through multiple nodes matching this suggestion"); + onSelect = inspector.once("inspector-updated"); + EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin); + yield onSelect; + + yield checkCorrectButton(inspector, "#iframe-4"); + + info("Press enter to cycle through multiple nodes matching this suggestion"); + onSelect = inspector.once("inspector-updated"); + EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin); + yield onSelect; + + yield checkCorrectButton(inspector, "#iframe-1"); +}); + +let checkCorrectButton = Task.async(function* (inspector, frameSelector) { + let {walker} = inspector; + let node = inspector.selection.nodeFront; + + ok(node.id, "b1", "The selected node is #b1"); + ok(node.tagName.toLowerCase(), "button", + "The selected node is <button>"); + + let selectedNodeDoc = yield walker.document(node); + let iframe = yield walker.multiFrameQuerySelectorAll(frameSelector); + iframe = yield iframe.item(0); + let iframeDoc = (yield walker.children(iframe)).nodes[0]; + is(selectedNodeDoc, iframeDoc, "The selected node is in " + frameSelector); +}); diff --git a/devtools/client/inspector/test/browser_inspector_search-06.js b/devtools/client/inspector/test/browser_inspector_search-06.js new file mode 100644 index 000000000..1b3950c00 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-06.js @@ -0,0 +1,87 @@ +/* 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 searching again for nodes after they are removed or added from the +// DOM works correctly. + +const TEST_URL = URL_ROOT + "doc_inspector_search.html"; + +add_task(function* () { + let { inspector, testActor } = yield openInspectorForURL(TEST_URL); + + info("Searching for test node #d1"); + yield focusSearchBoxUsingShortcut(inspector.panelWin); + yield synthesizeKeys(["#", "d", "1", "VK_RETURN"], inspector); + + yield inspector.search.once("search-result"); + assertHasResult(inspector, true); + + info("Removing node #d1"); + // Expect an inspector-updated event here, because removing #d1 causes the + // breadcrumbs to update (since #d1 is displayed in it). + let onUpdated = inspector.once("inspector-updated"); + yield mutatePage(inspector, testActor, + "document.getElementById(\"d1\").remove()"); + yield onUpdated; + + info("Pressing return button to search again for node #d1."); + yield synthesizeKeys("VK_RETURN", inspector); + + yield inspector.search.once("search-result"); + assertHasResult(inspector, false); + + info("Emptying the field and searching for a node that doesn't exist: #d3"); + let keys = ["VK_BACK_SPACE", "VK_BACK_SPACE", "VK_BACK_SPACE", "#", "d", "3", + "VK_RETURN"]; + yield synthesizeKeys(keys, inspector); + + yield inspector.search.once("search-result"); + assertHasResult(inspector, false); + + info("Create the #d3 node in the page"); + // No need to expect an inspector-updated event here, Creating #d3 isn't going + // to update the breadcrumbs in any ways. + yield mutatePage(inspector, testActor, + `document.getElementById("d2").insertAdjacentHTML( + "afterend", "<div id=d3></div>")`); + + info("Pressing return button to search again for node #d3."); + yield synthesizeKeys("VK_RETURN", inspector); + + yield inspector.search.once("search-result"); + assertHasResult(inspector, true); + + // Catch-all event for remaining server requests when searching for the new + // node. + yield inspector.once("inspector-updated"); +}); + +function* synthesizeKeys(keys, inspector) { + if (typeof keys === "string") { + keys = [keys]; + } + + for (let key of keys) { + info("Synthesizing key " + key + " in the search box"); + let eventHandled = once(inspector.searchBox, "keypress", true); + EventUtils.synthesizeKey(key, {}, inspector.panelWin); + yield eventHandled; + info("Waiting for the search query to complete"); + yield inspector.searchSuggestions._lastQuery; + } +} + +function assertHasResult(inspector, expectResult) { + is(inspector.searchBox.classList.contains("devtools-style-searchbox-no-match"), + !expectResult, + "There are" + (expectResult ? "" : " no") + " search results"); +} + +function* mutatePage(inspector, testActor, expression) { + let onMutation = inspector.once("markupmutation"); + yield testActor.eval(expression); + yield onMutation; +} diff --git a/devtools/client/inspector/test/browser_inspector_search-07.js b/devtools/client/inspector/test/browser_inspector_search-07.js new file mode 100644 index 000000000..79e2021cd --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-07.js @@ -0,0 +1,49 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that searching for classes on SVG elements does work (see bug 1219920). + +const TEST_URL = URL_ROOT + "doc_inspector_search-svg.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. +const TEST_DATA = [{ + key: "c", + suggestions: ["circle", "clipPath", ".class1", ".class2"] +}, { + key: "VK_BACK_SPACE", + suggestions: [] +}, { + key: ".", + suggestions: [".class1", ".class2"] +}]; + +add_task(function* () { + let {inspector} = yield openInspectorForURL(TEST_URL); + let {searchBox} = inspector; + let popup = inspector.searchSuggestions.searchPopup; + + yield focusSearchBoxUsingShortcut(inspector.panelWin); + + for (let {key, suggestions} of TEST_DATA) { + info("Pressing " + key + " to get " + suggestions); + + let command = once(searchBox, "input"); + EventUtils.synthesizeKey(key, {}, inspector.panelWin); + yield command; + + info("Waiting for search query to complete and getting the suggestions"); + yield inspector.searchSuggestions._lastQuery; + 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(actualSuggestions[i].label, suggestions[i], + "The suggestion at " + i + "th index is correct."); + } + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_search-08.js b/devtools/client/inspector/test/browser_inspector_search-08.js new file mode 100644 index 000000000..f5c77fcac --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-08.js @@ -0,0 +1,64 @@ +/* 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 namespaced elements does work. + +const XHTML = ` + <!DOCTYPE html> + <html xmlns="http://www.w3.org/1999/xhtml" + xmlns:svg="http://www.w3.org/2000/svg"> + <body> + <svg:svg width="100" height="100"> + <svg:clipPath> + <svg:rect x="0" y="0" width="10" height="5"></svg:rect> + </svg:clipPath> + <svg:circle cx="0" cy="0" r="5"></svg:circle> + </svg:svg> + </body> + </html> +`; + +const TEST_URI = "data:application/xhtml+xml;charset=utf-8," + encodeURI(XHTML); + +// 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. +const TEST_DATA = [{ + key: "c", + suggestions: ["circle", "clipPath"] +}, { + key: "VK_BACK_SPACE", + suggestions: [] +}, { + key: "s", + suggestions: ["svg"] +}]; + +add_task(function* () { + let {inspector} = yield openInspectorForURL(TEST_URI); + let {searchBox} = inspector; + let popup = inspector.searchSuggestions.searchPopup; + + yield focusSearchBoxUsingShortcut(inspector.panelWin); + + for (let {key, suggestions} of TEST_DATA) { + info("Pressing " + key + " to get " + suggestions.join(", ")); + + let command = once(searchBox, "input"); + EventUtils.synthesizeKey(key, {}, inspector.panelWin); + yield command; + + info("Waiting for search query to complete and getting the suggestions"); + yield inspector.searchSuggestions._lastQuery; + 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(actualSuggestions[i].label, suggestions[i], + "The suggestion at " + i + "th index is correct."); + } + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_search-clear.js b/devtools/client/inspector/test/browser_inspector_search-clear.js new file mode 100644 index 000000000..4388c70a6 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-clear.js @@ -0,0 +1,52 @@ +/* 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"; + +// Bug 1295081 Test searchbox clear button's display behavior is correct + +const XHTML = ` + <!DOCTYPE html> + <html xmlns="http://www.w3.org/1999/xhtml" + xmlns:svg="http://www.w3.org/2000/svg"> + <body> + <svg:svg width="100" height="100"> + <svg:clipPath> + <svg:rect x="0" y="0" width="10" height="5"></svg:rect> + </svg:clipPath> + <svg:circle cx="0" cy="0" r="5"></svg:circle> + </svg:svg> + </body> + </html> +`; + +const TEST_URI = "data:application/xhtml+xml;charset=utf-8," + encodeURI(XHTML); + +// Type "d" in inspector-searchbox, Enter [Back space] key and check if the +// clear button is shown correctly +add_task(function* () { + let {inspector} = yield openInspectorForURL(TEST_URI); + let {searchBox, searchClearButton} = inspector; + + yield focusSearchBoxUsingShortcut(inspector.panelWin); + + info("Type d and the clear button will be shown"); + + let command = once(searchBox, "input"); + EventUtils.synthesizeKey("c", {}, inspector.panelWin); + yield command; + + info("Waiting for search query to complete and getting the suggestions"); + yield inspector.searchSuggestions._lastQuery; + + ok(!searchClearButton.hidden, + "The clear button is shown when some word is in searchBox"); + + EventUtils.synthesizeKey("VK_BACK_SPACE", {}, inspector.panelWin); + yield command; + + info("Waiting for search query to complete and getting the suggestions"); + yield inspector.searchSuggestions._lastQuery; + + ok(searchClearButton.hidden, "The clear button is hidden when no word is in searchBox"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js b/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js new file mode 100644 index 000000000..137456468 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js @@ -0,0 +1,82 @@ +/* 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 inspector's markup view search filter context menu works properly. + +const TEST_INPUT = "h1"; +const TEST_URI = "<h1>test filter context menu</h1>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {toolbox, inspector} = yield openInspector(); + let {searchBox} = inspector; + yield selectNode("h1", inspector); + + let win = inspector.panelWin; + let searchContextMenu = toolbox.textBoxContextMenuPopup; + ok(searchContextMenu, + "The search filter context menu is loaded in the inspector"); + + let cmdUndo = searchContextMenu.querySelector("[command=cmd_undo]"); + let cmdDelete = searchContextMenu.querySelector("[command=cmd_delete]"); + let cmdSelectAll = searchContextMenu.querySelector("[command=cmd_selectAll]"); + let cmdCut = searchContextMenu.querySelector("[command=cmd_cut]"); + let cmdCopy = searchContextMenu.querySelector("[command=cmd_copy]"); + let cmdPaste = searchContextMenu.querySelector("[command=cmd_paste]"); + + emptyClipboard(); + + info("Opening context menu"); + let onFocus = once(searchBox, "focus"); + searchBox.focus(); + yield onFocus; + + let onContextMenuPopup = once(searchContextMenu, "popupshowing"); + EventUtils.synthesizeMouse(searchBox, 2, 2, + {type: "contextmenu", button: 2}, win); + yield onContextMenuPopup; + + is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled"); + is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled"); + is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled"); + + // Cut/Copy items are enabled in context menu even if there + // is no selection. See also Bug 1303033 + is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled"); + is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled"); + + if (isWindows()) { + // emptyClipboard only works on Windows (666254), assert paste only for this OS. + is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled"); + } + + info("Closing context menu"); + let onContextMenuHidden = once(searchContextMenu, "popuphidden"); + searchContextMenu.hidePopup(); + yield onContextMenuHidden; + + info("Copy text in search field using the context menu"); + searchBox.value = TEST_INPUT; + searchBox.select(); + searchBox.focus(); + EventUtils.synthesizeMouse(searchBox, 2, 2, + {type: "contextmenu", button: 2}, win); + yield onContextMenuPopup; + yield waitForClipboardPromise(() => cmdCopy.click(), TEST_INPUT); + searchContextMenu.hidePopup(); + yield onContextMenuHidden; + + info("Reopen context menu and check command properties"); + EventUtils.synthesizeMouse(searchBox, 2, 2, + {type: "contextmenu", button: 2}, win); + yield onContextMenuPopup; + + is(cmdUndo.getAttribute("disabled"), "", "cmdUndo is enabled"); + is(cmdDelete.getAttribute("disabled"), "", "cmdDelete is enabled"); + is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled"); + is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled"); + is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled"); + is(cmdPaste.getAttribute("disabled"), "", "cmdPaste is enabled"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_search-label.js b/devtools/client/inspector/test/browser_inspector_search-label.js new file mode 100644 index 000000000..669ad79b8 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-label.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"; + +// Check that search label updated correctcly based on the search result. + +const TEST_URL = URL_ROOT + "doc_inspector_search.html"; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URL); + let { panelWin, searchResultsLabel } = inspector; + + info("Searching for test node #d1"); + // Expect the label shows 1 result + yield focusSearchBoxUsingShortcut(panelWin); + synthesizeKeys("#d1", panelWin); + EventUtils.synthesizeKey("VK_RETURN", {}, panelWin); + + yield inspector.search.once("search-result"); + is(searchResultsLabel.textContent, "1 of 1"); + + info("Click the clear button"); + // Expect the label is cleared after clicking the clear button. + + inspector.searchClearButton.click(); + is(searchResultsLabel.textContent, ""); + + // Catch-all event for remaining server requests when searching for the new + // node. + yield inspector.once("inspector-updated"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_search-navigation.js b/devtools/client/inspector/test/browser_inspector_search-navigation.js new file mode 100644 index 000000000..bf409fcc7 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-navigation.js @@ -0,0 +1,76 @@ +/* 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_BACK_SPACE", "div.l"], + ["VK_TAB", "div.l1"], + [" ", "div.l1 "], + ["VK_UP", "div.l1 div"], + ["VK_UP", "div.l1 span"], + ["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 span"], + ["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_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 = 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; + + info("Waiting for search query to complete"); + yield inspector.searchSuggestions._lastQuery; + + is(inspector.searchBox.value, query, "The searchbox value is correct"); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_search-reserved.js b/devtools/client/inspector/test/browser_inspector_search-reserved.js new file mode 100644 index 000000000..e8141eb08 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-reserved.js @@ -0,0 +1,132 @@ +/* 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 searching for ids and classes that contain reserved characters. +const TEST_URL = URL_ROOT + "doc_inspector_search-reserved.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: "#", + suggestions: [{label: "#d1\\.d2"}] + }, + { + key: "d", + suggestions: [{label: "#d1\\.d2"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [{label: "#d1\\.d2"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: ".", + suggestions: [{label: ".c1\\.c2"}] + }, + { + key: "c", + suggestions: [{label: ".c1\\.c2"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [{label: ".c1\\.c2"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: "d", + suggestions: [{label: "div"}, + {label: "#d1\\.d2"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: "c", + suggestions: [{label: ".c1\\.c2"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [] + }, + { + key: "b", + suggestions: [{label: "body"}] + }, + { + key: "o", + suggestions: [{label: "body"}] + }, + { + key: "d", + suggestions: [{label: "body"}] + }, + { + key: "y", + suggestions: [] + }, + { + key: " ", + suggestions: [{label: "body div"}] + }, + { + key: ".", + suggestions: [{label: "body .c1\\.c2"}] + }, + { + key: "VK_BACK_SPACE", + suggestions: [{label: "body div"}] + }, + { + key: "#", + suggestions: [{label: "body #d1\\.d2"}] + } +]; + +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, "input"); + 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."); + } + } +}); + +function formatSuggestions(suggestions) { + return "[" + suggestions + .map(s => "'" + s.label + "'") + .join(", ") + "]"; +} diff --git a/devtools/client/inspector/test/browser_inspector_search-selection.js b/devtools/client/inspector/test/browser_inspector_search-selection.js new file mode 100644 index 000000000..99f1e34bb --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-selection.js @@ -0,0 +1,62 @@ +/* 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 navigation between nodes in search results +const {AppConstants} = require("resource://gre/modules/AppConstants.jsm"); + +const TEST_URL = URL_ROOT + "doc_inspector_search.html"; + +add_task(function* () { + let {inspector} = yield openInspectorForURL(TEST_URL); + + info("Focus the search box"); + yield focusSearchBoxUsingShortcut(inspector.panelWin); + + info("Enter body > p to search"); + let processingDone = once(inspector.searchSuggestions, "processing-done"); + EventUtils.sendString("body > p", inspector.panelWin); + yield processingDone; + + info("Wait for search query to complete"); + yield inspector.searchSuggestions._lastQuery; + + let msg = "Press enter and expect a new selection"; + yield sendKeyAndCheck(inspector, msg, "VK_RETURN", {}, "#p1"); + + msg = "Press enter to cycle through multiple nodes"; + yield sendKeyAndCheck(inspector, msg, "VK_RETURN", {}, "#p2"); + + msg = "Press shift-enter to select the previous node"; + yield sendKeyAndCheck(inspector, msg, "VK_RETURN", { shiftKey: true }, "#p1"); + + if (AppConstants.platform === "macosx") { + msg = "Press meta-g to cycle through multiple nodes"; + yield sendKeyAndCheck(inspector, msg, "VK_G", { metaKey: true }, "#p2"); + + msg = "Press shift+meta-g to select the previous node"; + yield sendKeyAndCheck(inspector, msg, "VK_G", + { metaKey: true, shiftKey: true }, "#p1"); + } else { + msg = "Press ctrl-g to cycle through multiple nodes"; + yield sendKeyAndCheck(inspector, msg, "VK_G", { ctrlKey: true }, "#p2"); + + msg = "Press shift+ctrl-g to select the previous node"; + yield sendKeyAndCheck(inspector, msg, "VK_G", + { ctrlKey: true, shiftKey: true }, "#p1"); + } +}); + +let sendKeyAndCheck = Task.async(function* (inspector, description, key, + modifiers, expectedId) { + info(description); + let onSelect = inspector.once("inspector-updated"); + EventUtils.synthesizeKey(key, modifiers, inspector.panelWin); + yield onSelect; + + let selectedNode = inspector.selection.nodeFront; + info(selectedNode.id + " is selected with text " + inspector.searchBox.value); + let targetNode = yield getNodeFront(expectedId, inspector); + is(selectedNode, targetNode, "Correct node " + expectedId + " is selected"); +}); diff --git a/devtools/client/inspector/test/browser_inspector_search-sidebar.js b/devtools/client/inspector/test/browser_inspector_search-sidebar.js new file mode 100644 index 000000000..d65a670ac --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-sidebar.js @@ -0,0 +1,74 @@ +/* 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 depending where the user last clicked in the inspector, the right search +// field is focused when ctrl+F is pressed. + +add_task(function* () { + let {inspector} = yield openInspectorForURL("data:text/html;charset=utf-8,Search!"); + + info("Check that by default, the inspector search field gets focused"); + pressCtrlF(); + isInInspectorSearchBox(inspector); + + info("Click somewhere in the rule-view"); + clickInRuleView(inspector); + + info("Check that the rule-view search field gets focused"); + pressCtrlF(); + isInRuleViewSearchBox(inspector); + + info("Click in the inspector again"); + yield clickContainer("head", inspector); + + info("Check that now we're back in the inspector, its search field gets focused"); + pressCtrlF(); + isInInspectorSearchBox(inspector); + + info("Switch to the computed view, and click somewhere inside it"); + selectComputedView(inspector); + clickInComputedView(inspector); + + info("Check that the computed-view search field gets focused"); + pressCtrlF(); + isInComputedViewSearchBox(inspector); + + info("Click in the inspector yet again"); + yield clickContainer("body", inspector); + + info("We're back in the inspector again, check the inspector search field focuses"); + pressCtrlF(); + isInInspectorSearchBox(inspector); +}); + +function pressCtrlF() { + EventUtils.synthesizeKey("f", {accelKey: true}); +} + +function clickInRuleView(inspector) { + let el = inspector.panelDoc.querySelector("#sidebar-panel-ruleview"); + EventUtils.synthesizeMouseAtCenter(el, {}, inspector.panelDoc.defaultView); +} + +function clickInComputedView(inspector) { + let el = inspector.panelDoc.querySelector("#sidebar-panel-computedview"); + EventUtils.synthesizeMouseAtCenter(el, {}, inspector.panelDoc.defaultView); +} + +function isInInspectorSearchBox(inspector) { + // Focus ends up in an anonymous child of the XUL textbox. + ok(inspector.panelDoc.activeElement.closest("#inspector-searchbox"), + "The inspector search field is focused when ctrl+F is pressed"); +} + +function isInRuleViewSearchBox(inspector) { + is(inspector.panelDoc.activeElement, inspector.ruleview.view.searchField, + "The rule-view search field is focused when ctrl+F is pressed"); +} + +function isInComputedViewSearchBox(inspector) { + is(inspector.panelDoc.activeElement, inspector.computedview.computedView.searchField, + "The computed-view search field is focused when ctrl+F is pressed"); +} diff --git a/devtools/client/inspector/test/browser_inspector_search-suggests-ids-and-classes.js b/devtools/client/inspector/test/browser_inspector_search-suggests-ids-and-classes.js new file mode 100644 index 000000000..b20c72342 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search-suggests-ids-and-classes.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that the selector-search input proposes ids and classes even when . and +// # is missing, but that this only occurs when the query is one word (no +// selector combination) + +// The various states of the inspector: [key, suggestions array] +// [ +// what key to press, +// suggestions array with count [ +// [suggestion1, count1], [suggestion2] ... +// ] count can be left to represent 1 +// ] +const KEY_STATES = [ + ["s", [["span", 1], [".span", 1], ["#span", 1]]], + ["p", [["span", 1], [".span", 1], ["#span", 1]]], + ["a", [["span", 1], [".span", 1], ["#span", 1]]], + ["n", []], + [" ", [["span div", 1]]], + // mixed tag/class/id suggestions only work for the first word + ["d", [["span div", 1]]], + ["VK_BACK_SPACE", [["span div", 1]]], + ["VK_BACK_SPACE", []], + ["VK_BACK_SPACE", [["span", 1], [".span", 1], ["#span", 1]]], + ["VK_BACK_SPACE", [["span", 1], [".span", 1], ["#span", 1]]], + ["VK_BACK_SPACE", [["span", 1], [".span", 1], ["#span", 1]]], + ["VK_BACK_SPACE", []], + // Test that mixed tags, classes and ids are grouped by types, sorted by + // count and alphabetical order + ["b", [ + ["button", 3], + ["body", 1], + [".bc", 3], + [".ba", 1], + [".bb", 1], + ["#ba", 1], + ["#bb", 1], + ["#bc", 1] + ]], +]; + +const TEST_URL = `<span class="span" id="span"> + <div class="div" id="div"></div> + </span> + <button class="ba bc" id="bc"></button> + <button class="bb bc" id="bb"></button> + <button class="bc" id="ba"></button>`; + +add_task(function* () { + let {inspector} = yield openInspectorForURL("data:text/html;charset=utf-8," + + encodeURI(TEST_URL)); + + let searchBox = inspector.panelWin.document.getElementById( + "inspector-searchbox"); + let popup = inspector.searchSuggestions.searchPopup; + + yield focusSearchBoxUsingShortcut(inspector.panelWin); + + for (let [key, expectedSuggestions] of KEY_STATES) { + info("pressing key " + key + " to get suggestions " + + JSON.stringify(expectedSuggestions)); + + let onCommand = once(searchBox, "input", true); + EventUtils.synthesizeKey(key, {}, inspector.panelWin); + yield onCommand; + + info("Waiting for the suggestions to be retrieved"); + yield inspector.searchSuggestions._lastQuery; + + let actualSuggestions = popup.getItems(); + is(popup.isOpen ? actualSuggestions.length : 0, expectedSuggestions.length, + "There are expected number of suggestions"); + actualSuggestions.reverse(); + + for (let i = 0; i < expectedSuggestions.length; i++) { + is(expectedSuggestions[i][0], actualSuggestions[i].label, + "The suggestion at " + i + "th index is correct."); + is(expectedSuggestions[i][1] || 1, actualSuggestions[i].count, + "The count for suggestion at " + i + "th index is correct."); + } + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_search_keyboard_trap.js b/devtools/client/inspector/test/browser_inspector_search_keyboard_trap.js new file mode 100644 index 000000000..391d812a2 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_search_keyboard_trap.js @@ -0,0 +1,94 @@ +/* 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 ability to tab to and away from inspector search using keyboard. + +const TEST_URL = URL_ROOT + "doc_inspector_search.html"; + +/** + * Test data has the format of: + * { + * desc {String} description for better logging + * focused {Boolean} flag, indicating if search box contains focus + * keys: {Array} list of keys that include key code and optional + * event data (shiftKey, etc) + * } + * + */ +const TEST_DATA = [ + { + desc: "Move focus to a next focusable element", + focused: false, + keys: [ + { + key: "VK_TAB", + options: { } + } + ] + }, + { + desc: "Move focus back to searchbox", + focused: true, + keys: [ + { + key: "VK_TAB", + options: { shiftKey: true } + } + ] + }, + { + desc: "Open popup and then tab away (2 times) to the a next focusable " + + "element", + focused: false, + keys: [ + { + key: "d", + options: { } + }, + { + key: "VK_TAB", + options: { } + }, + { + key: "VK_TAB", + options: { } + } + ] + }, + { + desc: "Move focus back to searchbox", + focused: true, + keys: [ + { + key: "VK_TAB", + options: { shiftKey: true } + } + ] + } +]; + +add_task(function* () { + let { inspector } = yield openInspectorForURL(TEST_URL); + let { searchBox } = inspector; + let doc = inspector.panelDoc; + + yield selectNode("#b1", inspector); + yield focusSearchBoxUsingShortcut(inspector.panelWin); + + // Ensure a searchbox is focused. + ok(containsFocus(doc, searchBox), "Focus is in a searchbox"); + + for (let { desc, focused, keys } of TEST_DATA) { + info(desc); + for (let { key, options } of keys) { + let done = !focused ? + inspector.searchSuggestions.once("processing-done") : Promise.resolve(); + EventUtils.synthesizeKey(key, options); + yield done; + } + is(containsFocus(doc, searchBox), focused, "Focus is set correctly"); + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_select-docshell.js b/devtools/client/inspector/test/browser_inspector_select-docshell.js new file mode 100644 index 000000000..6a801fdea --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_select-docshell.js @@ -0,0 +1,86 @@ +/* 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 {inspector, toolbox, testActor} = yield openInspectorForURL(URL); + + // Verify we are on the top level document + ok((yield testActor.hasNode("#top")), + "We have the test node on the top level document"); + + assertMarkupViewIsLoaded(inspector); + + // Verify that the frame map button is empty at the moment. + let btn = toolbox.doc.getElementById("command-button-frames"); + ok(!btn.firstChild, "The frame list button doesn't have any children"); + + // Open frame menu and wait till it's available on the screen. + let menu = toolbox.showFramesMenu({target: btn}); + yield once(menu, "open"); + + // Verify that the menu is popuplated. + let frames = menu.items.slice(); + is(frames.length, 2, "We have both frames in the menu"); + + frames.sort(function (a, b) { + return a.label.localeCompare(b.label); + }); + + is(frames[0].label, FrameURL, "Got top level document in the list"); + is(frames[1].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); + }); + + // Only select the iframe after we are able to select an element from the top + // level document. + let newRoot = inspector.once("new-root"); + yield selectNode("#top", inspector); + info("Select the iframe"); + frames[0].click(); + + yield willNavigate; + yield newRoot; + + info("Navigation to the iframe is done, the inspector should be back up"); + + // Verify we are on page one + ok(!(yield testActor.hasNode("iframe")), + "We not longer have access to the top frame elements"); + ok((yield testActor.hasNode("#frame")), + "But now have direct access to the iframe elements"); + + // On page 2 load, verify we have the right content + assertMarkupViewIsLoaded(inspector); + + yield selectNode("#frame", inspector); + + 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/devtools/client/inspector/test/browser_inspector_select-last-selected.js b/devtools/client/inspector/test/browser_inspector_select-last-selected.js new file mode 100644 index 000000000..0f2050327 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_select-last-selected.js @@ -0,0 +1,95 @@ +/* -*- 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"; + +requestLongerTimeout(2); + +// Checks that the expected default node is selected after a page navigation or +// a reload. +var PAGE_1 = URL_ROOT + "doc_inspector_select-last-selected-01.html"; +var PAGE_2 = 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. +var 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, toolbox, testActor } = 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); + + let nodeFront = yield getNodeFront(selectedNode, inspector); + ok(nodeFront, "Got expected node front"); + is(inspector.selection.nodeFront, nodeFront, + selectedNode + " is selected after navigation."); + } + + function* navigateToAndWaitForNewRoot(url) { + info("Navigating and waiting for new-root event after navigation."); + + let current = yield testActor.eval("location.href"); + if (url == current) { + info("Reloading page."); + let markuploaded = inspector.once("markuploaded"); + let onNewRoot = inspector.once("new-root"); + let onUpdated = inspector.once("inspector-updated"); + + let activeTab = toolbox.target.activeTab; + yield activeTab.reload(); + info("Waiting for inspector to be ready."); + yield markuploaded; + yield onNewRoot; + yield onUpdated; + } else { + yield navigateTo(inspector, url); + } + } +}); diff --git a/devtools/client/inspector/test/browser_inspector_sidebarstate.js b/devtools/client/inspector/test/browser_inspector_sidebarstate.js new file mode 100644 index 000000000..a2bb764c1 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_sidebarstate.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"; + +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"); + + // Finish initialization of the computed panel before + // destroying the toolbox. + yield waitForTick(); + + 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/devtools/client/inspector/test/browser_inspector_switch-to-inspector-on-pick.js b/devtools/client/inspector/test/browser_inspector_switch-to-inspector-on-pick.js new file mode 100644 index 000000000..53b2892ac --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/browser_inspector_textbox-menu.js b/devtools/client/inspector/test/browser_inspector_textbox-menu.js new file mode 100644 index 000000000..74190229f --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_textbox-menu.js @@ -0,0 +1,90 @@ +/* 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 when right-clicking on various text boxes throughout the inspector does use +// the toolbox's context menu (copy/cut/paste/selectAll/Undo). + +add_task(function* () { + yield addTab(`data:text/html;charset=utf-8, + <style>h1 { color: red; }</style> + <h1 id="title">textbox context menu test</h1>`); + let {toolbox, inspector} = yield openInspector(); + yield selectNode("h1", inspector); + + info("Testing the markup-view tagname"); + let container = yield focusNode("h1", inspector); + let tag = container.editor.tag; + tag.focus(); + EventUtils.sendKey("return", inspector.panelWin); + yield checkTextBox(inspector.markup.doc.activeElement, toolbox); + + info("Testing the markup-view attribute"); + EventUtils.sendKey("tab", inspector.panelWin); + yield checkTextBox(inspector.markup.doc.activeElement, toolbox); + + info("Testing the markup-view new attribute"); + // It takes 2 tabs to focus the newAttr field, the first one just moves the cursor to + // the end of the field. + EventUtils.sendKey("tab", inspector.panelWin); + EventUtils.sendKey("tab", inspector.panelWin); + yield checkTextBox(inspector.markup.doc.activeElement, toolbox); + + info("Testing the markup-view textcontent"); + EventUtils.sendKey("tab", inspector.panelWin); + yield checkTextBox(inspector.markup.doc.activeElement, toolbox); + // Blur this last markup-view field, since we're moving on to the rule-view next. + EventUtils.sendKey("escape", inspector.panelWin); + + info("Testing the rule-view selector"); + let ruleView = inspector.ruleview.view; + let cssRuleEditor = getRuleViewRuleEditor(ruleView, 1); + EventUtils.synthesizeMouse(cssRuleEditor.selectorText, 0, 0, {}, inspector.panelWin); + yield checkTextBox(inspector.panelDoc.activeElement, toolbox); + + info("Testing the rule-view property name"); + EventUtils.sendKey("tab", inspector.panelWin); + yield checkTextBox(inspector.panelDoc.activeElement, toolbox); + + info("Testing the rule-view property value"); + EventUtils.sendKey("tab", inspector.panelWin); + yield checkTextBox(inspector.panelDoc.activeElement, toolbox); + + info("Testing the rule-view new property"); + // Tabbing out of the value field triggers a ruleview-changed event that we need to wait + // for. + let onRuleViewChanged = once(ruleView, "ruleview-changed"); + EventUtils.sendKey("tab", inspector.panelWin); + yield onRuleViewChanged; + yield checkTextBox(inspector.panelDoc.activeElement, toolbox); + + info("Switching to the computed-view"); + let onComputedViewReady = inspector.once("boxmodel-view-updated"); + selectComputedView(inspector); + yield onComputedViewReady; + + info("Testing the box-model region"); + let margin = inspector.panelDoc.querySelector(".boxmodel-margin.boxmodel-top > span"); + EventUtils.synthesizeMouseAtCenter(margin, {}, inspector.panelWin); + yield checkTextBox(inspector.panelDoc.activeElement, toolbox); +}); + +function* checkTextBox(textBox, {textBoxContextMenuPopup}) { + is(textBoxContextMenuPopup.state, "closed", "The menu is closed"); + + info("Simulating context click on the textbox and expecting the menu to open"); + let onContextMenu = once(textBoxContextMenuPopup, "popupshown"); + EventUtils.synthesizeMouse(textBox, 2, 2, {type: "contextmenu", button: 2}, + textBox.ownerDocument.defaultView); + yield onContextMenu; + + is(textBoxContextMenuPopup.state, "open", "The menu is now visible"); + + info("Closing the menu"); + let onContextMenuHidden = once(textBoxContextMenuPopup, "popuphidden"); + textBoxContextMenuPopup.hidePopup(); + yield onContextMenuHidden; + + is(textBoxContextMenuPopup.state, "closed", "The menu is closed again"); +} diff --git a/devtools/client/inspector/test/doc_inspector_add_node.html b/devtools/client/inspector/test/doc_inspector_add_node.html new file mode 100644 index 000000000..d024b2a99 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_add_node.html @@ -0,0 +1,22 @@ +<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>Add elements tests</title>
+ <style>
+ body::before {
+ content: "pseudo-element";
+ }
+ </style>
+</head>
+<body>
+ <div id="foo"></div>
+ <svg>
+ <rect x="0" y="0" width="100" height="50"></rect>
+ </svg>
+ <div id="bar">
+ <div id="baz"></div>
+ </div>
+ <iframe src="data:text/html;charset=utf-8,Test iframe content"></iframe>
+</body>
+</html>
diff --git a/devtools/client/inspector/test/doc_inspector_breadcrumbs.html b/devtools/client/inspector/test/doc_inspector_breadcrumbs.html new file mode 100644 index 000000000..fee063611 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_breadcrumbs.html @@ -0,0 +1,75 @@ +<!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> + <article id="i3"> + <link id="i31" /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + <link /> + </article> + <div id='pseudo-container'></div> + <!-- This is a comment node --> + <svg id="vector" viewBox="0 0 10 10"> + <clipPath id="clip"> + <rect id="rectangle" x="0" y="0" width="10" height="5"></rect> + </clipPath> + <circle cx="5" cy="5" r="5" fill="blue" clip-path="url(#clip)"></circle> + </svg> + </body> +</html> diff --git a/devtools/client/inspector/test/doc_inspector_breadcrumbs_visibility.html b/devtools/client/inspector/test/doc_inspector_breadcrumbs_visibility.html new file mode 100644 index 000000000..862f32407 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_breadcrumbs_visibility.html @@ -0,0 +1,22 @@ +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=windows-1252"> + </head> + <body> + <div id="aVeryLongIdToExceedTheBreadcrumbTruncationLimit"> + <div id="anotherVeryLongIdToExceedTheBreadcrumbTruncationLimit"> + <div id="aThirdVeryLongIdToExceedTheTruncationLimit"> + <div id="aFourthOneToExceedTheTruncationLimit"> + <div id="aFifthOneToExceedTheTruncationLimit"> + <div id="aSixthOneToExceedTheTruncationLimit"> + <div id="aSeventhOneToExceedTheTruncationLimit"> + A text node at the end + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </body> +</html> diff --git a/devtools/client/inspector/test/doc_inspector_csp.html b/devtools/client/inspector/test/doc_inspector_csp.html new file mode 100644 index 000000000..49af7e53b --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_csp.html @@ -0,0 +1,10 @@ +<!DOCTYPE html>
+<html>
+ <head>
+ <title>Inspector CSP Test</title>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ This HTTP response has CSP headers.
+ </body>
+</html>
diff --git a/devtools/client/inspector/test/doc_inspector_csp.html^headers^ b/devtools/client/inspector/test/doc_inspector_csp.html^headers^ new file mode 100644 index 000000000..3345a82b8 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_csp.html^headers^ @@ -0,0 +1,2 @@ +Content-Type: text/html; charset=UTF-8
+content-security-policy: default-src 'self'; connect-src 'self'; script-src 'self'; style-src 'self';
diff --git a/devtools/client/inspector/test/doc_inspector_delete-selected-node-01.html b/devtools/client/inspector/test/doc_inspector_delete-selected-node-01.html new file mode 100644 index 000000000..70edbd936 --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_delete-selected-node-02.html b/devtools/client/inspector/test/doc_inspector_delete-selected-node-02.html new file mode 100644 index 000000000..0749b064a --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_delete-selected-node-02.html @@ -0,0 +1,20 @@ +<!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> + <div id="deleteToMakeSingleTextNode"> + 1 + <b id="deleteWithNonElement">Delete me and select the non-element node</b> + 2 + </div> +</body> +</html> diff --git a/devtools/client/inspector/test/doc_inspector_embed.html b/devtools/client/inspector/test/doc_inspector_embed.html new file mode 100644 index 000000000..1d286ade0 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_embed.html @@ -0,0 +1,6 @@ +<!doctype html><html><head><meta charset="UTF-8"></head><body>
+<object>
+ <embed src="doc_inspector_menu.html" type="application/html"
+ width="422" height="258"></embed>
+</object>
+</body></html>
diff --git a/devtools/client/inspector/test/doc_inspector_gcli-inspect-command.html b/devtools/client/inspector/test/doc_inspector_gcli-inspect-command.html new file mode 100644 index 000000000..a7d28828c --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_highlight_after_transition.html b/devtools/client/inspector/test/doc_inspector_highlight_after_transition.html new file mode 100644 index 000000000..b2ba0b066 --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_highlighter-comments.html b/devtools/client/inspector/test/doc_inspector_highlighter-comments.html new file mode 100644 index 000000000..3dedc9f36 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_highlighter-comments.html @@ -0,0 +1,19 @@ +<!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> + Visible text node +</body> +</html> diff --git a/devtools/client/inspector/test/doc_inspector_highlighter-geometry_01.html b/devtools/client/inspector/test/doc_inspector_highlighter-geometry_01.html new file mode 100644 index 000000000..f05f15deb --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_highlighter-geometry_01.html @@ -0,0 +1,90 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>geometry highlighter test page</title> + <style type="text/css"> + html, body { + margin: 0; + padding: 0; + } + + .relative-sized-parent { + position: relative; + border: 2px solid black; + border-radius: 25px; + } + .size { + width: 300px; + height: 300px; + } + + .positioned-child { + position: absolute; + background: #f06; + } + .pos-top-left { + top: 30px; + left: 25%; + } + .pos-bottom-right { + bottom: 10em; + right: -10px; + } + + .inline-positioned { + background: yellow; + } + + #absolute-container { + position: absolute; + top: 50px; + left: 400px; + width: 500px; + height: 400px; + border: 1px solid black; + } + + .absolute-all-4 { + position: absolute; + top: 10px; + left: 10px; + bottom: 200px; + right: 300px; + border: 1px solid red; + } + + .relative { + position: relative; + top: 10%; + left: 50%; + height: 10px; + border: 1px solid blue; + } + + .fixed { + position: fixed; + top: 400px; + left: 0; + width: 50px; + height: 50px; + border-radius: 50%; + background: green; + } + </style> +</head> +<body> + <div id="node1" class="relative-sized-parent size"> + <div id="node2" class="positioned-child pos-top-left pos-bottom-right"> + <div id="node3" class="inline-positioned positioned-child pos-top-left" style="width:50px;height:50px;"></div> + </div> + </div> + + <div id="absolute-container"> + <div class="absolute-all-4"></div> + <div class="relative"></div> + </div> + + <div class="fixed"></div> +</body> +</html> diff --git a/devtools/client/inspector/test/doc_inspector_highlighter-geometry_02.html b/devtools/client/inspector/test/doc_inspector_highlighter-geometry_02.html new file mode 100644 index 000000000..4392c9042 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_highlighter-geometry_02.html @@ -0,0 +1,120 @@ +<!doctype html><html><head><meta charset="UTF-8"></head><body class="header"> + +<style> +.fixed { position: fixed; top: 40px; right: 20px; margin-top: 20px; background: #ccf; } +.fixed-bottom-right { position: fixed; bottom: 4em; right: 25%; margin: 20px; background: #ccf; } + +#absolute-container { position: relative; height: 150px; margin: 20px; } +.absolute { position: absolute; top: 20px; left: 400px; background: #fcc; } +.absolute-bottom-right { position: absolute; bottom: 20px; right: 50px; background: #fcc; } +.absolute-all-4 { position: absolute; top: 100px; bottom: 10px; left: 20px; right: 700px; background: #fcc; } +.absolute-negative { position: absolute; bottom: -25px; background: #fcc; } +.absolute-width-margin { position: absolute; top: 20px; right: 20px; width: 450px; margin: .3em; padding: 10px; border: 2px solid red; box-sizing: border-box; background: #fcc; } + +.relative { position: relative; top: 10px; left: 10px; background: #cfc;} +.relative-inline { position: relative; top: 10px; left: 10px; display: inline; background: #cfc;} + +.static { position: static; top: 10px; left: 10px; background: #fcf; } +.static-size { position: static; top: 10px; left: 10px; width: 300px; height: 100px; background: #fcf; } + +#sticky-container { + margin: 50px; + height: 400px; + width: 400px; + padding: 40px; + overflow: scroll; +} +#sticky-container dl { + margin: 0; + padding: 24px 0 0 0; +} + +#sticky-container dt { + background: #ffc; + border-bottom: 1px solid #989EA4; + border-top: 1px solid #717D85; + color: #FFF; + font: bold 18px/21px Helvetica, Arial, sans-serif; + margin: 0; + padding: 2px 0 0 12px; + position: sticky; + width: 99%; + top: 0px; +} + +#sticky-container dd { + font: bold 20px/45px Helvetica, Arial, sans-serif; + margin: 0; + padding: 0 0 0 12px; + white-space: nowrap; +} + +#sticky-container dd + dd { + border-top: 1px solid #CCC +} +</style> + +<h1>Positioning playground</h1> +<p>A demo of various positioning schemes: <a href="http://dev.w3.org/csswg/css-position/#pos-sch">http://dev.w3.org/csswg/css-position/#pos-sch</a>.</p> +<p>absolute, static, fixed, relative, sticky</p> + +<h2>Absolute positioning</h2> +<div class="absolute"> + Absolute child with no relative parent +</div> +<div id="absolute-container"> + <div class="absolute"> + Absolute child with a relative parent + </div> + <div class="absolute-bottom-right"> + Absolute child with a relative parent, positioned from the bottom right + </div> + <div class="absolute-all-4"> + Absolute child with a relative parent, with all 4 positions + </div> + <div class="absolute-negative"> + Absolute child with a relative parent, with negative positions + </div> + <div class="absolute-width-margin"> + Absolute child with a relative parent, size, margin + </div> +</div> + +<h2>Relative positioning</h2> +<div id="relative-container"> + <div class="relative"> + Relative child + </div> + <div style="width: 100px;"> + <div class="relative-inline"> + Relative inline child, across multiple lines + </div> + </div> + <div style="position:relative;"> + <div class="relative"> + Relative child, in a positioned parent + </div> + </div> +</div> + +<h2>Fixed positioning</h2> +<div id="fixed-container"> + <div class="fixed"> + Fixed child + </div> + <div class="fixed-bottom-right"> + Fixed child, bottom right + </div> +</div> + +<h2>Static positioning</h2> +<div id="static-container"> + <div class="static"> + Static child with no width/height + </div> + <div class="static-size"> + Static child with width/height + </div> +</div> + +</body></html>
\ No newline at end of file diff --git a/devtools/client/inspector/test/doc_inspector_highlighter.html b/devtools/client/inspector/test/doc_inspector_highlighter.html new file mode 100644 index 000000000..376a9c714 --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_highlighter_csstransform.html b/devtools/client/inspector/test/doc_inspector_highlighter_csstransform.html new file mode 100644 index 000000000..cfa2761d7 --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_highlighter_dom.html b/devtools/client/inspector/test/doc_inspector_highlighter_dom.html new file mode 100644 index 000000000..fab0c8803 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_highlighter_dom.html @@ -0,0 +1,20 @@ +<!DOCTYPE html>
+<html>
+<body>
+
+<p>Hello World!</p>
+
+<div id="complex-div">
+ <div id="simple-div1">
+ <p id="useless-para">The DOM is very useful! <em>#useless-para</em></p>
+ <p id="useful-para">This example is <b id="bold">really</b> useful. <em>#useful-para</em></p>
+ </div>
+
+ <div id="simple-div2">
+ <p id="another">This is another node. You won't reach this in my test.</p>
+ <p id="ahoy">Ahoy! How you doin' Capn'? <em>#ahoy</em></p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/devtools/client/inspector/test/doc_inspector_highlighter_inline.html b/devtools/client/inspector/test/doc_inspector_highlighter_inline.html new file mode 100644 index 000000000..e1aa5bb1f --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_highlighter_rect.html b/devtools/client/inspector/test/doc_inspector_highlighter_rect.html new file mode 100644 index 000000000..4d23d52fd --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_highlighter_rect_iframe.html b/devtools/client/inspector/test/doc_inspector_highlighter_rect_iframe.html new file mode 100644 index 000000000..d59050f69 --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_highlighter_xbl.xul b/devtools/client/inspector/test/doc_inspector_highlighter_xbl.xul new file mode 100644 index 000000000..8cbf990ea --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_highlighter_xbl.xul @@ -0,0 +1,9 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Test that the picker works correctly with XBL anonymous nodes" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<scale id="scale" style="background:red"/> + +</window> diff --git a/devtools/client/inspector/test/doc_inspector_infobar.html b/devtools/client/inspector/test/doc_inspector_infobar.html new file mode 100644 index 000000000..137b3487f --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_infobar_01.html b/devtools/client/inspector/test/doc_inspector_infobar_01.html new file mode 100644 index 000000000..a0c42ee38 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_infobar_01.html @@ -0,0 +1,44 @@ +<!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; + } + + svg { + width: 10px; + height: 10px; + } + </style> + </head> + <body> + <div id="vertical">Vertical</div> + <div id="top" class="class1 class2">Top</div> + <div id="bottom">Bottom</div> + <svg viewBox="0 0 10 10"> + <clipPath id="clip"> + <rect x="0" y="0" width="10" height="5"></rect> + </clipPath> + <circle cx="5" cy="5" r="5" fill="blue" clip-path="url(#clip)"></circle> + </svg> + </body> + </html> diff --git a/devtools/client/inspector/test/doc_inspector_infobar_02.html b/devtools/client/inspector/test/doc_inspector_infobar_02.html new file mode 100644 index 000000000..ed1843f8d --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_infobar_03.html b/devtools/client/inspector/test/doc_inspector_infobar_03.html new file mode 100644 index 000000000..a9aa05fa0 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_infobar_03.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + + <style> + body { + height: 300vh; + } + </style> + </head> + <body> + </body> + </html> diff --git a/devtools/client/inspector/test/doc_inspector_infobar_textnode.html b/devtools/client/inspector/test/doc_inspector_infobar_textnode.html new file mode 100644 index 000000000..2370708f4 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_infobar_textnode.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> +</head> +<body> + <div id="textnode-container"> + text + <span>content</span> + <span>content</span> + text + </div> +</body> +</html> diff --git a/devtools/client/inspector/test/doc_inspector_long-divs.html b/devtools/client/inspector/test/doc_inspector_long-divs.html new file mode 100644 index 000000000..52d6343aa --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_long-divs.html @@ -0,0 +1,104 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Inspector Long Div Listing</title> + <style> + div { + background-color: #0002; + padding-left: 1em; + } + </style> +</head> +<body> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div id="focus-here">focus here</div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div> + <div> + <div> + <div> + <div id="zoom-here">zoom-here</div> + </div> + </div> + </div> + </div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + +</body> +</html> diff --git a/devtools/client/inspector/test/doc_inspector_menu.html b/devtools/client/inspector/test/doc_inspector_menu.html new file mode 100644 index 000000000..862a34579 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_menu.html @@ -0,0 +1,29 @@ +<!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 class="duplicate">This will be duplicated</p> + <p id="delete">This has to be deleted</p> + <img id="copyimage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==" /> + <div id="hiddenElement" style="display: none;"> + <p id="nestedHiddenElement">Visible element nested inside a non-visible element</p> + </div> + <p id="console-var">Paragraph for testing console variables</p> + <p id="console-var-multi">Paragraph for testing multiple console variables</p> + <p id="attributes" data-edit="original" data-remove="thing">Attributes are going to be changed here</p> + </div> + </body> +</html> diff --git a/devtools/client/inspector/test/doc_inspector_outerhtml.html b/devtools/client/inspector/test/doc_inspector_outerhtml.html new file mode 100644 index 000000000..cc400674d --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_outerhtml.html @@ -0,0 +1,11 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Inspector Copy OuterHTML Test</title> +</head> +<body> + <!-- Comment --> + <div><p>Test copy OuterHTML</p></div> +</body> +</html> diff --git a/devtools/client/inspector/test/doc_inspector_remove-iframe-during-load.html b/devtools/client/inspector/test/doc_inspector_remove-iframe-during-load.html new file mode 100644 index 000000000..25454e122 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_remove-iframe-during-load.html @@ -0,0 +1,45 @@ +<!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"> + "use strict"; + + 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 () { + let newIframe = document.createElement("iframe"); + document.body.appendChild(newIframe); + newIframe.remove(); + yay.textContent = "DOMContentLoaded"; + }); + + // Create/remove an iframe on window load. + window.addEventListener("load", function () { + let newIframe = document.createElement("iframe"); + document.body.appendChild(newIframe); + newIframe.remove(); + yay.textContent = "load"; + + // Dispatch the done event. + window.dispatchEvent(event); + }); + </script> +</body> +</html> diff --git a/devtools/client/inspector/test/doc_inspector_search-reserved.html b/devtools/client/inspector/test/doc_inspector_search-reserved.html new file mode 100644 index 000000000..15cf8c3af --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_search-reserved.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Inspector Search Box Reserved Character Test</title> +</head> +<body> + <div id="d1.d2">Hi, I'm an id that contains a CSS reserved character</div> + <div class="c1.c2">Hi, a class that contains a CSS reserved character</div> +</body> +</html> diff --git a/devtools/client/inspector/test/doc_inspector_search-suggestions.html b/devtools/client/inspector/test/doc_inspector_search-suggestions.html new file mode 100644 index 000000000..a84a2e3d4 --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_search-svg.html b/devtools/client/inspector/test/doc_inspector_search-svg.html new file mode 100644 index 000000000..f762b2288 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_search-svg.html @@ -0,0 +1,16 @@ +<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>Inspector SVG Search Box Test</title>
+</head>
+<body>
+ <div class="class1"></div>
+ <svg>
+ <clipPath>
+ <rect x="0" y="0" width="10" height="5"></rect>
+ </clipPath>
+ <circle cx="0" cy="0" r="50" class="class2" />
+ </svg>
+</body>
+</html>
diff --git a/devtools/client/inspector/test/doc_inspector_search.html b/devtools/client/inspector/test/doc_inspector_search.html new file mode 100644 index 000000000..262eb0be6 --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_select-last-selected-01.html b/devtools/client/inspector/test/doc_inspector_select-last-selected-01.html new file mode 100644 index 000000000..fbe1251cb --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_select-last-selected-02.html b/devtools/client/inspector/test/doc_inspector_select-last-selected-02.html new file mode 100644 index 000000000..2fbef312c --- /dev/null +++ b/devtools/client/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/devtools/client/inspector/test/doc_inspector_svg.svg b/devtools/client/inspector/test/doc_inspector_svg.svg new file mode 100644 index 000000000..75154dcf3 --- /dev/null +++ b/devtools/client/inspector/test/doc_inspector_svg.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <circle r="5"/> +</svg> diff --git a/devtools/client/inspector/test/head.js b/devtools/client/inspector/test/head.js new file mode 100644 index 000000000..f251568df --- /dev/null +++ b/devtools/client/inspector/test/head.js @@ -0,0 +1,732 @@ +/* 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/. */ +/* eslint no-unused-vars: [2, {"vars": "local"}] */ +/* import-globals-from ../../framework/test/shared-head.js */ +/* import-globals-from ../../commandline/test/helpers.js */ +/* import-globals-from ../../shared/test/test-actor-registry.js */ +/* import-globals-from ../../inspector/test/shared-head.js */ +"use strict"; + +// Load the shared-head file first. +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", + this); + +// Services.prefs.setBoolPref("devtools.debugger.log", true); +// SimpleTest.registerCleanupFunction(() => { +// Services.prefs.clearUserPref("devtools.debugger.log"); +// }); + +// Import the GCLI test helper +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/commandline/test/helpers.js", + this); + +// Import helpers registering the test-actor in remote targets +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/shared/test/test-actor-registry.js", + this); + +// Import helpers for the inspector that are also shared with others +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this); + +const {LocalizationHelper} = require("devtools/shared/l10n"); +const INSPECTOR_L10N = + new LocalizationHelper("devtools/client/locales/inspector.properties"); + +flags.testing = true; +registerCleanupFunction(() => { + flags.testing = false; +}); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.inspector.activeSidebar"); +}); + +registerCleanupFunction(function* () { + // 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); +}); + +var navigateTo = Task.async(function* (inspector, url) { + let markuploaded = inspector.once("markuploaded"); + let onNewRoot = inspector.once("new-root"); + let onUpdated = inspector.once("inspector-updated"); + + info("Navigating to: " + url); + let activeTab = inspector.toolbox.target.activeTab; + yield activeTab.navigateTo(url); + + info("Waiting for markup view to load after navigation."); + yield markuploaded; + + info("Waiting for new root."); + yield onNewRoot; + + info("Waiting for inspector to update after new-root event."); + yield onUpdated; +}); + +/** + * Start the element picker and focus the content window. + * @param {Toolbox} toolbox + * @param {Boolean} skipFocus - Allow tests to bypass the focus event. + */ +var startPicker = Task.async(function* (toolbox, skipFocus) { + info("Start the element picker"); + toolbox.win.focus(); + yield toolbox.highlighterUtils.startPicker(); + if (!skipFocus) { + // By default make sure the content window is focused since the picker may not focus + // the content window by default. + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + content.focus(); + }); + } +}); + +/** + * 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"); +} + +/** + * Select node for a given selector, make it focusable and set focus in its + * container element. + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector The current inspector-panel instance. + * @return {MarkupContainer} + */ +function* focusNode(selector, inspector) { + getContainerForNodeFront(inspector.walker.rootNode, inspector).elt.focus(); + let nodeFront = yield getNodeFront(selector, inspector); + let container = getContainerForNodeFront(nodeFront, inspector); + yield selectNode(nodeFront, inspector); + EventUtils.sendKey("return", inspector.panelWin); + return container; +} + +/** + * Set the inspector's current selection to null so that no node is selected + * + * @param {InspectorPanel} inspector + * The instance of InspectorPanel currently loaded in the toolbox + * @return a promise that resolves when the inspector is updated + */ +function clearCurrentNodeSelection(inspector) { + info("Clearing the current selection"); + let updated = inspector.once("inspector-updated"); + inspector.selection.setNodeFront(null); + return updated; +} + +/** + * Open the inspector in a tab with given URL. + * @param {string} url The URL to open. + * @param {String} hostType Optional hostType, as defined in Toolbox.HostType + * @return A promise that is resolved once the tab and inspector have loaded + * with an object: { tab, toolbox, inspector }. + */ +var openInspectorForURL = Task.async(function* (url, hostType) { + let tab = yield addTab(url); + let { inspector, toolbox, testActor } = yield openInspector(hostType); + return { tab, inspector, toolbox, testActor }; +}); + +function getActiveInspector() { + let target = TargetFactory.forTab(gBrowser.selectedTab); + return gDevTools.getToolbox(target).getPanel("inspector"); +} + +/** + * Right click on a node in the test page and click on the inspect menu item. + * @param {TestActor} + * @param {String} selector The selector for the node to click on in the page. + * @return {Promise} Resolves to the inspector when it has opened and is updated + */ +var clickOnInspectMenuItem = Task.async(function* (testActor, selector) { + info("Showing the contextual menu on node " + selector); + let contentAreaContextMenu = document.querySelector( + "#contentAreaContextMenu"); + let contextOpened = once(contentAreaContextMenu, "popupshown"); + + yield testActor.synthesizeMouse({ + selector: selector, + center: true, + options: {type: "contextmenu", button: 2} + }); + + yield contextOpened; + + info("Triggering the inspect action"); + yield gContextMenu.inspectNode(); + + info("Hiding the menu"); + let contextClosed = once(contentAreaContextMenu, "popuphidden"); + contentAreaContextMenu.hidePopup(); + yield contextClosed; + + return getActiveInspector(); +}); + +/** + * 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 + * @return {Promise} Resolves when the inspector is updated with the new node + */ +var getNodeFrontInFrame = Task.async(function* (selector, frameSelector, + inspector) { + let iframe = yield getNodeFront(frameSelector, inspector); + let {nodes} = yield inspector.walker.children(iframe); + return inspector.walker.querySelector(nodes[0], selector); +}); + +var focusSearchBoxUsingShortcut = Task.async(function* (panelWin, callback) { + info("Focusing search box"); + let searchBox = panelWin.document.getElementById("inspector-searchbox"); + let focused = once(searchBox, "focus"); + + panelWin.focus(); + + synthesizeKeyShortcut(INSPECTOR_L10N.getStr("inspector.searchHTML.key")); + + 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} + */ +var 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 + */ +var 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. + */ +var 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; +}); + +/** + * 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 = defer(); + + // Find another element to mouseover over in order to leave the markup-view + let btn = inspector.toolbox.doc.querySelector("#toolbox-controls"); + + EventUtils.synthesizeMouseAtCenter(btn, {type: "mousemove"}, + inspector.toolbox.win); + executeSoon(def.resolve); + + return def.promise; +} + +/** + * Dispatch the copy event on the given element + */ +function fireCopyEvent(element) { + let evt = element.ownerDocument.createEvent("Event"); + evt.initEvent("copy", true, true); + element.dispatchEvent(evt); +} + +/** + * 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; +} + +/** + * 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); + } + + info("Retrieving front for doctype node"); + let {nodes} = yield inspector.walker.children(inspector.walker.rootNode); + return nodes[0]; +} + +/** + * A simple polling helper that executes a given function until it returns true. + * @param {Function} check A generator function that is expected to return true at some + * stage. + * @param {String} desc A text description to be displayed when the polling starts. + * @param {Number} attemptes Optional number of times we poll. Defaults to 10. + * @param {Number} timeBetweenAttempts Optional time to wait between each attempt. + * Defaults to 200ms. + */ +function* poll(check, desc, attempts = 10, timeBetweenAttempts = 200) { + info(desc); + + for (let i = 0; i < attempts; i++) { + if (yield check()) { + return; + } + yield new Promise(resolve => setTimeout(resolve, timeBetweenAttempts)); + } + + throw new Error(`Timeout while: ${desc}`); +} + +/** + * Encapsulate some common operations for highlighter's tests, to have + * the tests cleaner, without exposing directly `inspector`, `highlighter`, and + * `testActor` if not needed. + * + * @param {String} + * The highlighter's type + * @return + * A generator function that takes an object with `inspector` and `testActor` + * properties. (see `openInspector`) + */ +const getHighlighterHelperFor = (type) => Task.async( + function* ({inspector, testActor}) { + let front = inspector.inspector; + let highlighter = yield front.getHighlighterByType(type); + + let prefix = ""; + + // Internals for mouse events + let prevX, prevY; + + // Highlighted node + let highlightedNode = null; + + return { + set prefix(value) { + prefix = value; + }, + + get highlightedNode() { + if (!highlightedNode) { + return null; + } + + return { + getComputedStyle: function* (options = {}) { + return yield inspector.pageStyle.getComputed( + highlightedNode, options); + } + }; + }, + + show: function* (selector = ":root", options) { + highlightedNode = yield getNodeFront(selector, inspector); + return yield highlighter.show(highlightedNode, options); + }, + + hide: function* () { + yield highlighter.hide(); + }, + + isElementHidden: function* (id) { + return (yield testActor.getHighlighterNodeAttribute( + prefix + id, "hidden", highlighter)) === "true"; + }, + + getElementTextContent: function* (id) { + return yield testActor.getHighlighterNodeTextContent( + prefix + id, highlighter); + }, + + getElementAttribute: function* (id, name) { + return yield testActor.getHighlighterNodeAttribute( + prefix + id, name, highlighter); + }, + + waitForElementAttributeSet: function* (id, name) { + yield poll(function* () { + let value = yield testActor.getHighlighterNodeAttribute( + prefix + id, name, highlighter); + return !!value; + }, `Waiting for element ${id} to have attribute ${name} set`); + }, + + waitForElementAttributeRemoved: function* (id, name) { + yield poll(function* () { + let value = yield testActor.getHighlighterNodeAttribute( + prefix + id, name, highlighter); + return !value; + }, `Waiting for element ${id} to have attribute ${name} removed`); + }, + + synthesizeMouse: function* (options) { + options = Object.assign({selector: ":root"}, options); + yield testActor.synthesizeMouse(options); + }, + + // This object will synthesize any "mouse" prefixed event to the + // `testActor`, using the name of method called as suffix for the + // event's name. + // If no x, y coords are given, the previous ones are used. + // + // For example: + // mouse.down(10, 20); // synthesize "mousedown" at 10,20 + // mouse.move(20, 30); // synthesize "mousemove" at 20,30 + // mouse.up(); // synthesize "mouseup" at 20,30 + mouse: new Proxy({}, { + get: (target, name) => + function* (x = prevX, y = prevY) { + prevX = x; + prevY = y; + yield testActor.synthesizeMouse({ + selector: ":root", x, y, options: {type: "mouse" + name}}); + } + }), + + reflow: function* () { + yield testActor.reflow(); + }, + + finalize: function* () { + highlightedNode = null; + yield highlighter.finalize(); + } + }; + } +); + +// The expand all operation of the markup-view calls itself recursively and +// there's not one event we can wait for to know when it's done so use this +// helper function to wait until all recursive children updates are done. +function* waitForMultipleChildrenUpdates(inspector) { + // As long as child updates are queued up while we wait for an update already + // wait again + if (inspector.markup._queuedChildUpdates && + inspector.markup._queuedChildUpdates.size) { + yield waitForChildrenUpdated(inspector); + return yield waitForMultipleChildrenUpdates(inspector); + } + return null; +} + +/** + * Using the markupview's _waitForChildren function, wait for all queued + * children updates to be handled. + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @return a promise that resolves when all queued children updates have been + * handled + */ +function waitForChildrenUpdated({markup}) { + info("Waiting for queued children updates to be handled"); + let def = defer(); + markup._waitForChildren().then(() => { + executeSoon(def.resolve); + }); + return def.promise; +} + +/** + * Wait for the toolbox to emit the styleeditor-selected event and when done + * wait for the stylesheet identified by href to be loaded in the stylesheet + * editor + * + * @param {Toolbox} toolbox + * @param {String} href + * Optional, if not provided, wait for the first editor to be ready + * @return a promise that resolves to the editor when the stylesheet editor is + * ready + */ +function waitForStyleEditor(toolbox, href) { + let def = defer(); + + info("Waiting for the toolbox to switch to the styleeditor"); + toolbox.once("styleeditor-selected").then(() => { + let panel = toolbox.getCurrentPanel(); + ok(panel && panel.UI, "Styleeditor panel switched to front"); + + // A helper that resolves the promise once it receives an editor that + // matches the expected href. Returns false if the editor was not correct. + let gotEditor = (event, editor) => { + let currentHref = editor.styleSheet.href; + if (!href || (href && currentHref.endsWith(href))) { + info("Stylesheet editor selected"); + panel.UI.off("editor-selected", gotEditor); + + editor.getSourceEditor().then(sourceEditor => { + info("Stylesheet editor fully loaded"); + def.resolve(sourceEditor); + }); + + return true; + } + + info("The editor was incorrect. Waiting for editor-selected event."); + return false; + }; + + // The expected editor may already be selected. Check the if the currently + // selected editor is the expected one and if not wait for an + // editor-selected event. + if (!gotEditor("styleeditor-selected", panel.UI.selectedEditor)) { + // The expected editor is not selected (yet). Wait for it. + panel.UI.on("editor-selected", gotEditor); + } + }); + + return def.promise; +} + +/** + * Checks if document's active element is within the given element. + * @param {HTMLDocument} doc document with active element in question + * @param {DOMNode} container element tested on focus containment + * @return {Boolean} + */ +function containsFocus(doc, container) { + let elm = doc.activeElement; + while (elm) { + if (elm === container) { + return true; + } + elm = elm.parentNode; + } + return false; +} + +/** + * Listen for a new tab to open and return a promise that resolves when one + * does and completes the load event. + * + * @return a promise that resolves to the tab object + */ +var waitForTab = Task.async(function* () { + info("Waiting for a tab to open"); + yield once(gBrowser.tabContainer, "TabOpen"); + let tab = gBrowser.selectedTab; + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + info("The tab load completed"); + return tab; +}); + +/** + * Simulate the key input for the given input in the window. + * + * @param {String} input + * The string value to input + * @param {Window} win + * The window containing the panel + */ +function synthesizeKeys(input, win) { + for (let key of input.split("")) { + EventUtils.synthesizeKey(key, {}, win); + } +} + +/** + * Given a tooltip object instance (see Tooltip.js), checks if it is set to + * toggle and hover and if so, checks if the given target is a valid hover + * target. This won't actually show the tooltip (the less we interact with XUL + * panels during test runs, the better). + * + * @return a promise that resolves when the answer is known + */ +function isHoverTooltipTarget(tooltip, target) { + if (!tooltip._toggle._baseNode || !tooltip.panel) { + return promise.reject(new Error( + "The tooltip passed isn't set to toggle on hover or is not a tooltip")); + } + return tooltip._toggle.isValidHoverTarget(target); +} + +/** + * Same as isHoverTooltipTarget except that it will fail the test if there is no + * tooltip defined on hover of the given element + * + * @return a promise + */ +function assertHoverTooltipOn(tooltip, element) { + return isHoverTooltipTarget(tooltip, element).then(() => { + ok(true, "A tooltip is defined on hover of the given element"); + }, () => { + ok(false, "No tooltip is defined on hover of the given element"); + }); +} + +/** + * Open the inspector menu and return all of it's items in a flat array + * @param {InspectorPanel} inspector + * @param {Object} options to pass into openMenu + * @return An array of MenuItems + */ +function openContextMenuAndGetAllItems(inspector, options) { + let menu = inspector._openMenu(options); + + // Flatten all menu items into a single array to make searching through it easier + let allItems = [].concat.apply([], menu.items.map(function addItem(item) { + if (item.submenu) { + return addItem(item.submenu.items); + } + return item; + })); + + return allItems; +} + +/** + * Get the rule editor from the rule-view given its index + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {Number} childrenIndex + * The children index of the element to get + * @param {Number} nodeIndex + * The child node index of the element to get + * @return {DOMNode} The rule editor if any at this index + */ +function getRuleViewRuleEditor(view, childrenIndex, nodeIndex) { + return nodeIndex !== undefined ? + view.element.children[childrenIndex].childNodes[nodeIndex]._ruleEditor : + view.element.children[childrenIndex]._ruleEditor; +} + +/** + * Get the text displayed for a given DOM Element's textContent within the + * markup view. + * + * @param {String} selector + * @param {InspectorPanel} inspector + * @return {String} The text displayed in the markup view + */ +function* getDisplayedNodeTextContent(selector, inspector) { + // We have to ensure that the textContent is displayed, for that the DOM + // Element has to be selected in the markup view and to be expanded. + yield selectNode(selector, inspector); + + let container = yield getContainerForSelector(selector, inspector); + yield inspector.markup.expandNode(container.node); + yield waitForMultipleChildrenUpdates(inspector); + if (container) { + let textContainer = container.elt.querySelector("pre"); + return textContainer.textContent; + } + return null; +} diff --git a/devtools/client/inspector/test/shared-head.js b/devtools/client/inspector/test/shared-head.js new file mode 100644 index 000000000..13eeca0f7 --- /dev/null +++ b/devtools/client/inspector/test/shared-head.js @@ -0,0 +1,186 @@ +/* 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"; + +/* eslint no-unused-vars: [2, {"vars": "local"}] */ +/* globals registerTestActor, getTestActor, Task, openToolboxForTab, gBrowser */ + +// This file contains functions related to the inspector that are also of interest to +// other test directores as well. + +/** + * Open the toolbox, with the inspector tool visible. + * @param {String} hostType Optional hostType, as defined in Toolbox.HostType + * @return a promise that resolves when the inspector is ready + */ +var openInspector = Task.async(function* (hostType) { + info("Opening the inspector"); + + let toolbox = yield openToolboxForTab(gBrowser.selectedTab, "inspector", + hostType); + let inspector = toolbox.getPanel("inspector"); + + if (inspector._updateProgress) { + info("Need to wait for the inspector to update"); + yield inspector.once("inspector-updated"); + } + + info("Waiting for actor features to be detected"); + yield inspector._detectingActorFeatures; + + yield registerTestActor(toolbox.target.client); + let testActor = yield getTestActor(toolbox); + + return {toolbox, inspector, testActor}; +}); + +/** + * Open the toolbox, with the inspector tool visible, and the one of the sidebar + * tabs selected. + * + * @param {String} id + * The ID of the sidebar tab to be opened + * @return a promise that resolves when the inspector is ready and the tab is + * visible and ready + */ +var openInspectorSidebarTab = Task.async(function* (id) { + let {toolbox, inspector, testActor} = yield openInspector(); + + info("Selecting the " + id + " sidebar"); + inspector.sidebar.select(id); + + return { + toolbox, + inspector, + testActor + }; +}); + +/** + * Open the toolbox, with the inspector tool visible, and the rule-view + * sidebar tab selected. + * + * @return a promise that resolves when the inspector is ready and the rule view + * is visible and ready + */ +function openRuleView() { + return openInspectorSidebarTab("ruleview").then(data => { + // Replace the view to use a custom throttle function that can be triggered manually + // through an additional ".flush()" property. + data.inspector.ruleview.view.throttle = manualThrottle(); + + return { + toolbox: data.toolbox, + inspector: data.inspector, + testActor: data.testActor, + view: data.inspector.ruleview.view + }; + }); +} + +/** + * Open the toolbox, with the inspector tool visible, and the computed-view + * sidebar tab selected. + * + * @return a promise that resolves when the inspector is ready and the computed + * view is visible and ready + */ +function openComputedView() { + return openInspectorSidebarTab("computedview").then(data => { + return { + toolbox: data.toolbox, + inspector: data.inspector, + testActor: data.testActor, + view: data.inspector.computedview.computedView + }; + }); +} + +/** + * Select the rule view sidebar tab on an already opened inspector panel. + * + * @param {InspectorPanel} inspector + * The opened inspector panel + * @return {CssRuleView} the rule view + */ +function selectRuleView(inspector) { + inspector.sidebar.select("ruleview"); + return inspector.ruleview.view; +} + +/** + * Select the computed view sidebar tab on an already opened inspector panel. + * + * @param {InspectorPanel} inspector + * The opened inspector panel + * @return {CssComputedView} the computed view + */ +function selectComputedView(inspector) { + inspector.sidebar.select("computedview"); + return inspector.computedview.computedView; +} + +/** + * 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); +} + +/** + * 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 + */ +var 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; +}); + +/** + * Create a throttling function that can be manually "flushed". This is to replace the + * use of the `throttle` function from `devtools/client/inspector/shared/utils.js`, which + * has a setTimeout that can cause intermittents. + * @return {Function} This function has the same function signature as throttle, but + * the property `.flush()` has been added for flushing out any + * throttled calls. + */ +function manualThrottle() { + let calls = []; + + function throttle(func, wait, scope) { + return function () { + let existingCall = calls.find(call => call.func === func); + if (existingCall) { + existingCall.args = arguments; + } else { + calls.push({ func, wait, scope, args: arguments }); + } + }; + } + + throttle.flush = function () { + calls.forEach(({func, scope, args}) => func.apply(scope, args)); + calls = []; + }; + + return throttle; +} |