browser/devtools/shared/widgets/Tooltip.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/devtools/shared/widgets/Tooltip.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1090 @@
     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 +"use strict";
     1.9 +
    1.10 +const {Cc, Cu, Ci} = require("chrome");
    1.11 +const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
    1.12 +const IOService = Cc["@mozilla.org/network/io-service;1"]
    1.13 +  .getService(Ci.nsIIOService);
    1.14 +const {Spectrum} = require("devtools/shared/widgets/Spectrum");
    1.15 +const EventEmitter = require("devtools/toolkit/event-emitter");
    1.16 +const {colorUtils} = require("devtools/css-color");
    1.17 +const Heritage = require("sdk/core/heritage");
    1.18 +const {CSSTransformPreviewer} = require("devtools/shared/widgets/CSSTransformPreviewer");
    1.19 +const {Eyedropper} = require("devtools/eyedropper/eyedropper");
    1.20 +
    1.21 +Cu.import("resource://gre/modules/Services.jsm");
    1.22 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.23 +
    1.24 +XPCOMUtils.defineLazyModuleGetter(this, "setNamedTimeout",
    1.25 +  "resource:///modules/devtools/ViewHelpers.jsm");
    1.26 +XPCOMUtils.defineLazyModuleGetter(this, "clearNamedTimeout",
    1.27 +  "resource:///modules/devtools/ViewHelpers.jsm");
    1.28 +XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
    1.29 +  "resource:///modules/devtools/VariablesView.jsm");
    1.30 +XPCOMUtils.defineLazyModuleGetter(this, "VariablesViewController",
    1.31 +  "resource:///modules/devtools/VariablesViewController.jsm");
    1.32 +XPCOMUtils.defineLazyModuleGetter(this, "Task",
    1.33 +  "resource://gre/modules/Task.jsm");
    1.34 +
    1.35 +const GRADIENT_RE = /\b(repeating-)?(linear|radial)-gradient\(((rgb|hsl)a?\(.+?\)|[^\)])+\)/gi;
    1.36 +const BORDERCOLOR_RE = /^border-[-a-z]*color$/ig;
    1.37 +const BORDER_RE = /^border(-(top|bottom|left|right))?$/ig;
    1.38 +const XHTML_NS = "http://www.w3.org/1999/xhtml";
    1.39 +const SPECTRUM_FRAME = "chrome://browser/content/devtools/spectrum-frame.xhtml";
    1.40 +const ESCAPE_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE;
    1.41 +const RETURN_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
    1.42 +const POPUP_EVENTS = ["shown", "hidden", "showing", "hiding"];
    1.43 +const FONT_FAMILY_PREVIEW_TEXT = "(ABCabc123&@%)";
    1.44 +
    1.45 +/**
    1.46 + * Tooltip widget.
    1.47 + *
    1.48 + * This widget is intended at any tool that may need to show rich content in the
    1.49 + * form of floating panels.
    1.50 + * A common use case is image previewing in the CSS rule view, but more complex
    1.51 + * use cases may include color pickers, object inspection, etc...
    1.52 + *
    1.53 + * Tooltips are based on XUL (namely XUL arrow-type <panel>s), and therefore
    1.54 + * need a XUL Document to live in.
    1.55 + * This is pretty much the only requirement they have on their environment.
    1.56 + *
    1.57 + * The way to use a tooltip is simply by instantiating a tooltip yourself and
    1.58 + * attaching some content in it, or using one of the ready-made content types.
    1.59 + *
    1.60 + * A convenient `startTogglingOnHover` method may avoid having to register event
    1.61 + * handlers yourself if the tooltip has to be shown when hovering over a
    1.62 + * specific element or group of elements (which is usually the most common case)
    1.63 + */
    1.64 +
    1.65 +/**
    1.66 + * Container used for dealing with optional parameters.
    1.67 + *
    1.68 + * @param {Object} defaults
    1.69 + *        An object with all default options {p1: v1, p2: v2, ...}
    1.70 + * @param {Object} options
    1.71 + *        The actual values.
    1.72 + */
    1.73 +function OptionsStore(defaults, options) {
    1.74 +  this.defaults = defaults || {};
    1.75 +  this.options = options || {};
    1.76 +}
    1.77 +
    1.78 +OptionsStore.prototype = {
    1.79 +  /**
    1.80 +   * Get the value for a given option name.
    1.81 +   * @return {Object} Returns the value for that option, coming either for the
    1.82 +   *         actual values that have been set in the constructor, or from the
    1.83 +   *         defaults if that options was not specified.
    1.84 +   */
    1.85 +  get: function(name) {
    1.86 +    if (typeof this.options[name] !== "undefined") {
    1.87 +      return this.options[name];
    1.88 +    } else {
    1.89 +      return this.defaults[name];
    1.90 +    }
    1.91 +  }
    1.92 +};
    1.93 +
    1.94 +/**
    1.95 + * The low level structure of a tooltip is a XUL element (a <panel>).
    1.96 + */
    1.97 +let PanelFactory = {
    1.98 +  /**
    1.99 +   * Get a new XUL panel instance.
   1.100 +   * @param {XULDocument} doc
   1.101 +   *        The XUL document to put that panel into
   1.102 +   * @param {OptionsStore} options
   1.103 +   *        An options store to get some configuration from
   1.104 +   */
   1.105 +  get: function(doc, options) {
   1.106 +    // Create the tooltip
   1.107 +    let panel = doc.createElement("panel");
   1.108 +    panel.setAttribute("hidden", true);
   1.109 +    panel.setAttribute("ignorekeys", true);
   1.110 +    panel.setAttribute("animate", false);
   1.111 +
   1.112 +    panel.setAttribute("consumeoutsideclicks", options.get("consumeOutsideClick"));
   1.113 +    panel.setAttribute("noautofocus", options.get("noAutoFocus"));
   1.114 +    panel.setAttribute("type", "arrow");
   1.115 +    panel.setAttribute("level", "top");
   1.116 +
   1.117 +    panel.setAttribute("class", "devtools-tooltip theme-tooltip-panel");
   1.118 +    doc.querySelector("window").appendChild(panel);
   1.119 +
   1.120 +    return panel;
   1.121 +  }
   1.122 +};
   1.123 +
   1.124 +/**
   1.125 + * Tooltip class.
   1.126 + *
   1.127 + * Basic usage:
   1.128 + *   let t = new Tooltip(xulDoc);
   1.129 + *   t.content = someXulContent;
   1.130 + *   t.show();
   1.131 + *   t.hide();
   1.132 + *   t.destroy();
   1.133 + *
   1.134 + * Better usage:
   1.135 + *   let t = new Tooltip(xulDoc);
   1.136 + *   t.startTogglingOnHover(container, target => {
   1.137 + *     if (<condition based on target>) {
   1.138 + *       t.setImageContent("http://image.png");
   1.139 + *       return true;
   1.140 + *     }
   1.141 + *   });
   1.142 + *   t.destroy();
   1.143 + *
   1.144 + * @param {XULDocument} doc
   1.145 + *        The XUL document hosting this tooltip
   1.146 + * @param {Object} options
   1.147 + *        Optional options that give options to consumers:
   1.148 + *        - consumeOutsideClick {Boolean} Wether the first click outside of the
   1.149 + *        tooltip should close the tooltip and be consumed or not.
   1.150 + *        Defaults to false.
   1.151 + *        - closeOnKeys {Array} An array of key codes that should close the
   1.152 + *        tooltip. Defaults to [27] (escape key).
   1.153 + *        - closeOnEvents [{emitter: {Object}, event: {String}, useCapture: {Boolean}}]
   1.154 + *        Provide an optional list of emitter objects and event names here to
   1.155 + *        trigger the closing of the tooltip when these events are fired by the
   1.156 + *        emitters. The emitter objects should either implement on/off(event, cb)
   1.157 + *        or addEventListener/removeEventListener(event, cb). Defaults to [].
   1.158 + *        For instance, the following would close the tooltip whenever the
   1.159 + *        toolbox selects a new tool and when a DOM node gets scrolled:
   1.160 + *        new Tooltip(doc, {
   1.161 + *          closeOnEvents: [
   1.162 + *            {emitter: toolbox, event: "select"},
   1.163 + *            {emitter: myContainer, event: "scroll", useCapture: true}
   1.164 + *          ]
   1.165 + *        });
   1.166 + *        - noAutoFocus {Boolean} Should the focus automatically go to the panel
   1.167 + *        when it opens. Defaults to true.
   1.168 + *
   1.169 + * Fires these events:
   1.170 + * - showing : just before the tooltip shows
   1.171 + * - shown : when the tooltip is shown
   1.172 + * - hiding : just before the tooltip closes
   1.173 + * - hidden : when the tooltip gets hidden
   1.174 + * - keypress : when any key gets pressed, with keyCode
   1.175 + */
   1.176 +function Tooltip(doc, options) {
   1.177 +  EventEmitter.decorate(this);
   1.178 +
   1.179 +  this.doc = doc;
   1.180 +  this.options = new OptionsStore({
   1.181 +    consumeOutsideClick: false,
   1.182 +    closeOnKeys: [ESCAPE_KEYCODE],
   1.183 +    noAutoFocus: true,
   1.184 +    closeOnEvents: []
   1.185 +  }, options);
   1.186 +  this.panel = PanelFactory.get(doc, this.options);
   1.187 +
   1.188 +  // Used for namedTimeouts in the mouseover handling
   1.189 +  this.uid = "tooltip-" + Date.now();
   1.190 +
   1.191 +  // Emit show/hide events
   1.192 +  for (let event of POPUP_EVENTS) {
   1.193 +    this["_onPopup" + event] = ((e) => {
   1.194 +      return () => this.emit(e);
   1.195 +    })(event);
   1.196 +    this.panel.addEventListener("popup" + event,
   1.197 +      this["_onPopup" + event], false);
   1.198 +  }
   1.199 +
   1.200 +  // Listen to keypress events to close the tooltip if configured to do so
   1.201 +  let win = this.doc.querySelector("window");
   1.202 +  this._onKeyPress = event => {
   1.203 +    this.emit("keypress", event.keyCode);
   1.204 +    if (this.options.get("closeOnKeys").indexOf(event.keyCode) !== -1) {
   1.205 +      if (!this.panel.hidden) {
   1.206 +        event.stopPropagation();
   1.207 +      }
   1.208 +      this.hide();
   1.209 +    }
   1.210 +  };
   1.211 +  win.addEventListener("keypress", this._onKeyPress, false);
   1.212 +
   1.213 +  // Listen to custom emitters' events to close the tooltip
   1.214 +  this.hide = this.hide.bind(this);
   1.215 +  let closeOnEvents = this.options.get("closeOnEvents");
   1.216 +  for (let {emitter, event, useCapture} of closeOnEvents) {
   1.217 +    for (let add of ["addEventListener", "on"]) {
   1.218 +      if (add in emitter) {
   1.219 +        emitter[add](event, this.hide, useCapture);
   1.220 +        break;
   1.221 +      }
   1.222 +    }
   1.223 +  }
   1.224 +}
   1.225 +
   1.226 +module.exports.Tooltip = Tooltip;
   1.227 +
   1.228 +Tooltip.prototype = {
   1.229 +  defaultPosition: "before_start",
   1.230 +  defaultOffsetX: 0, // px
   1.231 +  defaultOffsetY: 0, // px
   1.232 +  defaultShowDelay: 50, // ms
   1.233 +
   1.234 +  /**
   1.235 +   * Show the tooltip. It might be wise to append some content first if you
   1.236 +   * don't want the tooltip to be empty. You may access the content of the
   1.237 +   * tooltip by setting a XUL node to t.content.
   1.238 +   * @param {node} anchor
   1.239 +   *        Which node should the tooltip be shown on
   1.240 +   * @param {string} position [optional]
   1.241 +   *        Optional tooltip position. Defaults to before_start
   1.242 +   *        https://developer.mozilla.org/en-US/docs/XUL/PopupGuide/Positioning
   1.243 +   * @param {number} x, y [optional]
   1.244 +   *        The left and top offset coordinates, in pixels.
   1.245 +   */
   1.246 +  show: function(anchor,
   1.247 +    position = this.defaultPosition,
   1.248 +    x = this.defaultOffsetX,
   1.249 +    y = this.defaultOffsetY) {
   1.250 +    this.panel.hidden = false;
   1.251 +    this.panel.openPopup(anchor, position, x, y);
   1.252 +  },
   1.253 +
   1.254 +  /**
   1.255 +   * Hide the tooltip
   1.256 +   */
   1.257 +  hide: function() {
   1.258 +    this.panel.hidden = true;
   1.259 +    this.panel.hidePopup();
   1.260 +  },
   1.261 +
   1.262 +  isShown: function() {
   1.263 +    return this.panel.state !== "closed" && this.panel.state !== "hiding";
   1.264 +  },
   1.265 +
   1.266 +  setSize: function(width, height) {
   1.267 +    this.panel.sizeTo(width, height);
   1.268 +  },
   1.269 +
   1.270 +  /**
   1.271 +   * Empty the tooltip's content
   1.272 +   */
   1.273 +  empty: function() {
   1.274 +    while (this.panel.hasChildNodes()) {
   1.275 +      this.panel.removeChild(this.panel.firstChild);
   1.276 +    }
   1.277 +  },
   1.278 +
   1.279 +  /**
   1.280 +   * Gets this panel's visibility state.
   1.281 +   * @return boolean
   1.282 +   */
   1.283 +  isHidden: function() {
   1.284 +    return this.panel.state == "closed" || this.panel.state == "hiding";
   1.285 +  },
   1.286 +
   1.287 +  /**
   1.288 +   * Gets if this panel has any child nodes.
   1.289 +   * @return boolean
   1.290 +   */
   1.291 +  isEmpty: function() {
   1.292 +    return !this.panel.hasChildNodes();
   1.293 +  },
   1.294 +
   1.295 +  /**
   1.296 +   * Get rid of references and event listeners
   1.297 +   */
   1.298 +  destroy: function () {
   1.299 +    this.hide();
   1.300 +
   1.301 +    for (let event of POPUP_EVENTS) {
   1.302 +      this.panel.removeEventListener("popup" + event,
   1.303 +        this["_onPopup" + event], false);
   1.304 +    }
   1.305 +
   1.306 +    let win = this.doc.querySelector("window");
   1.307 +    win.removeEventListener("keypress", this._onKeyPress, false);
   1.308 +
   1.309 +    let closeOnEvents = this.options.get("closeOnEvents");
   1.310 +    for (let {emitter, event, useCapture} of closeOnEvents) {
   1.311 +      for (let remove of ["removeEventListener", "off"]) {
   1.312 +        if (remove in emitter) {
   1.313 +          emitter[remove](event, this.hide, useCapture);
   1.314 +          break;
   1.315 +        }
   1.316 +      }
   1.317 +    }
   1.318 +
   1.319 +    this.content = null;
   1.320 +
   1.321 +    if (this._basedNode) {
   1.322 +      this.stopTogglingOnHover();
   1.323 +    }
   1.324 +
   1.325 +    this.doc = null;
   1.326 +
   1.327 +    this.panel.remove();
   1.328 +    this.panel = null;
   1.329 +  },
   1.330 +
   1.331 +  /**
   1.332 +   * Show/hide the tooltip when the mouse hovers over particular nodes.
   1.333 +   *
   1.334 +   * 2 Ways to make this work:
   1.335 +   * - Provide a single node to attach the tooltip to, as the baseNode, and
   1.336 +   *   omit the second targetNodeCb argument
   1.337 +   * - Provide a baseNode that is the container of possibly numerous children
   1.338 +   *   elements that may receive a tooltip. In this case, provide the second
   1.339 +   *   targetNodeCb argument to decide wether or not a child should receive
   1.340 +   *   a tooltip.
   1.341 +   *
   1.342 +   * This works by tracking mouse movements on a base container node (baseNode)
   1.343 +   * and showing the tooltip when the mouse stops moving. The targetNodeCb
   1.344 +   * callback is used to know whether or not the particular element being
   1.345 +   * hovered over should indeed receive the tooltip. If you don't provide it
   1.346 +   * it's equivalent to a function that always returns true.
   1.347 +   *
   1.348 +   * Note that if you call this function a second time, it will itself call
   1.349 +   * stopTogglingOnHover before adding mouse tracking listeners again.
   1.350 +   *
   1.351 +   * @param {node} baseNode
   1.352 +   *        The container for all target nodes
   1.353 +   * @param {Function} targetNodeCb
   1.354 +   *        A function that accepts a node argument and returns true or false
   1.355 +   *        (or a promise that resolves or rejects) to signify if the tooltip
   1.356 +   *        should be shown on that node or not.
   1.357 +   *        Additionally, the function receives a second argument which is the
   1.358 +   *        tooltip instance itself, to be used to add/modify the content of the
   1.359 +   *        tooltip if needed. If omitted, the tooltip will be shown everytime.
   1.360 +   * @param {Number} showDelay
   1.361 +   *        An optional delay that will be observed before showing the tooltip.
   1.362 +   *        Defaults to this.defaultShowDelay.
   1.363 +   */
   1.364 +  startTogglingOnHover: function(baseNode, targetNodeCb, showDelay=this.defaultShowDelay) {
   1.365 +    if (this._basedNode) {
   1.366 +      this.stopTogglingOnHover();
   1.367 +    }
   1.368 +
   1.369 +    this._basedNode = baseNode;
   1.370 +    this._showDelay = showDelay;
   1.371 +    this._targetNodeCb = targetNodeCb || (() => true);
   1.372 +
   1.373 +    this._onBaseNodeMouseMove = this._onBaseNodeMouseMove.bind(this);
   1.374 +    this._onBaseNodeMouseLeave = this._onBaseNodeMouseLeave.bind(this);
   1.375 +
   1.376 +    baseNode.addEventListener("mousemove", this._onBaseNodeMouseMove, false);
   1.377 +    baseNode.addEventListener("mouseleave", this._onBaseNodeMouseLeave, false);
   1.378 +  },
   1.379 +
   1.380 +  /**
   1.381 +   * If the startTogglingOnHover function has been used previously, and you want
   1.382 +   * to get rid of this behavior, then call this function to remove the mouse
   1.383 +   * movement tracking
   1.384 +   */
   1.385 +  stopTogglingOnHover: function() {
   1.386 +    clearNamedTimeout(this.uid);
   1.387 +
   1.388 +    this._basedNode.removeEventListener("mousemove",
   1.389 +      this._onBaseNodeMouseMove, false);
   1.390 +    this._basedNode.removeEventListener("mouseleave",
   1.391 +      this._onBaseNodeMouseLeave, false);
   1.392 +
   1.393 +    this._basedNode = null;
   1.394 +    this._targetNodeCb = null;
   1.395 +    this._lastHovered = null;
   1.396 +  },
   1.397 +
   1.398 +  _onBaseNodeMouseMove: function(event) {
   1.399 +    if (event.target !== this._lastHovered) {
   1.400 +      this.hide();
   1.401 +      this._lastHovered = event.target;
   1.402 +      setNamedTimeout(this.uid, this._showDelay, () => {
   1.403 +        this.isValidHoverTarget(event.target).then(target => {
   1.404 +          this.show(target);
   1.405 +        });
   1.406 +      });
   1.407 +    }
   1.408 +  },
   1.409 +
   1.410 +  /**
   1.411 +   * Is the given target DOMNode a valid node for toggling the tooltip on hover.
   1.412 +   * This delegates to the user-defined _targetNodeCb callback.
   1.413 +   * @return a promise that resolves or rejects depending if the tooltip should
   1.414 +   * be shown or not. If it resolves, it does to the actual anchor to be used
   1.415 +   */
   1.416 +  isValidHoverTarget: function(target) {
   1.417 +    // Execute the user-defined callback which should return either true/false
   1.418 +    // or a promise that resolves or rejects
   1.419 +    let res = this._targetNodeCb(target, this);
   1.420 +
   1.421 +    // The callback can additionally return a DOMNode to replace the anchor of
   1.422 +    // the tooltip when shown
   1.423 +    if (res && res.then) {
   1.424 +      return res.then(arg => {
   1.425 +        return arg instanceof Ci.nsIDOMNode ? arg : target;
   1.426 +      }, () => {
   1.427 +        return false;
   1.428 +      });
   1.429 +    } else {
   1.430 +      let newTarget = res instanceof Ci.nsIDOMNode ? res : target;
   1.431 +      return res ? promise.resolve(newTarget) : promise.reject(false);
   1.432 +    }
   1.433 +  },
   1.434 +
   1.435 +  _onBaseNodeMouseLeave: function() {
   1.436 +    clearNamedTimeout(this.uid);
   1.437 +    this._lastHovered = null;
   1.438 +    this.hide();
   1.439 +  },
   1.440 +
   1.441 +  /**
   1.442 +   * Set the content of this tooltip. Will first empty the tooltip and then
   1.443 +   * append the new content element.
   1.444 +   * Consider using one of the set<type>Content() functions instead.
   1.445 +   * @param {node} content
   1.446 +   *        A node that can be appended in the tooltip XUL element
   1.447 +   */
   1.448 +  set content(content) {
   1.449 +    if (this.content == content) {
   1.450 +      return;
   1.451 +    }
   1.452 +
   1.453 +    this.empty();
   1.454 +    this.panel.removeAttribute("clamped-dimensions");
   1.455 +
   1.456 +    if (content) {
   1.457 +      this.panel.appendChild(content);
   1.458 +    }
   1.459 +  },
   1.460 +
   1.461 +  get content() {
   1.462 +    return this.panel.firstChild;
   1.463 +  },
   1.464 +
   1.465 +  /**
   1.466 +   * Sets some text as the content of this tooltip.
   1.467 +   *
   1.468 +   * @param {array} messages
   1.469 +   *        A list of text messages.
   1.470 +   * @param {string} messagesClass [optional]
   1.471 +   *        A style class for the text messages.
   1.472 +   * @param {string} containerClass [optional]
   1.473 +   *        A style class for the text messages container.
   1.474 +   * @param {boolean} isAlertTooltip [optional]
   1.475 +   *        Pass true to add an alert image for your tooltip.
   1.476 +   */
   1.477 +  setTextContent: function(
   1.478 +    {
   1.479 +      messages,
   1.480 +      messagesClass,
   1.481 +      containerClass,
   1.482 +      isAlertTooltip
   1.483 +    },
   1.484 +    extraButtons = []) {
   1.485 +    messagesClass = messagesClass || "default-tooltip-simple-text-colors";
   1.486 +    containerClass = containerClass || "default-tooltip-simple-text-colors";
   1.487 +
   1.488 +    let vbox = this.doc.createElement("vbox");
   1.489 +    vbox.className = "devtools-tooltip-simple-text-container " + containerClass;
   1.490 +    vbox.setAttribute("flex", "1");
   1.491 +
   1.492 +    for (let text of messages) {
   1.493 +      let description = this.doc.createElement("description");
   1.494 +      description.setAttribute("flex", "1");
   1.495 +      description.className = "devtools-tooltip-simple-text " + messagesClass;
   1.496 +      description.textContent = text;
   1.497 +      vbox.appendChild(description);
   1.498 +    }
   1.499 +
   1.500 +    for (let { label, className, command } of extraButtons) {
   1.501 +      let button = this.doc.createElement("button");
   1.502 +      button.className = className;
   1.503 +      button.setAttribute("label", label);
   1.504 +      button.addEventListener("command", command);
   1.505 +      vbox.appendChild(button);
   1.506 +    }
   1.507 +
   1.508 +    if (isAlertTooltip) {
   1.509 +      let hbox = this.doc.createElement("hbox");
   1.510 +      hbox.setAttribute("align", "start");
   1.511 +
   1.512 +      let alertImg = this.doc.createElement("image");
   1.513 +      alertImg.className = "devtools-tooltip-alert-icon";
   1.514 +      hbox.appendChild(alertImg);
   1.515 +      hbox.appendChild(vbox);
   1.516 +      this.content = hbox;
   1.517 +    } else {
   1.518 +      this.content = vbox;
   1.519 +    }
   1.520 +  },
   1.521 +
   1.522 +  /**
   1.523 +   * Fill the tooltip with a variables view, inspecting an object via its
   1.524 +   * corresponding object actor, as specified in the remote debugging protocol.
   1.525 +   *
   1.526 +   * @param {object} objectActor
   1.527 +   *        The value grip for the object actor.
   1.528 +   * @param {object} viewOptions [optional]
   1.529 +   *        Options for the variables view visualization.
   1.530 +   * @param {object} controllerOptions [optional]
   1.531 +   *        Options for the variables view controller.
   1.532 +   * @param {object} relayEvents [optional]
   1.533 +   *        A collection of events to listen on the variables view widget.
   1.534 +   *        For example, { fetched: () => ... }
   1.535 +   * @param {boolean} reuseCachedWidget [optional]
   1.536 +   *        Pass false to instantiate a brand new widget for this variable.
   1.537 +   *        Otherwise, if a variable was previously inspected, its widget
   1.538 +   *        will be reused.
   1.539 +   * @param {Toolbox} toolbox [optional]
   1.540 +   *        Pass the instance of the current toolbox if you want the variables
   1.541 +   *        view widget to allow highlighting and selection of DOM nodes
   1.542 +   */
   1.543 +  setVariableContent: function(
   1.544 +    objectActor,
   1.545 +    viewOptions = {},
   1.546 +    controllerOptions = {},
   1.547 +    relayEvents = {},
   1.548 +    extraButtons = [],
   1.549 +    toolbox = null) {
   1.550 +
   1.551 +    let vbox = this.doc.createElement("vbox");
   1.552 +    vbox.className = "devtools-tooltip-variables-view-box";
   1.553 +    vbox.setAttribute("flex", "1");
   1.554 +
   1.555 +    let innerbox = this.doc.createElement("vbox");
   1.556 +    innerbox.className = "devtools-tooltip-variables-view-innerbox";
   1.557 +    innerbox.setAttribute("flex", "1");
   1.558 +    vbox.appendChild(innerbox);
   1.559 +
   1.560 +    for (let { label, className, command } of extraButtons) {
   1.561 +      let button = this.doc.createElement("button");
   1.562 +      button.className = className;
   1.563 +      button.setAttribute("label", label);
   1.564 +      button.addEventListener("command", command);
   1.565 +      vbox.appendChild(button);
   1.566 +    }
   1.567 +
   1.568 +    let widget = new VariablesView(innerbox, viewOptions);
   1.569 +
   1.570 +    // If a toolbox was provided, link it to the vview
   1.571 +    if (toolbox) {
   1.572 +      widget.toolbox = toolbox;
   1.573 +    }
   1.574 +
   1.575 +    // Analyzing state history isn't useful with transient object inspectors.
   1.576 +    widget.commitHierarchy = () => {};
   1.577 +
   1.578 +    for (let e in relayEvents) widget.on(e, relayEvents[e]);
   1.579 +    VariablesViewController.attach(widget, controllerOptions);
   1.580 +
   1.581 +    // Some of the view options are allowed to change between uses.
   1.582 +    widget.searchPlaceholder = viewOptions.searchPlaceholder;
   1.583 +    widget.searchEnabled = viewOptions.searchEnabled;
   1.584 +
   1.585 +    // Use the object actor's grip to display it as a variable in the widget.
   1.586 +    // The controller options are allowed to change between uses.
   1.587 +    widget.controller.setSingleVariable(
   1.588 +      { objectActor: objectActor }, controllerOptions);
   1.589 +
   1.590 +    this.content = vbox;
   1.591 +    this.panel.setAttribute("clamped-dimensions", "");
   1.592 +  },
   1.593 +
   1.594 +  /**
   1.595 +   * Uses the provided inspectorFront's getImageDataFromURL method to resolve
   1.596 +   * the relative URL on the server-side, in the page context, and then sets the
   1.597 +   * tooltip content with the resulting image just like |setImageContent| does.
   1.598 +   * @return a promise that resolves when the image is shown in the tooltip or
   1.599 +   * resolves when the broken image tooltip content is ready, but never rejects.
   1.600 +   */
   1.601 +  setRelativeImageContent: Task.async(function*(imageUrl, inspectorFront, maxDim) {
   1.602 +    if (imageUrl.startsWith("data:")) {
   1.603 +      // If the imageUrl already is a data-url, save ourselves a round-trip
   1.604 +      this.setImageContent(imageUrl, {maxDim: maxDim});
   1.605 +    } else if (inspectorFront) {
   1.606 +      try {
   1.607 +        let {data, size} = yield inspectorFront.getImageDataFromURL(imageUrl, maxDim);
   1.608 +        size.maxDim = maxDim;
   1.609 +        let str = yield data.string();
   1.610 +        this.setImageContent(str, size);
   1.611 +      } catch (e) {
   1.612 +        this.setBrokenImageContent();
   1.613 +      }
   1.614 +    }
   1.615 +  }),
   1.616 +
   1.617 +  /**
   1.618 +   * Fill the tooltip with a message explaining the the image is missing
   1.619 +   */
   1.620 +  setBrokenImageContent: function() {
   1.621 +    this.setTextContent({
   1.622 +      messages: [l10n.strings.GetStringFromName("previewTooltip.image.brokenImage")]
   1.623 +    });
   1.624 +  },
   1.625 +
   1.626 +  /**
   1.627 +   * Fill the tooltip with an image and add the image dimension at the bottom.
   1.628 +   *
   1.629 +   * Only use this for absolute URLs that can be queried from the devtools
   1.630 +   * client-side. For relative URLs, use |setRelativeImageContent|.
   1.631 +   *
   1.632 +   * @param {string} imageUrl
   1.633 +   *        The url to load the image from
   1.634 +   * @param {Object} options
   1.635 +   *        The following options are supported:
   1.636 +   *        - resized : whether or not the image identified by imageUrl has been
   1.637 +   *        resized before this function was called.
   1.638 +   *        - naturalWidth/naturalHeight : the original size of the image before
   1.639 +   *        it was resized, if if was resized before this function was called.
   1.640 +   *        If not provided, will be measured on the loaded image.
   1.641 +   *        - maxDim : if the image should be resized before being shown, pass
   1.642 +   *        a number here
   1.643 +   */
   1.644 +  setImageContent: function(imageUrl, options={}) {
   1.645 +    if (!imageUrl) {
   1.646 +      return;
   1.647 +    }
   1.648 +
   1.649 +    // Main container
   1.650 +    let vbox = this.doc.createElement("vbox");
   1.651 +    vbox.setAttribute("align", "center");
   1.652 +
   1.653 +    // Display the image
   1.654 +    let image = this.doc.createElement("image");
   1.655 +    image.setAttribute("src", imageUrl);
   1.656 +    if (options.maxDim) {
   1.657 +      image.style.maxWidth = options.maxDim + "px";
   1.658 +      image.style.maxHeight = options.maxDim + "px";
   1.659 +    }
   1.660 +    vbox.appendChild(image);
   1.661 +
   1.662 +    // Dimension label
   1.663 +    let label = this.doc.createElement("label");
   1.664 +    label.classList.add("devtools-tooltip-caption");
   1.665 +    label.classList.add("theme-comment");
   1.666 +    if (options.naturalWidth && options.naturalHeight) {
   1.667 +      label.textContent = this._getImageDimensionLabel(options.naturalWidth,
   1.668 +        options.naturalHeight);
   1.669 +    } else {
   1.670 +      // If no dimensions were provided, load the image to get them
   1.671 +      label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
   1.672 +      let imgObj = new this.doc.defaultView.Image();
   1.673 +      imgObj.src = imageUrl;
   1.674 +      imgObj.onload = () => {
   1.675 +        imgObj.onload = null;
   1.676 +        label.textContent = this._getImageDimensionLabel(imgObj.naturalWidth,
   1.677 +          imgObj.naturalHeight);
   1.678 +      }
   1.679 +    }
   1.680 +    vbox.appendChild(label);
   1.681 +
   1.682 +    this.content = vbox;
   1.683 +  },
   1.684 +
   1.685 +  _getImageDimensionLabel: (w, h) => w + " x " + h,
   1.686 +
   1.687 +  /**
   1.688 +   * Fill the tooltip with a new instance of the spectrum color picker widget
   1.689 +   * initialized with the given color, and return a promise that resolves to
   1.690 +   * the instance of spectrum
   1.691 +   */
   1.692 +  setColorPickerContent: function(color) {
   1.693 +    let def = promise.defer();
   1.694 +
   1.695 +    // Create an iframe to contain spectrum
   1.696 +    let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
   1.697 +    iframe.setAttribute("transparent", true);
   1.698 +    iframe.setAttribute("width", "210");
   1.699 +    iframe.setAttribute("height", "216");
   1.700 +    iframe.setAttribute("flex", "1");
   1.701 +    iframe.setAttribute("class", "devtools-tooltip-iframe");
   1.702 +
   1.703 +    let panel = this.panel;
   1.704 +    let xulWin = this.doc.ownerGlobal;
   1.705 +
   1.706 +    // Wait for the load to initialize spectrum
   1.707 +    function onLoad() {
   1.708 +      iframe.removeEventListener("load", onLoad, true);
   1.709 +      let win = iframe.contentWindow.wrappedJSObject;
   1.710 +
   1.711 +      let container = win.document.getElementById("spectrum");
   1.712 +      let spectrum = new Spectrum(container, color);
   1.713 +
   1.714 +      function finalizeSpectrum() {
   1.715 +        spectrum.show();
   1.716 +        def.resolve(spectrum);
   1.717 +      }
   1.718 +
   1.719 +      // Finalize spectrum's init when the tooltip becomes visible
   1.720 +      if (panel.state == "open") {
   1.721 +        finalizeSpectrum();
   1.722 +      }
   1.723 +      else {
   1.724 +        panel.addEventListener("popupshown", function shown() {
   1.725 +          panel.removeEventListener("popupshown", shown, true);
   1.726 +          finalizeSpectrum();
   1.727 +        }, true);
   1.728 +      }
   1.729 +    }
   1.730 +    iframe.addEventListener("load", onLoad, true);
   1.731 +    iframe.setAttribute("src", SPECTRUM_FRAME);
   1.732 +
   1.733 +    // Put the iframe in the tooltip
   1.734 +    this.content = iframe;
   1.735 +
   1.736 +    return def.promise;
   1.737 +  },
   1.738 +
   1.739 +  /**
   1.740 +   * Set the content of the tooltip to be the result of CSSTransformPreviewer.
   1.741 +   * Meaning a canvas previewing a css transformation.
   1.742 +   *
   1.743 +   * @param {String} transform
   1.744 +   *        The CSS transform value (e.g. "rotate(45deg) translateX(50px)")
   1.745 +   * @param {PageStyleActor} pageStyle
   1.746 +   *        An instance of the PageStyleActor that will be used to retrieve
   1.747 +   *        computed styles
   1.748 +   * @param {NodeActor} node
   1.749 +   *        The NodeActor for the currently selected node
   1.750 +   * @return A promise that resolves when the tooltip content is ready, or
   1.751 +   *         rejects if no transform is provided or the transform is invalid
   1.752 +   */
   1.753 +  setCssTransformContent: Task.async(function*(transform, pageStyle, node) {
   1.754 +    if (!transform) {
   1.755 +      throw "Missing transform";
   1.756 +    }
   1.757 +
   1.758 +    // Look into the computed styles to find the width and height and possibly
   1.759 +    // the origin if it hadn't been provided
   1.760 +    let styles = yield pageStyle.getComputed(node, {
   1.761 +      filter: "user",
   1.762 +      markMatched: false,
   1.763 +      onlyMatched: false
   1.764 +    });
   1.765 +
   1.766 +    let origin = styles["transform-origin"].value;
   1.767 +    let width = parseInt(styles["width"].value);
   1.768 +    let height = parseInt(styles["height"].value);
   1.769 +
   1.770 +    let root = this.doc.createElementNS(XHTML_NS, "div");
   1.771 +    let previewer = new CSSTransformPreviewer(root);
   1.772 +    this.content = root;
   1.773 +    if (!previewer.preview(transform, origin, width, height)) {
   1.774 +      throw "Invalid transform";
   1.775 +    }
   1.776 +  }),
   1.777 +
   1.778 +  /**
   1.779 +   * Set the content of the tooltip to display a font family preview.
   1.780 +   * This is based on Lea Verou's Dablet. See https://github.com/LeaVerou/dabblet
   1.781 +   * for more info.
   1.782 +   * @param {String} font The font family value.
   1.783 +   */
   1.784 +  setFontFamilyContent: function(font) {
   1.785 +    if (!font) {
   1.786 +      return;
   1.787 +    }
   1.788 +
   1.789 +    // Main container
   1.790 +    let vbox = this.doc.createElement("vbox");
   1.791 +    vbox.setAttribute("flex", "1");
   1.792 +
   1.793 +    // Display the font family previewer
   1.794 +    let previewer = this.doc.createElement("description");
   1.795 +    previewer.setAttribute("flex", "1");
   1.796 +    previewer.style.fontFamily = font;
   1.797 +    previewer.classList.add("devtools-tooltip-font-previewer-text");
   1.798 +    previewer.textContent = FONT_FAMILY_PREVIEW_TEXT;
   1.799 +    vbox.appendChild(previewer);
   1.800 +
   1.801 +    this.content = vbox;
   1.802 +  }
   1.803 +};
   1.804 +
   1.805 +/**
   1.806 + * Base class for all (color, gradient, ...)-swatch based value editors inside
   1.807 + * tooltips
   1.808 + *
   1.809 + * @param {XULDocument} doc
   1.810 + */
   1.811 +function SwatchBasedEditorTooltip(doc) {
   1.812 +  // Creating a tooltip instance
   1.813 +  // This one will consume outside clicks as it makes more sense to let the user
   1.814 +  // close the tooltip by clicking out
   1.815 +  // It will also close on <escape> and <enter>
   1.816 +  this.tooltip = new Tooltip(doc, {
   1.817 +    consumeOutsideClick: true,
   1.818 +    closeOnKeys: [ESCAPE_KEYCODE, RETURN_KEYCODE],
   1.819 +    noAutoFocus: false
   1.820 +  });
   1.821 +
   1.822 +  // By default, swatch-based editor tooltips revert value change on <esc> and
   1.823 +  // commit value change on <enter>
   1.824 +  this._onTooltipKeypress = (event, code) => {
   1.825 +    if (code === ESCAPE_KEYCODE) {
   1.826 +      this.revert();
   1.827 +    } else if (code === RETURN_KEYCODE) {
   1.828 +      this.commit();
   1.829 +    }
   1.830 +  };
   1.831 +  this.tooltip.on("keypress", this._onTooltipKeypress);
   1.832 +
   1.833 +  // All target swatches are kept in a map, indexed by swatch DOM elements
   1.834 +  this.swatches = new Map();
   1.835 +
   1.836 +  // When a swatch is clicked, and for as long as the tooltip is shown, the
   1.837 +  // activeSwatch property will hold the reference to the swatch DOM element
   1.838 +  // that was clicked
   1.839 +  this.activeSwatch = null;
   1.840 +
   1.841 +  this._onSwatchClick = this._onSwatchClick.bind(this);
   1.842 +}
   1.843 +
   1.844 +SwatchBasedEditorTooltip.prototype = {
   1.845 +  show: function() {
   1.846 +    if (this.activeSwatch) {
   1.847 +      this.tooltip.show(this.activeSwatch, "topcenter bottomleft");
   1.848 +      this.tooltip.once("hidden", () => {
   1.849 +        if (!this.eyedropperOpen) {
   1.850 +          this.activeSwatch = null;
   1.851 +        }
   1.852 +      });
   1.853 +    }
   1.854 +  },
   1.855 +
   1.856 +  hide: function() {
   1.857 +    this.tooltip.hide();
   1.858 +  },
   1.859 +
   1.860 +  /**
   1.861 +   * Add a new swatch DOM element to the list of swatch elements this editor
   1.862 +   * tooltip knows about. That means from now on, clicking on that swatch will
   1.863 +   * toggle the editor.
   1.864 +   *
   1.865 +   * @param {node} swatchEl
   1.866 +   *        The element to add
   1.867 +   * @param {object} callbacks
   1.868 +   *        Callbacks that will be executed when the editor wants to preview a
   1.869 +   *        value change, or revert a change, or commit a change.
   1.870 +   *        - onPreview: will be called when one of the sub-classes calls preview
   1.871 +   *        - onRevert: will be called when the user ESCapes out of the tooltip
   1.872 +   *        - onCommit: will be called when the user presses ENTER or clicks
   1.873 +   *        outside the tooltip. If the user-defined onCommit returns a value,
   1.874 +   *        it will be used to replace originalValue, so that the swatch-based
   1.875 +   *        tooltip always knows what is the current originalValue and can use
   1.876 +   *        it when reverting
   1.877 +   * @param {object} originalValue
   1.878 +   *        The original value before the editor in the tooltip makes changes
   1.879 +   *        This can be of any type, and will be passed, as is, in the revert
   1.880 +   *        callback
   1.881 +   */
   1.882 +  addSwatch: function(swatchEl, callbacks={}, originalValue) {
   1.883 +    if (!callbacks.onPreview) callbacks.onPreview = function() {};
   1.884 +    if (!callbacks.onRevert) callbacks.onRevert = function() {};
   1.885 +    if (!callbacks.onCommit) callbacks.onCommit = function() {};
   1.886 +
   1.887 +    this.swatches.set(swatchEl, {
   1.888 +      callbacks: callbacks,
   1.889 +      originalValue: originalValue
   1.890 +    });
   1.891 +    swatchEl.addEventListener("click", this._onSwatchClick, false);
   1.892 +  },
   1.893 +
   1.894 +  removeSwatch: function(swatchEl) {
   1.895 +    if (this.swatches.has(swatchEl)) {
   1.896 +      if (this.activeSwatch === swatchEl) {
   1.897 +        this.hide();
   1.898 +        this.activeSwatch = null;
   1.899 +      }
   1.900 +      swatchEl.removeEventListener("click", this._onSwatchClick, false);
   1.901 +      this.swatches.delete(swatchEl);
   1.902 +    }
   1.903 +  },
   1.904 +
   1.905 +  _onSwatchClick: function(event) {
   1.906 +    let swatch = this.swatches.get(event.target);
   1.907 +    if (swatch) {
   1.908 +      this.activeSwatch = event.target;
   1.909 +      this.show();
   1.910 +      event.stopPropagation();
   1.911 +    }
   1.912 +  },
   1.913 +
   1.914 +  /**
   1.915 +   * Not called by this parent class, needs to be taken care of by sub-classes
   1.916 +   */
   1.917 +  preview: function(value) {
   1.918 +    if (this.activeSwatch) {
   1.919 +      let swatch = this.swatches.get(this.activeSwatch);
   1.920 +      swatch.callbacks.onPreview(value);
   1.921 +    }
   1.922 +  },
   1.923 +
   1.924 +  /**
   1.925 +   * This parent class only calls this on <esc> keypress
   1.926 +   */
   1.927 +  revert: function() {
   1.928 +    if (this.activeSwatch) {
   1.929 +      let swatch = this.swatches.get(this.activeSwatch);
   1.930 +      swatch.callbacks.onRevert(swatch.originalValue);
   1.931 +    }
   1.932 +  },
   1.933 +
   1.934 +  /**
   1.935 +   * This parent class only calls this on <enter> keypress
   1.936 +   */
   1.937 +  commit: function() {
   1.938 +    if (this.activeSwatch) {
   1.939 +      let swatch = this.swatches.get(this.activeSwatch);
   1.940 +      let newValue = swatch.callbacks.onCommit();
   1.941 +      if (typeof newValue !== "undefined") {
   1.942 +        swatch.originalValue = newValue;
   1.943 +      }
   1.944 +    }
   1.945 +  },
   1.946 +
   1.947 +  destroy: function() {
   1.948 +    this.swatches.clear();
   1.949 +    this.activeSwatch = null;
   1.950 +    this.tooltip.off("keypress", this._onTooltipKeypress);
   1.951 +    this.tooltip.destroy();
   1.952 +  }
   1.953 +};
   1.954 +
   1.955 +/**
   1.956 + * The swatch color picker tooltip class is a specific class meant to be used
   1.957 + * along with output-parser's generated color swatches.
   1.958 + * It extends the parent SwatchBasedEditorTooltip class.
   1.959 + * It just wraps a standard Tooltip and sets its content with an instance of a
   1.960 + * color picker.
   1.961 + *
   1.962 + * @param {XULDocument} doc
   1.963 + */
   1.964 +function SwatchColorPickerTooltip(doc) {
   1.965 +  SwatchBasedEditorTooltip.call(this, doc);
   1.966 +
   1.967 +  // Creating a spectrum instance. this.spectrum will always be a promise that
   1.968 +  // resolves to the spectrum instance
   1.969 +  this.spectrum = this.tooltip.setColorPickerContent([0, 0, 0, 1]);
   1.970 +  this._onSpectrumColorChange = this._onSpectrumColorChange.bind(this);
   1.971 +  this._openEyeDropper = this._openEyeDropper.bind(this);
   1.972 +}
   1.973 +
   1.974 +module.exports.SwatchColorPickerTooltip = SwatchColorPickerTooltip;
   1.975 +
   1.976 +SwatchColorPickerTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.prototype, {
   1.977 +  /**
   1.978 +   * Overriding the SwatchBasedEditorTooltip.show function to set spectrum's
   1.979 +   * color.
   1.980 +   */
   1.981 +  show: function() {
   1.982 +    // Call then parent class' show function
   1.983 +    SwatchBasedEditorTooltip.prototype.show.call(this);
   1.984 +    // Then set spectrum's color and listen to color changes to preview them
   1.985 +    if (this.activeSwatch) {
   1.986 +      this.currentSwatchColor = this.activeSwatch.nextSibling;
   1.987 +      let swatch = this.swatches.get(this.activeSwatch);
   1.988 +      let color = this.activeSwatch.style.backgroundColor;
   1.989 +      this.spectrum.then(spectrum => {
   1.990 +        spectrum.off("changed", this._onSpectrumColorChange);
   1.991 +        spectrum.rgb = this._colorToRgba(color);
   1.992 +        spectrum.on("changed", this._onSpectrumColorChange);
   1.993 +        spectrum.updateUI();
   1.994 +      });
   1.995 +    }
   1.996 +
   1.997 +    let tooltipDoc = this.tooltip.content.contentDocument;
   1.998 +    let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
   1.999 +    eyeButton.addEventListener("click", this._openEyeDropper);
  1.1000 +  },
  1.1001 +
  1.1002 +  _onSpectrumColorChange: function(event, rgba, cssColor) {
  1.1003 +    this._selectColor(cssColor);
  1.1004 +  },
  1.1005 +
  1.1006 +  _selectColor: function(color) {
  1.1007 +    if (this.activeSwatch) {
  1.1008 +      this.activeSwatch.style.backgroundColor = color;
  1.1009 +      this.currentSwatchColor.textContent = color;
  1.1010 +      this.preview(color);
  1.1011 +    }
  1.1012 +  },
  1.1013 +
  1.1014 + _openEyeDropper: function() {
  1.1015 +    let chromeWindow = this.tooltip.doc.defaultView.top;
  1.1016 +    let windowType = chromeWindow.document.documentElement
  1.1017 +                     .getAttribute("windowtype");
  1.1018 +    let toolboxWindow;
  1.1019 +    if (windowType != "navigator:browser") {
  1.1020 +      // this means the toolbox is in a seperate window. We need to make
  1.1021 +      // sure we'll be inspecting the browser window instead
  1.1022 +      toolboxWindow = chromeWindow;
  1.1023 +      chromeWindow = Services.wm.getMostRecentWindow("navigator:browser");
  1.1024 +      chromeWindow.focus();
  1.1025 +    }
  1.1026 +    let dropper = new Eyedropper(chromeWindow, { copyOnSelect: false });
  1.1027 +
  1.1028 +    dropper.once("select", (event, color) => {
  1.1029 +      if (toolboxWindow) {
  1.1030 +        toolboxWindow.focus();
  1.1031 +      }
  1.1032 +      this._selectColor(color);
  1.1033 +    });
  1.1034 +
  1.1035 +    dropper.once("destroy", () => {
  1.1036 +      this.eyedropperOpen = false;
  1.1037 +      this.activeSwatch = null;
  1.1038 +    })
  1.1039 +
  1.1040 +    dropper.open();
  1.1041 +    this.eyedropperOpen = true;
  1.1042 +
  1.1043 +    // close the colorpicker tooltip so that only the eyedropper is open.
  1.1044 +    this.hide();
  1.1045 +
  1.1046 +    this.tooltip.emit("eyedropper-opened", dropper);
  1.1047 +  },
  1.1048 +
  1.1049 +  _colorToRgba: function(color) {
  1.1050 +    color = new colorUtils.CssColor(color);
  1.1051 +    let rgba = color._getRGBATuple();
  1.1052 +    return [rgba.r, rgba.g, rgba.b, rgba.a];
  1.1053 +  },
  1.1054 +
  1.1055 +  destroy: function() {
  1.1056 +    SwatchBasedEditorTooltip.prototype.destroy.call(this);
  1.1057 +    this.currentSwatchColor = null;
  1.1058 +    this.spectrum.then(spectrum => {
  1.1059 +      spectrum.off("changed", this._onSpectrumColorChange);
  1.1060 +      spectrum.destroy();
  1.1061 +    });
  1.1062 +  }
  1.1063 +});
  1.1064 +
  1.1065 +/**
  1.1066 + * Internal util, checks whether a css declaration is a gradient
  1.1067 + */
  1.1068 +function isGradientRule(property, value) {
  1.1069 +  return (property === "background" || property === "background-image") &&
  1.1070 +    value.match(GRADIENT_RE);
  1.1071 +}
  1.1072 +
  1.1073 +/**
  1.1074 + * Internal util, checks whether a css declaration is a color
  1.1075 + */
  1.1076 +function isColorOnly(property, value) {
  1.1077 +  return property === "background-color" ||
  1.1078 +         property === "color" ||
  1.1079 +         property.match(BORDERCOLOR_RE);
  1.1080 +}
  1.1081 +
  1.1082 +/**
  1.1083 + * L10N utility class
  1.1084 + */
  1.1085 +function L10N() {}
  1.1086 +L10N.prototype = {};
  1.1087 +
  1.1088 +let l10n = new L10N();
  1.1089 +
  1.1090 +loader.lazyGetter(L10N.prototype, "strings", () => {
  1.1091 +  return Services.strings.createBundle(
  1.1092 +    "chrome://browser/locale/devtools/inspector.properties");
  1.1093 +});

mercurial