browser/devtools/styleinspector/test/head.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
michael@0 2 /* Any copyright is dedicated to the Public Domain.
michael@0 3 http://creativecommons.org/publicdomain/zero/1.0/ */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 const Cu = Components.utils;
michael@0 8 let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
michael@0 9 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
michael@0 10 let TargetFactory = devtools.TargetFactory;
michael@0 11 let {CssHtmlTree} = devtools.require("devtools/styleinspector/computed-view");
michael@0 12 let {CssRuleView, _ElementStyle} = devtools.require("devtools/styleinspector/rule-view");
michael@0 13 let {CssLogic, CssSelector} = devtools.require("devtools/styleinspector/css-logic");
michael@0 14 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
michael@0 15 let {editableField, getInplaceEditorForSpan: inplaceEditor} = devtools.require("devtools/shared/inplace-editor");
michael@0 16 let {console} = Components.utils.import("resource://gre/modules/devtools/Console.jsm", {});
michael@0 17
michael@0 18 // All test are asynchronous
michael@0 19 waitForExplicitFinish();
michael@0 20
michael@0 21 const TEST_URL_ROOT = "http://example.com/browser/browser/devtools/styleinspector/test/";
michael@0 22 const TEST_URL_ROOT_SSL = "https://example.com/browser/browser/devtools/styleinspector/test/";
michael@0 23
michael@0 24 // Auto clean-up when a test ends
michael@0 25 registerCleanupFunction(() => {
michael@0 26 try {
michael@0 27 let target = TargetFactory.forTab(gBrowser.selectedTab);
michael@0 28 gDevTools.closeToolbox(target);
michael@0 29 } catch (ex) {
michael@0 30 dump(ex);
michael@0 31 }
michael@0 32 while (gBrowser.tabs.length > 1) {
michael@0 33 gBrowser.removeCurrentTab();
michael@0 34 }
michael@0 35 });
michael@0 36
michael@0 37 // Uncomment to log events
michael@0 38 // Services.prefs.setBoolPref("devtools.dump.emit", true);
michael@0 39
michael@0 40 // Clean-up all prefs that might have been changed during a test run
michael@0 41 // (safer here because if the test fails, then the pref is never reverted)
michael@0 42 registerCleanupFunction(() => {
michael@0 43 Services.prefs.clearUserPref("devtools.dump.emit");
michael@0 44 Services.prefs.clearUserPref("devtools.defaultColorUnit");
michael@0 45 });
michael@0 46
michael@0 47 /**
michael@0 48 * The functions found below are here to ease test development and maintenance.
michael@0 49 * Most of these functions are stateless and will require some form of context
michael@0 50 * (the instance of the current toolbox, or inspector panel for instance).
michael@0 51 *
michael@0 52 * Most of these functions are async too and return promises.
michael@0 53 *
michael@0 54 * All tests should follow the following pattern:
michael@0 55 *
michael@0 56 * let test = asyncTest(function*() {
michael@0 57 * yield addTab(TEST_URI);
michael@0 58 * let {toolbox, inspector, view} = yield openComputedView();
michael@0 59 *
michael@0 60 * yield selectNode("#test", inspector);
michael@0 61 * yield someAsyncTestFunction(view);
michael@0 62 * });
michael@0 63 *
michael@0 64 * asyncTest is the way to define the testcase in the test file. It accepts
michael@0 65 * a single generator-function argument.
michael@0 66 * The generator function should yield any async call.
michael@0 67 *
michael@0 68 * There is no need to clean tabs up at the end of a test as this is done
michael@0 69 * automatically.
michael@0 70 *
michael@0 71 * It is advised not to store any references on the global scope. There shouldn't
michael@0 72 * be a need to anyway. Thanks to asyncTest, test steps, even though asynchronous,
michael@0 73 * can be described in a nice flat way, and if/for/while/... control flow can be
michael@0 74 * used as in sync code, making it possible to write the outline of the test case
michael@0 75 * all in asyncTest, and delegate actual processing and assertions to other
michael@0 76 * functions.
michael@0 77 */
michael@0 78
michael@0 79 /* *********************************************
michael@0 80 * UTILS
michael@0 81 * *********************************************
michael@0 82 * General test utilities.
michael@0 83 * Define the test case, add new tabs, open the toolbox and switch to the
michael@0 84 * various panels, select nodes, get node references, ...
michael@0 85 */
michael@0 86
michael@0 87 /**
michael@0 88 * Define an async test based on a generator function
michael@0 89 */
michael@0 90 function asyncTest(generator) {
michael@0 91 return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish);
michael@0 92 }
michael@0 93
michael@0 94 /**
michael@0 95 * Add a new test tab in the browser and load the given url.
michael@0 96 * @param {String} url The url to be loaded in the new tab
michael@0 97 * @return a promise that resolves to the tab object when the url is loaded
michael@0 98 */
michael@0 99 function addTab(url) {
michael@0 100 let def = promise.defer();
michael@0 101
michael@0 102 let tab = gBrowser.selectedTab = gBrowser.addTab();
michael@0 103 gBrowser.selectedBrowser.addEventListener("load", function onload() {
michael@0 104 gBrowser.selectedBrowser.removeEventListener("load", onload, true);
michael@0 105 info("URL " + url + " loading complete into new test tab");
michael@0 106 waitForFocus(() => {
michael@0 107 def.resolve(tab);
michael@0 108 }, content);
michael@0 109 }, true);
michael@0 110 content.location = url;
michael@0 111
michael@0 112 return def.promise;
michael@0 113 }
michael@0 114
michael@0 115 /**
michael@0 116 * Simple DOM node accesor function that takes either a node or a string css
michael@0 117 * selector as argument and returns the corresponding node
michael@0 118 * @param {String|DOMNode} nodeOrSelector
michael@0 119 * @return {DOMNode}
michael@0 120 */
michael@0 121 function getNode(nodeOrSelector) {
michael@0 122 return typeof nodeOrSelector === "string" ?
michael@0 123 content.document.querySelector(nodeOrSelector) :
michael@0 124 nodeOrSelector;
michael@0 125 }
michael@0 126
michael@0 127 /**
michael@0 128 * Set the inspector's current selection to a node or to the first match of the
michael@0 129 * given css selector
michael@0 130 * @param {InspectorPanel} inspector The instance of InspectorPanel currently
michael@0 131 * loaded in the toolbox
michael@0 132 * @param {String} reason Defaults to "test" which instructs the inspector not
michael@0 133 * to highlight the node upon selection
michael@0 134 * @param {String} reason Defaults to "test" which instructs the inspector not to highlight the node upon selection
michael@0 135 * @return a promise that resolves when the inspector is updated with the new
michael@0 136 * node
michael@0 137 */
michael@0 138 function selectNode(nodeOrSelector, inspector, reason="test") {
michael@0 139 info("Selecting the node " + nodeOrSelector);
michael@0 140 let node = getNode(nodeOrSelector);
michael@0 141 let updated = inspector.once("inspector-updated");
michael@0 142 inspector.selection.setNode(node, reason);
michael@0 143 return updated;
michael@0 144 }
michael@0 145
michael@0 146 /**
michael@0 147 * Set the inspector's current selection to null so that no node is selected
michael@0 148 * @param {InspectorPanel} inspector The instance of InspectorPanel currently
michael@0 149 * loaded in the toolbox
michael@0 150 * @return a promise that resolves when the inspector is updated
michael@0 151 */
michael@0 152 function clearCurrentNodeSelection(inspector) {
michael@0 153 info("Clearing the current selection");
michael@0 154 let updated = inspector.once("inspector-updated");
michael@0 155 inspector.selection.setNode(null);
michael@0 156 return updated;
michael@0 157 }
michael@0 158
michael@0 159 /**
michael@0 160 * Open the toolbox, with the inspector tool visible.
michael@0 161 * @return a promise that resolves when the inspector is ready
michael@0 162 */
michael@0 163 let openInspector = Task.async(function*() {
michael@0 164 info("Opening the inspector");
michael@0 165 let target = TargetFactory.forTab(gBrowser.selectedTab);
michael@0 166
michael@0 167 let inspector, toolbox;
michael@0 168
michael@0 169 // Checking if the toolbox and the inspector are already loaded
michael@0 170 // The inspector-updated event should only be waited for if the inspector
michael@0 171 // isn't loaded yet
michael@0 172 toolbox = gDevTools.getToolbox(target);
michael@0 173 if (toolbox) {
michael@0 174 inspector = toolbox.getPanel("inspector");
michael@0 175 if (inspector) {
michael@0 176 info("Toolbox and inspector already open");
michael@0 177 return {
michael@0 178 toolbox: toolbox,
michael@0 179 inspector: inspector
michael@0 180 };
michael@0 181 }
michael@0 182 }
michael@0 183
michael@0 184 info("Opening the toolbox");
michael@0 185 toolbox = yield gDevTools.showToolbox(target, "inspector");
michael@0 186 yield waitForToolboxFrameFocus(toolbox);
michael@0 187 inspector = toolbox.getPanel("inspector");
michael@0 188
michael@0 189 info("Waiting for the inspector to update");
michael@0 190 yield inspector.once("inspector-updated");
michael@0 191
michael@0 192 return {
michael@0 193 toolbox: toolbox,
michael@0 194 inspector: inspector
michael@0 195 };
michael@0 196 });
michael@0 197
michael@0 198 /**
michael@0 199 * Wait for the toolbox frame to receive focus after it loads
michael@0 200 * @param {Toolbox} toolbox
michael@0 201 * @return a promise that resolves when focus has been received
michael@0 202 */
michael@0 203 function waitForToolboxFrameFocus(toolbox) {
michael@0 204 info("Making sure that the toolbox's frame is focused");
michael@0 205 let def = promise.defer();
michael@0 206 let win = toolbox.frame.contentWindow;
michael@0 207 waitForFocus(def.resolve, win);
michael@0 208 return def.promise;
michael@0 209 }
michael@0 210
michael@0 211 /**
michael@0 212 * Open the toolbox, with the inspector tool visible, and the sidebar that
michael@0 213 * corresponds to the given id selected
michael@0 214 * @return a promise that resolves when the inspector is ready and the sidebar
michael@0 215 * view is visible and ready
michael@0 216 */
michael@0 217 let openInspectorSideBar = Task.async(function*(id) {
michael@0 218 let {toolbox, inspector} = yield openInspector();
michael@0 219
michael@0 220 if (!hasSideBarTab(inspector, id)) {
michael@0 221 info("Waiting for the " + id + " sidebar to be ready");
michael@0 222 yield inspector.sidebar.once(id + "-ready");
michael@0 223 }
michael@0 224
michael@0 225 info("Selecting the " + id + " sidebar");
michael@0 226 inspector.sidebar.select(id);
michael@0 227
michael@0 228 return {
michael@0 229 toolbox: toolbox,
michael@0 230 inspector: inspector,
michael@0 231 view: inspector.sidebar.getWindowForTab(id)[id].view
michael@0 232 };
michael@0 233 });
michael@0 234
michael@0 235 /**
michael@0 236 * Open the toolbox, with the inspector tool visible, and the computed-view
michael@0 237 * sidebar tab selected.
michael@0 238 * @return a promise that resolves when the inspector is ready and the computed
michael@0 239 * view is visible and ready
michael@0 240 */
michael@0 241 function openComputedView() {
michael@0 242 return openInspectorSideBar("computedview");
michael@0 243 }
michael@0 244
michael@0 245 /**
michael@0 246 * Open the toolbox, with the inspector tool visible, and the rule-view
michael@0 247 * sidebar tab selected.
michael@0 248 * @return a promise that resolves when the inspector is ready and the rule
michael@0 249 * view is visible and ready
michael@0 250 */
michael@0 251 function openRuleView() {
michael@0 252 return openInspectorSideBar("ruleview");
michael@0 253 }
michael@0 254
michael@0 255 /**
michael@0 256 * Wait for eventName on target.
michael@0 257 * @param {Object} target An observable object that either supports on/off or
michael@0 258 * addEventListener/removeEventListener
michael@0 259 * @param {String} eventName
michael@0 260 * @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
michael@0 261 * @return A promise that resolves when the event has been handled
michael@0 262 */
michael@0 263 function once(target, eventName, useCapture=false) {
michael@0 264 info("Waiting for event: '" + eventName + "' on " + target + ".");
michael@0 265
michael@0 266 let deferred = promise.defer();
michael@0 267
michael@0 268 for (let [add, remove] of [
michael@0 269 ["addEventListener", "removeEventListener"],
michael@0 270 ["addListener", "removeListener"],
michael@0 271 ["on", "off"]
michael@0 272 ]) {
michael@0 273 if ((add in target) && (remove in target)) {
michael@0 274 target[add](eventName, function onEvent(...aArgs) {
michael@0 275 target[remove](eventName, onEvent, useCapture);
michael@0 276 deferred.resolve.apply(deferred, aArgs);
michael@0 277 }, useCapture);
michael@0 278 break;
michael@0 279 }
michael@0 280 }
michael@0 281
michael@0 282 return deferred.promise;
michael@0 283 }
michael@0 284
michael@0 285 /**
michael@0 286 * This shouldn't be used in the tests, but is useful when writing new tests or
michael@0 287 * debugging existing tests in order to introduce delays in the test steps
michael@0 288 * @param {Number} ms The time to wait
michael@0 289 * @return A promise that resolves when the time is passed
michael@0 290 */
michael@0 291 function wait(ms) {
michael@0 292 let def = promise.defer();
michael@0 293 content.setTimeout(def.resolve, ms);
michael@0 294 return def.promise;
michael@0 295 }
michael@0 296
michael@0 297 /**
michael@0 298 * Given an inplace editable element, click to switch it to edit mode, wait for
michael@0 299 * focus
michael@0 300 * @return a promise that resolves to the inplace-editor element when ready
michael@0 301 */
michael@0 302 let focusEditableField = Task.async(function*(editable, xOffset=1, yOffset=1, options={}) {
michael@0 303 let onFocus = once(editable.parentNode, "focus", true);
michael@0 304
michael@0 305 info("Clicking on editable field to turn to edit mode");
michael@0 306 EventUtils.synthesizeMouse(editable, xOffset, yOffset, options,
michael@0 307 editable.ownerDocument.defaultView);
michael@0 308 let event = yield onFocus;
michael@0 309
michael@0 310 info("Editable field gained focus, returning the input field now");
michael@0 311 return inplaceEditor(editable.ownerDocument.activeElement);
michael@0 312 });
michael@0 313
michael@0 314 /**
michael@0 315 * Given a tooltip object instance (see Tooltip.js), checks if it is set to
michael@0 316 * toggle and hover and if so, checks if the given target is a valid hover target.
michael@0 317 * This won't actually show the tooltip (the less we interact with XUL panels
michael@0 318 * during test runs, the better).
michael@0 319 * @return a promise that resolves when the answer is known
michael@0 320 */
michael@0 321 function isHoverTooltipTarget(tooltip, target) {
michael@0 322 if (!tooltip._basedNode || !tooltip.panel) {
michael@0 323 return promise.reject(new Error(
michael@0 324 "The tooltip passed isn't set to toggle on hover or is not a tooltip"));
michael@0 325 }
michael@0 326 return tooltip.isValidHoverTarget(target);
michael@0 327 }
michael@0 328
michael@0 329 /**
michael@0 330 * Same as isHoverTooltipTarget except that it will fail the test if there is no
michael@0 331 * tooltip defined on hover of the given element
michael@0 332 * @return a promise
michael@0 333 */
michael@0 334 function assertHoverTooltipOn(tooltip, element) {
michael@0 335 return isHoverTooltipTarget(tooltip, element).then(() => {
michael@0 336 ok(true, "A tooltip is defined on hover of the given element");
michael@0 337 }, () => {
michael@0 338 ok(false, "No tooltip is defined on hover of the given element");
michael@0 339 });
michael@0 340 }
michael@0 341
michael@0 342 /**
michael@0 343 * Same as assertHoverTooltipOn but fails the test if there is a tooltip defined
michael@0 344 * on hover of the given element
michael@0 345 * @return a promise
michael@0 346 */
michael@0 347 function assertNoHoverTooltipOn(tooltip, element) {
michael@0 348 return isHoverTooltipTarget(tooltip, element).then(() => {
michael@0 349 ok(false, "A tooltip is defined on hover of the given element");
michael@0 350 }, () => {
michael@0 351 ok(true, "No tooltip is defined on hover of the given element");
michael@0 352 });
michael@0 353 }
michael@0 354
michael@0 355 /**
michael@0 356 * Listen for a new window to open and return a promise that resolves when one
michael@0 357 * does and completes its load.
michael@0 358 * Only resolves when the new window topic isn't domwindowopened.
michael@0 359 * @return a promise that resolves to the window object
michael@0 360 */
michael@0 361 function waitForWindow() {
michael@0 362 let def = promise.defer();
michael@0 363
michael@0 364 info("Waiting for a window to open");
michael@0 365 Services.ww.registerNotification(function onWindow(subject, topic) {
michael@0 366 if (topic != "domwindowopened") {
michael@0 367 return;
michael@0 368 }
michael@0 369 info("A window has been opened");
michael@0 370 let win = subject.QueryInterface(Ci.nsIDOMWindow);
michael@0 371 once(win, "load").then(() => {
michael@0 372 info("The window load completed");
michael@0 373 Services.ww.unregisterNotification(onWindow);
michael@0 374 def.resolve(win);
michael@0 375 });
michael@0 376 });
michael@0 377
michael@0 378 return def.promise;
michael@0 379 }
michael@0 380
michael@0 381 /**
michael@0 382 * @see SimpleTest.waitForClipboard
michael@0 383 * @param {Function} setup Function to execute before checking for the
michael@0 384 * clipboard content
michael@0 385 * @param {String|Boolean} expected An expected string or validator function
michael@0 386 * @return a promise that resolves when the expected string has been found or
michael@0 387 * the validator function has returned true, rejects otherwise.
michael@0 388 */
michael@0 389 function waitForClipboard(setup, expected) {
michael@0 390 let def = promise.defer();
michael@0 391 SimpleTest.waitForClipboard(expected, setup, def.resolve, def.reject);
michael@0 392 return def.promise;
michael@0 393 }
michael@0 394
michael@0 395 /**
michael@0 396 * Dispatch the copy event on the given element
michael@0 397 */
michael@0 398 function fireCopyEvent(element) {
michael@0 399 let evt = element.ownerDocument.createEvent("Event");
michael@0 400 evt.initEvent("copy", true, true);
michael@0 401 element.dispatchEvent(evt);
michael@0 402 }
michael@0 403
michael@0 404 /**
michael@0 405 * Polls a given function waiting for it to return true.
michael@0 406 *
michael@0 407 * @param {Function} validatorFn A validator function that returns a boolean.
michael@0 408 * This is called every few milliseconds to check if the result is true. When
michael@0 409 * it is true, the promise resolves. If validatorFn never returns true, then
michael@0 410 * polling timeouts after several tries and the promise rejects.
michael@0 411 * @param {String} name Optional name of the test. This is used to generate
michael@0 412 * the success and failure messages.
michael@0 413 * @param {Number} timeout Optional timeout for the validator function, in
michael@0 414 * milliseconds. Default is 5000.
michael@0 415 * @return a promise that resolves when the function returned true or rejects
michael@0 416 * if the timeout is reached
michael@0 417 */
michael@0 418 function waitForSuccess(validatorFn, name="untitled", timeout=5000) {
michael@0 419 let def = promise.defer();
michael@0 420 let start = Date.now();
michael@0 421
michael@0 422 function wait(validatorFn) {
michael@0 423 if ((Date.now() - start) > timeout) {
michael@0 424 ok(false, "Validator function " + name + " timed out");
michael@0 425 return def.reject();
michael@0 426 }
michael@0 427 if (validatorFn()) {
michael@0 428 ok(true, "Validator function " + name + " returned true");
michael@0 429 def.resolve();
michael@0 430 } else {
michael@0 431 setTimeout(() => wait(validatorFn), 100);
michael@0 432 }
michael@0 433 }
michael@0 434 wait(validatorFn);
michael@0 435
michael@0 436 return def.promise;
michael@0 437 }
michael@0 438
michael@0 439 /**
michael@0 440 * Create a new style tag containing the given style text and append it to the
michael@0 441 * document's head node
michael@0 442 * @param {Document} doc
michael@0 443 * @param {String} style
michael@0 444 * @return {DOMNode} The newly created style node
michael@0 445 */
michael@0 446 function addStyle(doc, style) {
michael@0 447 info("Adding a new style tag to the document with style content: " +
michael@0 448 style.substring(0, 50));
michael@0 449 let node = doc.createElement('style');
michael@0 450 node.setAttribute("type", "text/css");
michael@0 451 node.textContent = style;
michael@0 452 doc.getElementsByTagName("head")[0].appendChild(node);
michael@0 453 return node;
michael@0 454 }
michael@0 455
michael@0 456 /**
michael@0 457 * Checks whether the inspector's sidebar corresponding to the given id already
michael@0 458 * exists
michael@0 459 * @param {InspectorPanel}
michael@0 460 * @param {String}
michael@0 461 * @return {Boolean}
michael@0 462 */
michael@0 463 function hasSideBarTab(inspector, id) {
michael@0 464 return !!inspector.sidebar.getWindowForTab(id);
michael@0 465 }
michael@0 466
michael@0 467 /* *********************************************
michael@0 468 * RULE-VIEW
michael@0 469 * *********************************************
michael@0 470 * Rule-view related test utility functions
michael@0 471 * This object contains functions to get rules, get properties, ...
michael@0 472 */
michael@0 473
michael@0 474 /**
michael@0 475 * Get the DOMNode for a css rule in the rule-view that corresponds to the given
michael@0 476 * selector
michael@0 477 * @param {CssRuleView} view The instance of the rule-view panel
michael@0 478 * @param {String} selectorText The selector in the rule-view for which the rule
michael@0 479 * object is wanted
michael@0 480 * @return {DOMNode}
michael@0 481 */
michael@0 482 function getRuleViewRule(view, selectorText) {
michael@0 483 let rule;
michael@0 484 for (let r of view.doc.querySelectorAll(".ruleview-rule")) {
michael@0 485 let selector = r.querySelector(".ruleview-selector, .ruleview-selector-matched");
michael@0 486 if (selector && selector.textContent === selectorText) {
michael@0 487 rule = r;
michael@0 488 break;
michael@0 489 }
michael@0 490 }
michael@0 491
michael@0 492 return rule;
michael@0 493 }
michael@0 494
michael@0 495 /**
michael@0 496 * Get references to the name and value span nodes corresponding to a given
michael@0 497 * selector and property name in the rule-view
michael@0 498 * @param {CssRuleView} view The instance of the rule-view panel
michael@0 499 * @param {String} selectorText The selector in the rule-view to look for the
michael@0 500 * property in
michael@0 501 * @param {String} propertyName The name of the property
michael@0 502 * @return {Object} An object like {nameSpan: DOMNode, valueSpan: DOMNode}
michael@0 503 */
michael@0 504 function getRuleViewProperty(view, selectorText, propertyName) {
michael@0 505 let prop;
michael@0 506
michael@0 507 let rule = getRuleViewRule(view, selectorText);
michael@0 508 if (rule) {
michael@0 509 // Look for the propertyName in that rule element
michael@0 510 for (let p of rule.querySelectorAll(".ruleview-property")) {
michael@0 511 let nameSpan = p.querySelector(".ruleview-propertyname");
michael@0 512 let valueSpan = p.querySelector(".ruleview-propertyvalue");
michael@0 513
michael@0 514 if (nameSpan.textContent === propertyName) {
michael@0 515 prop = {nameSpan: nameSpan, valueSpan: valueSpan};
michael@0 516 break;
michael@0 517 }
michael@0 518 }
michael@0 519 }
michael@0 520 return prop;
michael@0 521 }
michael@0 522
michael@0 523 /**
michael@0 524 * Get the text value of the property corresponding to a given selector and name
michael@0 525 * in the rule-view
michael@0 526 * @param {CssRuleView} view The instance of the rule-view panel
michael@0 527 * @param {String} selectorText The selector in the rule-view to look for the
michael@0 528 * property in
michael@0 529 * @param {String} propertyName The name of the property
michael@0 530 * @return {String} The property value
michael@0 531 */
michael@0 532 function getRuleViewPropertyValue(view, selectorText, propertyName) {
michael@0 533 return getRuleViewProperty(view, selectorText, propertyName)
michael@0 534 .valueSpan.textContent;
michael@0 535 }
michael@0 536
michael@0 537 /**
michael@0 538 * Simulate a color change in a given color picker tooltip, and optionally wait
michael@0 539 * for a given element in the page to have its style changed as a result
michael@0 540 * @param {SwatchColorPickerTooltip} colorPicker
michael@0 541 * @param {Array} newRgba The new color to be set [r, g, b, a]
michael@0 542 * @param {Object} expectedChange Optional object that needs the following props:
michael@0 543 * - {DOMNode} element The element in the page that will have its
michael@0 544 * style changed.
michael@0 545 * - {String} name The style name that will be changed
michael@0 546 * - {String} value The expected style value
michael@0 547 * The style will be checked like so: getComputedStyle(element)[name] === value
michael@0 548 */
michael@0 549 let simulateColorPickerChange = Task.async(function*(colorPicker, newRgba, expectedChange) {
michael@0 550 info("Getting the spectrum colorpicker object");
michael@0 551 let spectrum = yield colorPicker.spectrum;
michael@0 552 info("Setting the new color");
michael@0 553 spectrum.rgb = newRgba;
michael@0 554 info("Applying the change");
michael@0 555 spectrum.updateUI();
michael@0 556 spectrum.onChange();
michael@0 557
michael@0 558 if (expectedChange) {
michael@0 559 info("Waiting for the style to be applied on the page");
michael@0 560 yield waitForSuccess(() => {
michael@0 561 let {element, name, value} = expectedChange;
michael@0 562 return content.getComputedStyle(element)[name] === value;
michael@0 563 }, "Color picker change applied on the page");
michael@0 564 }
michael@0 565 });
michael@0 566
michael@0 567 /**
michael@0 568 * Get a rule-link from the rule-view given its index
michael@0 569 * @param {CssRuleView} view The instance of the rule-view panel
michael@0 570 * @param {Number} index The index of the link to get
michael@0 571 * @return {DOMNode} The link if any at this index
michael@0 572 */
michael@0 573 function getRuleViewLinkByIndex(view, index) {
michael@0 574 let links = view.doc.querySelectorAll(".ruleview-rule-source");
michael@0 575 return links[index];
michael@0 576 }
michael@0 577
michael@0 578 /**
michael@0 579 * Click on a rule-view's close brace to focus a new property name editor
michael@0 580 * @param {RuleEditor} ruleEditor An instance of RuleEditor that will receive
michael@0 581 * the new property
michael@0 582 * @return a promise that resolves to the newly created editor when ready and
michael@0 583 * focused
michael@0 584 */
michael@0 585 let focusNewRuleViewProperty = Task.async(function*(ruleEditor) {
michael@0 586 info("Clicking on a close ruleEditor brace to start editing a new property");
michael@0 587 ruleEditor.closeBrace.scrollIntoView();
michael@0 588 let editor = yield focusEditableField(ruleEditor.closeBrace);
michael@0 589
michael@0 590 is(inplaceEditor(ruleEditor.newPropSpan), editor, "Focused editor is the new property editor.");
michael@0 591 is(ruleEditor.rule.textProps.length, 0, "Starting with one new text property.");
michael@0 592 is(ruleEditor.propertyList.children.length, 1, "Starting with two property editors.");
michael@0 593
michael@0 594 return editor;
michael@0 595 });
michael@0 596
michael@0 597 /**
michael@0 598 * Create a new property name in the rule-view, focusing a new property editor
michael@0 599 * by clicking on the close brace, and then entering the given text.
michael@0 600 * Keep in mind that the rule-view knows how to handle strings with multiple
michael@0 601 * properties, so the input text may be like: "p1:v1;p2:v2;p3:v3".
michael@0 602 * @param {RuleEditor} ruleEditor The instance of RuleEditor that will receive
michael@0 603 * the new property(ies)
michael@0 604 * @param {String} inputValue The text to be entered in the new property name
michael@0 605 * field
michael@0 606 * @return a promise that resolves when the new property name has been entered
michael@0 607 * and once the value field is focused
michael@0 608 */
michael@0 609 let createNewRuleViewProperty = Task.async(function*(ruleEditor, inputValue) {
michael@0 610 info("Creating a new property editor");
michael@0 611 let editor = yield focusNewRuleViewProperty(ruleEditor);
michael@0 612
michael@0 613 info("Entering the value " + inputValue);
michael@0 614 editor.input.value = inputValue;
michael@0 615
michael@0 616 info("Submitting the new value and waiting for value field focus");
michael@0 617 let onFocus = once(ruleEditor.element, "focus", true);
michael@0 618 EventUtils.synthesizeKey("VK_RETURN", {},
michael@0 619 ruleEditor.element.ownerDocument.defaultView);
michael@0 620 yield onFocus;
michael@0 621 });
michael@0 622
michael@0 623 // TO BE UNCOMMENTED WHEN THE EYEDROPPER FINALLY LANDS
michael@0 624 // /**
michael@0 625 // * Given a color swatch in the ruleview, click on it to open the color picker
michael@0 626 // * and then click on the eyedropper button to start the eyedropper tool
michael@0 627 // * @param {CssRuleView} view The instance of the rule-view panel
michael@0 628 // * @param {DOMNode} swatch The color swatch to be clicked on
michael@0 629 // * @return A promise that resolves when the dropper is opened
michael@0 630 // */
michael@0 631 // let openRuleViewEyeDropper = Task.async(function*(view, swatch) {
michael@0 632 // info("Opening the colorpicker tooltip on a colorswatch");
michael@0 633 // let tooltip = view.colorPicker.tooltip;
michael@0 634 // let onTooltipShown = tooltip.once("shown");
michael@0 635 // swatch.click();
michael@0 636 // yield onTooltipShown;
michael@0 637
michael@0 638 // info("Finding the eyedropper icon in the colorpicker document");
michael@0 639 // let tooltipDoc = tooltip.content.contentDocument;
michael@0 640 // let dropperButton = tooltipDoc.querySelector("#eyedropper-button");
michael@0 641 // ok(dropperButton, "Found the eyedropper icon");
michael@0 642
michael@0 643 // info("Opening the eyedropper");
michael@0 644 // let onOpen = tooltip.once("eyedropper-opened");
michael@0 645 // dropperButton.click();
michael@0 646 // return yield onOpen;
michael@0 647 // });
michael@0 648
michael@0 649 /* *********************************************
michael@0 650 * COMPUTED-VIEW
michael@0 651 * *********************************************
michael@0 652 * Computed-view related utility functions.
michael@0 653 * Allows to get properties, links, expand properties, ...
michael@0 654 */
michael@0 655
michael@0 656 /**
michael@0 657 * Get references to the name and value span nodes corresponding to a given
michael@0 658 * property name in the computed-view
michael@0 659 * @param {CssHtmlTree} view The instance of the computed view panel
michael@0 660 * @param {String} name The name of the property to retrieve
michael@0 661 * @return an object {nameSpan, valueSpan}
michael@0 662 */
michael@0 663 function getComputedViewProperty(view, name) {
michael@0 664 let prop;
michael@0 665 for (let property of view.styleDocument.querySelectorAll(".property-view")) {
michael@0 666 let nameSpan = property.querySelector(".property-name");
michael@0 667 let valueSpan = property.querySelector(".property-value");
michael@0 668
michael@0 669 if (nameSpan.textContent === name) {
michael@0 670 prop = {nameSpan: nameSpan, valueSpan: valueSpan};
michael@0 671 break;
michael@0 672 }
michael@0 673 }
michael@0 674 return prop;
michael@0 675 }
michael@0 676
michael@0 677 /**
michael@0 678 * Get the text value of the property corresponding to a given name in the
michael@0 679 * computed-view
michael@0 680 * @param {CssHtmlTree} view The instance of the computed view panel
michael@0 681 * @param {String} name The name of the property to retrieve
michael@0 682 * @return {String} The property value
michael@0 683 */
michael@0 684 function getComputedViewPropertyValue(view, selectorText, propertyName) {
michael@0 685 return getComputedViewProperty(view, selectorText, propertyName)
michael@0 686 .valueSpan.textContent;
michael@0 687 }
michael@0 688
michael@0 689 /**
michael@0 690 * Expand a given property, given its index in the current property list of
michael@0 691 * the computed view
michael@0 692 * @param {CssHtmlTree} view The instance of the computed view panel
michael@0 693 * @param {InspectorPanel} inspector The instance of the inspector panel
michael@0 694 * @param {Number} index The index of the property to be expanded
michael@0 695 * @return a promise that resolves when the property has been expanded, or
michael@0 696 * rejects if the property was not found
michael@0 697 */
michael@0 698 function expandComputedViewPropertyByIndex(view, inspector, index) {
michael@0 699 info("Expanding property " + index + " in the computed view");
michael@0 700 let expandos = view.styleDocument.querySelectorAll(".expandable");
michael@0 701 if (!expandos.length || !expandos[index]) {
michael@0 702 return promise.reject();
michael@0 703 }
michael@0 704
michael@0 705 let onExpand = inspector.once("computed-view-property-expanded");
michael@0 706 expandos[index].click();
michael@0 707 return onExpand;
michael@0 708 }
michael@0 709
michael@0 710 /**
michael@0 711 * Get a rule-link from the computed-view given its index
michael@0 712 * @param {CssHtmlTree} view The instance of the computed view panel
michael@0 713 * @param {Number} index The index of the link to be retrieved
michael@0 714 * @return {DOMNode} The link at the given index, if one exists, null otherwise
michael@0 715 */
michael@0 716 function getComputedViewLinkByIndex(view, index) {
michael@0 717 let links = view.styleDocument.querySelectorAll(".rule-link .link");
michael@0 718 return links[index];
michael@0 719 }
michael@0 720
michael@0 721 /* *********************************************
michael@0 722 * STYLE-EDITOR
michael@0 723 * *********************************************
michael@0 724 * Style-editor related utility functions.
michael@0 725 */
michael@0 726
michael@0 727 /**
michael@0 728 * Wait for the toolbox to emit the styleeditor-selected event and when done
michael@0 729 * wait for the stylesheet identified by href to be loaded in the stylesheet
michael@0 730 * editor
michael@0 731 * @param {Toolbox} toolbox
michael@0 732 * @param {String} href Optional, if not provided, wait for the first editor
michael@0 733 * to be ready
michael@0 734 * @return a promise that resolves to the editor when the stylesheet editor is
michael@0 735 * ready
michael@0 736 */
michael@0 737 function waitForStyleEditor(toolbox, href) {
michael@0 738 let def = promise.defer();
michael@0 739
michael@0 740 info("Waiting for the toolbox to switch to the styleeditor");
michael@0 741 toolbox.once("styleeditor-ready").then(() => {
michael@0 742 let panel = toolbox.getCurrentPanel();
michael@0 743 ok(panel && panel.UI, "Styleeditor panel switched to front");
michael@0 744
michael@0 745 panel.UI.on("editor-selected", function onEditorSelected(event, editor) {
michael@0 746 let currentHref = editor.styleSheet.href;
michael@0 747 if (!href || (href && currentHref.endsWith(href))) {
michael@0 748 info("Stylesheet editor selected");
michael@0 749 panel.UI.off("editor-selected", onEditorSelected);
michael@0 750 editor.getSourceEditor().then(editor => {
michael@0 751 info("Stylesheet editor fully loaded");
michael@0 752 def.resolve(editor);
michael@0 753 });
michael@0 754 }
michael@0 755 });
michael@0 756 });
michael@0 757
michael@0 758 return def.promise;
michael@0 759 }

mercurial