1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/markupview/test/head.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,383 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +const Cu = Components.utils; 1.9 +let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); 1.10 +let TargetFactory = devtools.TargetFactory; 1.11 +let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {}); 1.12 +let promise = devtools.require("devtools/toolkit/deprecated-sync-thenables"); 1.13 +let {getInplaceEditorForSpan: inplaceEditor} = devtools.require("devtools/shared/inplace-editor"); 1.14 + 1.15 +// All test are asynchronous 1.16 +waitForExplicitFinish(); 1.17 + 1.18 +//Services.prefs.setBoolPref("devtools.dump.emit", true); 1.19 + 1.20 +// Set the testing flag on gDevTools and reset it when the test ends 1.21 +gDevTools.testing = true; 1.22 +registerCleanupFunction(() => gDevTools.testing = false); 1.23 + 1.24 +// Clear preferences that may be set during the course of tests. 1.25 +registerCleanupFunction(() => { 1.26 + Services.prefs.clearUserPref("devtools.inspector.htmlPanelOpen"); 1.27 + Services.prefs.clearUserPref("devtools.inspector.sidebarOpen"); 1.28 + Services.prefs.clearUserPref("devtools.inspector.activeSidebar"); 1.29 + Services.prefs.clearUserPref("devtools.dump.emit"); 1.30 + Services.prefs.clearUserPref("devtools.markup.pagesize"); 1.31 +}); 1.32 + 1.33 +// Auto close the toolbox and close the test tabs when the test ends 1.34 +registerCleanupFunction(() => { 1.35 + try { 1.36 + let target = TargetFactory.forTab(gBrowser.selectedTab); 1.37 + gDevTools.closeToolbox(target); 1.38 + } catch (ex) { 1.39 + dump(ex); 1.40 + } 1.41 + while (gBrowser.tabs.length > 1) { 1.42 + gBrowser.removeCurrentTab(); 1.43 + } 1.44 +}); 1.45 + 1.46 +const TEST_URL_ROOT = "http://mochi.test:8888/browser/browser/devtools/markupview/test/"; 1.47 + 1.48 +/** 1.49 + * Define an async test based on a generator function 1.50 + */ 1.51 +function asyncTest(generator) { 1.52 + return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish); 1.53 +} 1.54 + 1.55 +/** 1.56 + * Add a new test tab in the browser and load the given url. 1.57 + * @param {String} url The url to be loaded in the new tab 1.58 + * @return a promise that resolves to the tab object when the url is loaded 1.59 + */ 1.60 +function addTab(url) { 1.61 + info("Adding a new tab with URL: '" + url + "'"); 1.62 + let def = promise.defer(); 1.63 + 1.64 + let tab = gBrowser.selectedTab = gBrowser.addTab(); 1.65 + gBrowser.selectedBrowser.addEventListener("load", function onload() { 1.66 + gBrowser.selectedBrowser.removeEventListener("load", onload, true); 1.67 + info("URL '" + url + "' loading complete"); 1.68 + waitForFocus(() => { 1.69 + def.resolve(tab); 1.70 + }, content); 1.71 + }, true); 1.72 + content.location = url; 1.73 + 1.74 + return def.promise; 1.75 +} 1.76 + 1.77 +/** 1.78 + * Some tests may need to import one or more of the test helper scripts. 1.79 + * A test helper script is simply a js file that contains common test code that 1.80 + * is either not common-enough to be in head.js, or that is located in a separate 1.81 + * directory. 1.82 + * The script will be loaded synchronously and in the test's scope. 1.83 + * @param {String} filePath The file path, relative to the current directory. 1.84 + * Examples: 1.85 + * - "helper_attributes_test_runner.js" 1.86 + * - "../../../commandline/test/helpers.js" 1.87 + */ 1.88 +function loadHelperScript(filePath) { 1.89 + let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); 1.90 + Services.scriptloader.loadSubScript(testDir + "/" + filePath, this); 1.91 +} 1.92 + 1.93 +/** 1.94 + * Reload the current page 1.95 + * @return a promise that resolves when the inspector has emitted the event 1.96 + * new-root 1.97 + */ 1.98 +function reloadPage(inspector) { 1.99 + info("Reloading the page"); 1.100 + let newRoot = inspector.once("new-root"); 1.101 + content.location.reload(); 1.102 + return newRoot; 1.103 +} 1.104 + 1.105 +/** 1.106 + * Open the toolbox, with the inspector tool visible. 1.107 + * @return a promise that resolves when the inspector is ready 1.108 + */ 1.109 +function openInspector() { 1.110 + info("Opening the inspector panel"); 1.111 + let def = promise.defer(); 1.112 + 1.113 + let target = TargetFactory.forTab(gBrowser.selectedTab); 1.114 + gDevTools.showToolbox(target, "inspector").then(function(toolbox) { 1.115 + info("The toolbox is open"); 1.116 + let inspector = toolbox.getCurrentPanel(); 1.117 + inspector.once("inspector-updated", () => { 1.118 + info("The inspector panel is active and ready"); 1.119 + def.resolve({toolbox: toolbox, inspector: inspector}); 1.120 + }); 1.121 + }).then(null, console.error); 1.122 + 1.123 + return def.promise; 1.124 +} 1.125 + 1.126 +/** 1.127 + * Simple DOM node accesor function that takes either a node or a string css 1.128 + * selector as argument and returns the corresponding node 1.129 + * @param {String|DOMNode} nodeOrSelector 1.130 + * @return {DOMNode} 1.131 + */ 1.132 +function getNode(nodeOrSelector) { 1.133 + info("Getting the node for '" + nodeOrSelector + "'"); 1.134 + return typeof nodeOrSelector === "string" ? 1.135 + content.document.querySelector(nodeOrSelector) : 1.136 + nodeOrSelector; 1.137 +} 1.138 + 1.139 +/** 1.140 + * Set the inspector's current selection to a node or to the first match of the 1.141 + * given css selector 1.142 + * @param {String|DOMNode} nodeOrSelector 1.143 + * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox 1.144 + * @param {String} reason Defaults to "test" which instructs the inspector not to highlight the node upon selection 1.145 + * @return a promise that resolves when the inspector is updated with the new 1.146 + * node 1.147 + */ 1.148 +function selectNode(nodeOrSelector, inspector, reason="test") { 1.149 + info("Selecting the node for '" + nodeOrSelector + "'"); 1.150 + let node = getNode(nodeOrSelector); 1.151 + let updated = inspector.once("inspector-updated"); 1.152 + inspector.selection.setNode(node, reason); 1.153 + return updated; 1.154 +} 1.155 + 1.156 +/** 1.157 + * Get the MarkupContainer object instance that corresponds to the given 1.158 + * HTML node 1.159 + * @param {DOMNode|String} nodeOrSelector The DOM node for which the 1.160 + * container is required 1.161 + * @param {InspectorPanel} inspector The instance of InspectorPanel currently 1.162 + * loaded in the toolbox 1.163 + * @return {MarkupContainer} 1.164 + */ 1.165 +function getContainerForRawNode(nodeOrSelector, {markup}) { 1.166 + let front = markup.walker.frontForRawNode(getNode(nodeOrSelector)); 1.167 + let container = markup.getContainer(front); 1.168 + info("Markup-container object for " + nodeOrSelector + " " + container); 1.169 + return container; 1.170 +} 1.171 + 1.172 +/** 1.173 + * Using the markupview's _waitForChildren function, wait for all queued 1.174 + * children updates to be handled. 1.175 + * @param {InspectorPanel} inspector The instance of InspectorPanel currently 1.176 + * loaded in the toolbox 1.177 + * @return a promise that resolves when all queued children updates have been 1.178 + * handled 1.179 + */ 1.180 +function waitForChildrenUpdated({markup}) { 1.181 + info("Waiting for queued children updates to be handled"); 1.182 + let def = promise.defer(); 1.183 + markup._waitForChildren().then(() => { 1.184 + executeSoon(def.resolve); 1.185 + }); 1.186 + return def.promise; 1.187 +} 1.188 + 1.189 +/** 1.190 + * Simulate a mouse-over on the markup-container (a line in the markup-view) 1.191 + * that corresponds to the node or selector passed. 1.192 + * @param {String|DOMNode} nodeOrSelector 1.193 + * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox 1.194 + * @return a promise that resolves when the container is hovered and the higlighter 1.195 + * is shown on the corresponding node 1.196 + */ 1.197 +function hoverContainer(nodeOrSelector, inspector) { 1.198 + info("Hovering over the markup-container for node " + nodeOrSelector); 1.199 + let highlit = inspector.toolbox.once("node-highlight"); 1.200 + let container = getContainerForRawNode(getNode(nodeOrSelector), inspector); 1.201 + EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousemove"}, 1.202 + inspector.markup.doc.defaultView); 1.203 + return highlit; 1.204 +} 1.205 + 1.206 +/** 1.207 + * Simulate a click on the markup-container (a line in the markup-view) 1.208 + * that corresponds to the node or selector passed. 1.209 + * @param {String|DOMNode} nodeOrSelector 1.210 + * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox 1.211 + * @return a promise that resolves when the node has been selected. 1.212 + */ 1.213 +function clickContainer(nodeOrSelector, inspector) { 1.214 + info("Clicking on the markup-container for node " + nodeOrSelector); 1.215 + let updated = inspector.once("inspector-updated"); 1.216 + let container = getContainerForRawNode(getNode(nodeOrSelector), inspector); 1.217 + EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousedown"}, 1.218 + inspector.markup.doc.defaultView); 1.219 + EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mouseup"}, 1.220 + inspector.markup.doc.defaultView); 1.221 + return updated; 1.222 +} 1.223 + 1.224 +/** 1.225 + * Checks if the highlighter is visible currently 1.226 + * @return {Boolean} 1.227 + */ 1.228 +function isHighlighterVisible() { 1.229 + let highlighter = gBrowser.selectedBrowser.parentNode 1.230 + .querySelector(".highlighter-container .box-model-root"); 1.231 + return highlighter && !highlighter.hasAttribute("hidden"); 1.232 +} 1.233 + 1.234 +/** 1.235 + * Simulate the mouse leaving the markup-view area 1.236 + * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox 1.237 + * @return a promise when done 1.238 + */ 1.239 +function mouseLeaveMarkupView(inspector) { 1.240 + info("Leaving the markup-view area"); 1.241 + let def = promise.defer(); 1.242 + 1.243 + // Find another element to mouseover over in order to leave the markup-view 1.244 + let btn = inspector.toolbox.doc.querySelector(".toolbox-dock-button"); 1.245 + 1.246 + EventUtils.synthesizeMouseAtCenter(btn, {type: "mousemove"}, 1.247 + inspector.toolbox.doc.defaultView); 1.248 + executeSoon(def.resolve); 1.249 + 1.250 + return def.promise; 1.251 +} 1.252 + 1.253 +/** 1.254 + * Focus a given editable element, enter edit mode, set value, and commit 1.255 + * @param {DOMNode} field The element that gets editable after receiving focus 1.256 + * and <ENTER> keypress 1.257 + * @param {String} value The string value to be set into the edited field 1.258 + * @param {InspectorPanel} inspector The instance of InspectorPanel currently 1.259 + * loaded in the toolbox 1.260 + */ 1.261 +function setEditableFieldValue(field, value, inspector) { 1.262 + field.focus(); 1.263 + EventUtils.sendKey("return", inspector.panelWin); 1.264 + let input = inplaceEditor(field).input; 1.265 + ok(input, "Found editable field for setting value: " + value); 1.266 + input.value = value; 1.267 + EventUtils.sendKey("return", inspector.panelWin); 1.268 +} 1.269 + 1.270 +/** 1.271 + * Focus the new-attribute inplace-editor field of the nodeOrSelector's markup 1.272 + * container, and enters the given text, then wait for it to be applied and the 1.273 + * for the node to mutates (when new attribute(s) is(are) created) 1.274 + * @param {DOMNode|String} nodeOrSelector The node or node selector to edit. 1.275 + * @param {String} text The new attribute text to be entered (e.g. "id='test'") 1.276 + * @param {InspectorPanel} inspector The instance of InspectorPanel currently 1.277 + * loaded in the toolbox 1.278 + * @return a promise that resolves when the node has mutated 1.279 + */ 1.280 +function addNewAttributes(nodeOrSelector, text, inspector) { 1.281 + info("Entering text '" + text + "' in node '" + nodeOrSelector + "''s new attribute field"); 1.282 + 1.283 + let container = getContainerForRawNode(nodeOrSelector, inspector); 1.284 + ok(container, "The container for '" + nodeOrSelector + "' was found"); 1.285 + 1.286 + info("Listening for the markupmutation event"); 1.287 + let nodeMutated = inspector.once("markupmutation"); 1.288 + setEditableFieldValue(container.editor.newAttr, text, inspector); 1.289 + return nodeMutated; 1.290 +} 1.291 + 1.292 +/** 1.293 + * Checks that a node has the given attributes 1.294 + * 1.295 + * @param {DOMNode|String} nodeOrSelector The node or node selector to check. 1.296 + * @param {Object} attrs An object containing the attributes to check. 1.297 + * e.g. {id: "id1", class: "someclass"} 1.298 + * 1.299 + * Note that node.getAttribute() returns attribute values provided by the HTML 1.300 + * parser. The parser only provides unescaped entities so & will return &. 1.301 + */ 1.302 +function assertAttributes(nodeOrSelector, attrs) { 1.303 + let node = getNode(nodeOrSelector); 1.304 + 1.305 + is(node.attributes.length, Object.keys(attrs).length, 1.306 + "Node has the correct number of attributes."); 1.307 + for (let attr in attrs) { 1.308 + is(node.getAttribute(attr), attrs[attr], 1.309 + "Node has the correct " + attr + " attribute."); 1.310 + } 1.311 +} 1.312 + 1.313 +/** 1.314 + * Undo the last markup-view action and wait for the corresponding mutation to 1.315 + * occur 1.316 + * @param {InspectorPanel} inspector The instance of InspectorPanel currently 1.317 + * loaded in the toolbox 1.318 + * @return a promise that resolves when the markup-mutation has been treated or 1.319 + * rejects if no undo action is possible 1.320 + */ 1.321 +function undoChange(inspector) { 1.322 + let canUndo = inspector.markup.undo.canUndo(); 1.323 + ok(canUndo, "The last change in the markup-view can be undone"); 1.324 + if (!canUndo) { 1.325 + return promise.reject(); 1.326 + } 1.327 + 1.328 + let mutated = inspector.once("markupmutation"); 1.329 + inspector.markup.undo.undo(); 1.330 + return mutated; 1.331 +} 1.332 + 1.333 +/** 1.334 + * Redo the last markup-view action and wait for the corresponding mutation to 1.335 + * occur 1.336 + * @param {InspectorPanel} inspector The instance of InspectorPanel currently 1.337 + * loaded in the toolbox 1.338 + * @return a promise that resolves when the markup-mutation has been treated or 1.339 + * rejects if no redo action is possible 1.340 + */ 1.341 +function redoChange(inspector) { 1.342 + let canRedo = inspector.markup.undo.canRedo(); 1.343 + ok(canRedo, "The last change in the markup-view can be redone"); 1.344 + if (!canRedo) { 1.345 + return promise.reject(); 1.346 + } 1.347 + 1.348 + let mutated = inspector.once("markupmutation"); 1.349 + inspector.markup.undo.redo(); 1.350 + return mutated; 1.351 +} 1.352 + 1.353 +/** 1.354 + * Get the selector-search input box from the inspector panel 1.355 + * @return {DOMNode} 1.356 + */ 1.357 +function getSelectorSearchBox(inspector) { 1.358 + return inspector.panelWin.document.getElementById("inspector-searchbox"); 1.359 +} 1.360 + 1.361 +/** 1.362 + * Using the inspector panel's selector search box, search for a given selector. 1.363 + * The selector input string will be entered in the input field and the <ENTER> 1.364 + * keypress will be simulated. 1.365 + * This function won't wait for any events and is not async. It's up to callers 1.366 + * to subscribe to events and react accordingly. 1.367 + */ 1.368 +function searchUsingSelectorSearch(selector, inspector) { 1.369 + info("Entering \"" + selector + "\" into the selector-search input field"); 1.370 + let field = getSelectorSearchBox(inspector); 1.371 + field.focus(); 1.372 + field.value = selector; 1.373 + EventUtils.sendKey("return", inspector.panelWin); 1.374 +} 1.375 + 1.376 +/** 1.377 + * This shouldn't be used in the tests, but is useful when writing new tests or 1.378 + * debugging existing tests in order to introduce delays in the test steps 1.379 + * @param {Number} ms The time to wait 1.380 + * @return A promise that resolves when the time is passed 1.381 + */ 1.382 +function wait(ms) { 1.383 + let def = promise.defer(); 1.384 + content.setTimeout(def.resolve, ms); 1.385 + return def.promise; 1.386 +}