Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | const Cu = Components.utils; |
michael@0 | 6 | let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); |
michael@0 | 7 | let TargetFactory = devtools.TargetFactory; |
michael@0 | 8 | let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {}); |
michael@0 | 9 | let promise = devtools.require("devtools/toolkit/deprecated-sync-thenables"); |
michael@0 | 10 | let {getInplaceEditorForSpan: inplaceEditor} = devtools.require("devtools/shared/inplace-editor"); |
michael@0 | 11 | |
michael@0 | 12 | // All test are asynchronous |
michael@0 | 13 | waitForExplicitFinish(); |
michael@0 | 14 | |
michael@0 | 15 | //Services.prefs.setBoolPref("devtools.dump.emit", true); |
michael@0 | 16 | |
michael@0 | 17 | // Set the testing flag on gDevTools and reset it when the test ends |
michael@0 | 18 | gDevTools.testing = true; |
michael@0 | 19 | registerCleanupFunction(() => gDevTools.testing = false); |
michael@0 | 20 | |
michael@0 | 21 | // Clear preferences that may be set during the course of tests. |
michael@0 | 22 | registerCleanupFunction(() => { |
michael@0 | 23 | Services.prefs.clearUserPref("devtools.inspector.htmlPanelOpen"); |
michael@0 | 24 | Services.prefs.clearUserPref("devtools.inspector.sidebarOpen"); |
michael@0 | 25 | Services.prefs.clearUserPref("devtools.inspector.activeSidebar"); |
michael@0 | 26 | Services.prefs.clearUserPref("devtools.dump.emit"); |
michael@0 | 27 | Services.prefs.clearUserPref("devtools.markup.pagesize"); |
michael@0 | 28 | }); |
michael@0 | 29 | |
michael@0 | 30 | // Auto close the toolbox and close the test tabs when the test ends |
michael@0 | 31 | registerCleanupFunction(() => { |
michael@0 | 32 | try { |
michael@0 | 33 | let target = TargetFactory.forTab(gBrowser.selectedTab); |
michael@0 | 34 | gDevTools.closeToolbox(target); |
michael@0 | 35 | } catch (ex) { |
michael@0 | 36 | dump(ex); |
michael@0 | 37 | } |
michael@0 | 38 | while (gBrowser.tabs.length > 1) { |
michael@0 | 39 | gBrowser.removeCurrentTab(); |
michael@0 | 40 | } |
michael@0 | 41 | }); |
michael@0 | 42 | |
michael@0 | 43 | const TEST_URL_ROOT = "http://mochi.test:8888/browser/browser/devtools/markupview/test/"; |
michael@0 | 44 | |
michael@0 | 45 | /** |
michael@0 | 46 | * Define an async test based on a generator function |
michael@0 | 47 | */ |
michael@0 | 48 | function asyncTest(generator) { |
michael@0 | 49 | return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish); |
michael@0 | 50 | } |
michael@0 | 51 | |
michael@0 | 52 | /** |
michael@0 | 53 | * Add a new test tab in the browser and load the given url. |
michael@0 | 54 | * @param {String} url The url to be loaded in the new tab |
michael@0 | 55 | * @return a promise that resolves to the tab object when the url is loaded |
michael@0 | 56 | */ |
michael@0 | 57 | function addTab(url) { |
michael@0 | 58 | info("Adding a new tab with URL: '" + url + "'"); |
michael@0 | 59 | let def = promise.defer(); |
michael@0 | 60 | |
michael@0 | 61 | let tab = gBrowser.selectedTab = gBrowser.addTab(); |
michael@0 | 62 | gBrowser.selectedBrowser.addEventListener("load", function onload() { |
michael@0 | 63 | gBrowser.selectedBrowser.removeEventListener("load", onload, true); |
michael@0 | 64 | info("URL '" + url + "' loading complete"); |
michael@0 | 65 | waitForFocus(() => { |
michael@0 | 66 | def.resolve(tab); |
michael@0 | 67 | }, content); |
michael@0 | 68 | }, true); |
michael@0 | 69 | content.location = url; |
michael@0 | 70 | |
michael@0 | 71 | return def.promise; |
michael@0 | 72 | } |
michael@0 | 73 | |
michael@0 | 74 | /** |
michael@0 | 75 | * Some tests may need to import one or more of the test helper scripts. |
michael@0 | 76 | * A test helper script is simply a js file that contains common test code that |
michael@0 | 77 | * is either not common-enough to be in head.js, or that is located in a separate |
michael@0 | 78 | * directory. |
michael@0 | 79 | * The script will be loaded synchronously and in the test's scope. |
michael@0 | 80 | * @param {String} filePath The file path, relative to the current directory. |
michael@0 | 81 | * Examples: |
michael@0 | 82 | * - "helper_attributes_test_runner.js" |
michael@0 | 83 | * - "../../../commandline/test/helpers.js" |
michael@0 | 84 | */ |
michael@0 | 85 | function loadHelperScript(filePath) { |
michael@0 | 86 | let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); |
michael@0 | 87 | Services.scriptloader.loadSubScript(testDir + "/" + filePath, this); |
michael@0 | 88 | } |
michael@0 | 89 | |
michael@0 | 90 | /** |
michael@0 | 91 | * Reload the current page |
michael@0 | 92 | * @return a promise that resolves when the inspector has emitted the event |
michael@0 | 93 | * new-root |
michael@0 | 94 | */ |
michael@0 | 95 | function reloadPage(inspector) { |
michael@0 | 96 | info("Reloading the page"); |
michael@0 | 97 | let newRoot = inspector.once("new-root"); |
michael@0 | 98 | content.location.reload(); |
michael@0 | 99 | return newRoot; |
michael@0 | 100 | } |
michael@0 | 101 | |
michael@0 | 102 | /** |
michael@0 | 103 | * Open the toolbox, with the inspector tool visible. |
michael@0 | 104 | * @return a promise that resolves when the inspector is ready |
michael@0 | 105 | */ |
michael@0 | 106 | function openInspector() { |
michael@0 | 107 | info("Opening the inspector panel"); |
michael@0 | 108 | let def = promise.defer(); |
michael@0 | 109 | |
michael@0 | 110 | let target = TargetFactory.forTab(gBrowser.selectedTab); |
michael@0 | 111 | gDevTools.showToolbox(target, "inspector").then(function(toolbox) { |
michael@0 | 112 | info("The toolbox is open"); |
michael@0 | 113 | let inspector = toolbox.getCurrentPanel(); |
michael@0 | 114 | inspector.once("inspector-updated", () => { |
michael@0 | 115 | info("The inspector panel is active and ready"); |
michael@0 | 116 | def.resolve({toolbox: toolbox, inspector: inspector}); |
michael@0 | 117 | }); |
michael@0 | 118 | }).then(null, console.error); |
michael@0 | 119 | |
michael@0 | 120 | return def.promise; |
michael@0 | 121 | } |
michael@0 | 122 | |
michael@0 | 123 | /** |
michael@0 | 124 | * Simple DOM node accesor function that takes either a node or a string css |
michael@0 | 125 | * selector as argument and returns the corresponding node |
michael@0 | 126 | * @param {String|DOMNode} nodeOrSelector |
michael@0 | 127 | * @return {DOMNode} |
michael@0 | 128 | */ |
michael@0 | 129 | function getNode(nodeOrSelector) { |
michael@0 | 130 | info("Getting the node for '" + nodeOrSelector + "'"); |
michael@0 | 131 | return typeof nodeOrSelector === "string" ? |
michael@0 | 132 | content.document.querySelector(nodeOrSelector) : |
michael@0 | 133 | nodeOrSelector; |
michael@0 | 134 | } |
michael@0 | 135 | |
michael@0 | 136 | /** |
michael@0 | 137 | * Set the inspector's current selection to a node or to the first match of the |
michael@0 | 138 | * given css selector |
michael@0 | 139 | * @param {String|DOMNode} nodeOrSelector |
michael@0 | 140 | * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox |
michael@0 | 141 | * @param {String} reason Defaults to "test" which instructs the inspector not to highlight the node upon selection |
michael@0 | 142 | * @return a promise that resolves when the inspector is updated with the new |
michael@0 | 143 | * node |
michael@0 | 144 | */ |
michael@0 | 145 | function selectNode(nodeOrSelector, inspector, reason="test") { |
michael@0 | 146 | info("Selecting the node for '" + nodeOrSelector + "'"); |
michael@0 | 147 | let node = getNode(nodeOrSelector); |
michael@0 | 148 | let updated = inspector.once("inspector-updated"); |
michael@0 | 149 | inspector.selection.setNode(node, reason); |
michael@0 | 150 | return updated; |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | /** |
michael@0 | 154 | * Get the MarkupContainer object instance that corresponds to the given |
michael@0 | 155 | * HTML node |
michael@0 | 156 | * @param {DOMNode|String} nodeOrSelector The DOM node for which the |
michael@0 | 157 | * container is required |
michael@0 | 158 | * @param {InspectorPanel} inspector The instance of InspectorPanel currently |
michael@0 | 159 | * loaded in the toolbox |
michael@0 | 160 | * @return {MarkupContainer} |
michael@0 | 161 | */ |
michael@0 | 162 | function getContainerForRawNode(nodeOrSelector, {markup}) { |
michael@0 | 163 | let front = markup.walker.frontForRawNode(getNode(nodeOrSelector)); |
michael@0 | 164 | let container = markup.getContainer(front); |
michael@0 | 165 | info("Markup-container object for " + nodeOrSelector + " " + container); |
michael@0 | 166 | return container; |
michael@0 | 167 | } |
michael@0 | 168 | |
michael@0 | 169 | /** |
michael@0 | 170 | * Using the markupview's _waitForChildren function, wait for all queued |
michael@0 | 171 | * children updates to be handled. |
michael@0 | 172 | * @param {InspectorPanel} inspector The instance of InspectorPanel currently |
michael@0 | 173 | * loaded in the toolbox |
michael@0 | 174 | * @return a promise that resolves when all queued children updates have been |
michael@0 | 175 | * handled |
michael@0 | 176 | */ |
michael@0 | 177 | function waitForChildrenUpdated({markup}) { |
michael@0 | 178 | info("Waiting for queued children updates to be handled"); |
michael@0 | 179 | let def = promise.defer(); |
michael@0 | 180 | markup._waitForChildren().then(() => { |
michael@0 | 181 | executeSoon(def.resolve); |
michael@0 | 182 | }); |
michael@0 | 183 | return def.promise; |
michael@0 | 184 | } |
michael@0 | 185 | |
michael@0 | 186 | /** |
michael@0 | 187 | * Simulate a mouse-over on the markup-container (a line in the markup-view) |
michael@0 | 188 | * that corresponds to the node or selector passed. |
michael@0 | 189 | * @param {String|DOMNode} nodeOrSelector |
michael@0 | 190 | * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox |
michael@0 | 191 | * @return a promise that resolves when the container is hovered and the higlighter |
michael@0 | 192 | * is shown on the corresponding node |
michael@0 | 193 | */ |
michael@0 | 194 | function hoverContainer(nodeOrSelector, inspector) { |
michael@0 | 195 | info("Hovering over the markup-container for node " + nodeOrSelector); |
michael@0 | 196 | let highlit = inspector.toolbox.once("node-highlight"); |
michael@0 | 197 | let container = getContainerForRawNode(getNode(nodeOrSelector), inspector); |
michael@0 | 198 | EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousemove"}, |
michael@0 | 199 | inspector.markup.doc.defaultView); |
michael@0 | 200 | return highlit; |
michael@0 | 201 | } |
michael@0 | 202 | |
michael@0 | 203 | /** |
michael@0 | 204 | * Simulate a click on the markup-container (a line in the markup-view) |
michael@0 | 205 | * that corresponds to the node or selector passed. |
michael@0 | 206 | * @param {String|DOMNode} nodeOrSelector |
michael@0 | 207 | * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox |
michael@0 | 208 | * @return a promise that resolves when the node has been selected. |
michael@0 | 209 | */ |
michael@0 | 210 | function clickContainer(nodeOrSelector, inspector) { |
michael@0 | 211 | info("Clicking on the markup-container for node " + nodeOrSelector); |
michael@0 | 212 | let updated = inspector.once("inspector-updated"); |
michael@0 | 213 | let container = getContainerForRawNode(getNode(nodeOrSelector), inspector); |
michael@0 | 214 | EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousedown"}, |
michael@0 | 215 | inspector.markup.doc.defaultView); |
michael@0 | 216 | EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mouseup"}, |
michael@0 | 217 | inspector.markup.doc.defaultView); |
michael@0 | 218 | return updated; |
michael@0 | 219 | } |
michael@0 | 220 | |
michael@0 | 221 | /** |
michael@0 | 222 | * Checks if the highlighter is visible currently |
michael@0 | 223 | * @return {Boolean} |
michael@0 | 224 | */ |
michael@0 | 225 | function isHighlighterVisible() { |
michael@0 | 226 | let highlighter = gBrowser.selectedBrowser.parentNode |
michael@0 | 227 | .querySelector(".highlighter-container .box-model-root"); |
michael@0 | 228 | return highlighter && !highlighter.hasAttribute("hidden"); |
michael@0 | 229 | } |
michael@0 | 230 | |
michael@0 | 231 | /** |
michael@0 | 232 | * Simulate the mouse leaving the markup-view area |
michael@0 | 233 | * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox |
michael@0 | 234 | * @return a promise when done |
michael@0 | 235 | */ |
michael@0 | 236 | function mouseLeaveMarkupView(inspector) { |
michael@0 | 237 | info("Leaving the markup-view area"); |
michael@0 | 238 | let def = promise.defer(); |
michael@0 | 239 | |
michael@0 | 240 | // Find another element to mouseover over in order to leave the markup-view |
michael@0 | 241 | let btn = inspector.toolbox.doc.querySelector(".toolbox-dock-button"); |
michael@0 | 242 | |
michael@0 | 243 | EventUtils.synthesizeMouseAtCenter(btn, {type: "mousemove"}, |
michael@0 | 244 | inspector.toolbox.doc.defaultView); |
michael@0 | 245 | executeSoon(def.resolve); |
michael@0 | 246 | |
michael@0 | 247 | return def.promise; |
michael@0 | 248 | } |
michael@0 | 249 | |
michael@0 | 250 | /** |
michael@0 | 251 | * Focus a given editable element, enter edit mode, set value, and commit |
michael@0 | 252 | * @param {DOMNode} field The element that gets editable after receiving focus |
michael@0 | 253 | * and <ENTER> keypress |
michael@0 | 254 | * @param {String} value The string value to be set into the edited field |
michael@0 | 255 | * @param {InspectorPanel} inspector The instance of InspectorPanel currently |
michael@0 | 256 | * loaded in the toolbox |
michael@0 | 257 | */ |
michael@0 | 258 | function setEditableFieldValue(field, value, inspector) { |
michael@0 | 259 | field.focus(); |
michael@0 | 260 | EventUtils.sendKey("return", inspector.panelWin); |
michael@0 | 261 | let input = inplaceEditor(field).input; |
michael@0 | 262 | ok(input, "Found editable field for setting value: " + value); |
michael@0 | 263 | input.value = value; |
michael@0 | 264 | EventUtils.sendKey("return", inspector.panelWin); |
michael@0 | 265 | } |
michael@0 | 266 | |
michael@0 | 267 | /** |
michael@0 | 268 | * Focus the new-attribute inplace-editor field of the nodeOrSelector's markup |
michael@0 | 269 | * container, and enters the given text, then wait for it to be applied and the |
michael@0 | 270 | * for the node to mutates (when new attribute(s) is(are) created) |
michael@0 | 271 | * @param {DOMNode|String} nodeOrSelector The node or node selector to edit. |
michael@0 | 272 | * @param {String} text The new attribute text to be entered (e.g. "id='test'") |
michael@0 | 273 | * @param {InspectorPanel} inspector The instance of InspectorPanel currently |
michael@0 | 274 | * loaded in the toolbox |
michael@0 | 275 | * @return a promise that resolves when the node has mutated |
michael@0 | 276 | */ |
michael@0 | 277 | function addNewAttributes(nodeOrSelector, text, inspector) { |
michael@0 | 278 | info("Entering text '" + text + "' in node '" + nodeOrSelector + "''s new attribute field"); |
michael@0 | 279 | |
michael@0 | 280 | let container = getContainerForRawNode(nodeOrSelector, inspector); |
michael@0 | 281 | ok(container, "The container for '" + nodeOrSelector + "' was found"); |
michael@0 | 282 | |
michael@0 | 283 | info("Listening for the markupmutation event"); |
michael@0 | 284 | let nodeMutated = inspector.once("markupmutation"); |
michael@0 | 285 | setEditableFieldValue(container.editor.newAttr, text, inspector); |
michael@0 | 286 | return nodeMutated; |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | /** |
michael@0 | 290 | * Checks that a node has the given attributes |
michael@0 | 291 | * |
michael@0 | 292 | * @param {DOMNode|String} nodeOrSelector The node or node selector to check. |
michael@0 | 293 | * @param {Object} attrs An object containing the attributes to check. |
michael@0 | 294 | * e.g. {id: "id1", class: "someclass"} |
michael@0 | 295 | * |
michael@0 | 296 | * Note that node.getAttribute() returns attribute values provided by the HTML |
michael@0 | 297 | * parser. The parser only provides unescaped entities so & will return &. |
michael@0 | 298 | */ |
michael@0 | 299 | function assertAttributes(nodeOrSelector, attrs) { |
michael@0 | 300 | let node = getNode(nodeOrSelector); |
michael@0 | 301 | |
michael@0 | 302 | is(node.attributes.length, Object.keys(attrs).length, |
michael@0 | 303 | "Node has the correct number of attributes."); |
michael@0 | 304 | for (let attr in attrs) { |
michael@0 | 305 | is(node.getAttribute(attr), attrs[attr], |
michael@0 | 306 | "Node has the correct " + attr + " attribute."); |
michael@0 | 307 | } |
michael@0 | 308 | } |
michael@0 | 309 | |
michael@0 | 310 | /** |
michael@0 | 311 | * Undo the last markup-view action and wait for the corresponding mutation to |
michael@0 | 312 | * occur |
michael@0 | 313 | * @param {InspectorPanel} inspector The instance of InspectorPanel currently |
michael@0 | 314 | * loaded in the toolbox |
michael@0 | 315 | * @return a promise that resolves when the markup-mutation has been treated or |
michael@0 | 316 | * rejects if no undo action is possible |
michael@0 | 317 | */ |
michael@0 | 318 | function undoChange(inspector) { |
michael@0 | 319 | let canUndo = inspector.markup.undo.canUndo(); |
michael@0 | 320 | ok(canUndo, "The last change in the markup-view can be undone"); |
michael@0 | 321 | if (!canUndo) { |
michael@0 | 322 | return promise.reject(); |
michael@0 | 323 | } |
michael@0 | 324 | |
michael@0 | 325 | let mutated = inspector.once("markupmutation"); |
michael@0 | 326 | inspector.markup.undo.undo(); |
michael@0 | 327 | return mutated; |
michael@0 | 328 | } |
michael@0 | 329 | |
michael@0 | 330 | /** |
michael@0 | 331 | * Redo the last markup-view action and wait for the corresponding mutation to |
michael@0 | 332 | * occur |
michael@0 | 333 | * @param {InspectorPanel} inspector The instance of InspectorPanel currently |
michael@0 | 334 | * loaded in the toolbox |
michael@0 | 335 | * @return a promise that resolves when the markup-mutation has been treated or |
michael@0 | 336 | * rejects if no redo action is possible |
michael@0 | 337 | */ |
michael@0 | 338 | function redoChange(inspector) { |
michael@0 | 339 | let canRedo = inspector.markup.undo.canRedo(); |
michael@0 | 340 | ok(canRedo, "The last change in the markup-view can be redone"); |
michael@0 | 341 | if (!canRedo) { |
michael@0 | 342 | return promise.reject(); |
michael@0 | 343 | } |
michael@0 | 344 | |
michael@0 | 345 | let mutated = inspector.once("markupmutation"); |
michael@0 | 346 | inspector.markup.undo.redo(); |
michael@0 | 347 | return mutated; |
michael@0 | 348 | } |
michael@0 | 349 | |
michael@0 | 350 | /** |
michael@0 | 351 | * Get the selector-search input box from the inspector panel |
michael@0 | 352 | * @return {DOMNode} |
michael@0 | 353 | */ |
michael@0 | 354 | function getSelectorSearchBox(inspector) { |
michael@0 | 355 | return inspector.panelWin.document.getElementById("inspector-searchbox"); |
michael@0 | 356 | } |
michael@0 | 357 | |
michael@0 | 358 | /** |
michael@0 | 359 | * Using the inspector panel's selector search box, search for a given selector. |
michael@0 | 360 | * The selector input string will be entered in the input field and the <ENTER> |
michael@0 | 361 | * keypress will be simulated. |
michael@0 | 362 | * This function won't wait for any events and is not async. It's up to callers |
michael@0 | 363 | * to subscribe to events and react accordingly. |
michael@0 | 364 | */ |
michael@0 | 365 | function searchUsingSelectorSearch(selector, inspector) { |
michael@0 | 366 | info("Entering \"" + selector + "\" into the selector-search input field"); |
michael@0 | 367 | let field = getSelectorSearchBox(inspector); |
michael@0 | 368 | field.focus(); |
michael@0 | 369 | field.value = selector; |
michael@0 | 370 | EventUtils.sendKey("return", inspector.panelWin); |
michael@0 | 371 | } |
michael@0 | 372 | |
michael@0 | 373 | /** |
michael@0 | 374 | * This shouldn't be used in the tests, but is useful when writing new tests or |
michael@0 | 375 | * debugging existing tests in order to introduce delays in the test steps |
michael@0 | 376 | * @param {Number} ms The time to wait |
michael@0 | 377 | * @return A promise that resolves when the time is passed |
michael@0 | 378 | */ |
michael@0 | 379 | function wait(ms) { |
michael@0 | 380 | let def = promise.defer(); |
michael@0 | 381 | content.setTimeout(def.resolve, ms); |
michael@0 | 382 | return def.promise; |
michael@0 | 383 | } |