home *** CD-ROM | disk | FTP | other *** search
/ Freelog 112 / FreelogNo112-NovembreDecembre2012.iso / Multimedia / Songbird / Songbird_2.0.0-2311_windows-i686-msvc8.exe / jsmodules / DOMUtils.jsm < prev    next >
Text File  |  2012-06-08  |  33KB  |  980 lines

  1. /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* vim: set sw=2 :miv */
  3. /*
  4.  *=BEGIN SONGBIRD GPL
  5.  *
  6.  * This file is part of the Songbird web player.
  7.  *
  8.  * Copyright(c) 2005-2010 POTI, Inc.
  9.  * http://www.songbirdnest.com
  10.  *
  11.  * This file may be licensed under the terms of of the
  12.  * GNU General Public License Version 2 (the ``GPL'').
  13.  *
  14.  * Software distributed under the License is distributed
  15.  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
  16.  * express or implied. See the GPL for the specific language
  17.  * governing rights and limitations.
  18.  *
  19.  * You should have received a copy of the GPL along with this
  20.  * program. If not, go to http://www.gnu.org/licenses/gpl.html
  21.  * or write to the Free Software Foundation, Inc.,
  22.  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  23.  *
  24.  *=END SONGBIRD GPL
  25.  */
  26.  
  27. /**
  28.  * \file  DOMUtils.jsm
  29.  * \brief Javascript source for the DOM utility services.
  30.  */
  31.  
  32. //------------------------------------------------------------------------------
  33. //
  34. // DOM utility JSM configuration.
  35. //
  36. //------------------------------------------------------------------------------
  37.  
  38. EXPORTED_SYMBOLS = [ "DOMUtils",
  39.                      "sbDOMHighlighter",
  40.                      "DOMEventListenerSet",
  41.                      "sbColorUtils" ];
  42.  
  43.  
  44. //------------------------------------------------------------------------------
  45. //
  46. // DOM utility defs.
  47. //
  48. //------------------------------------------------------------------------------
  49.  
  50. const Cc = Components.classes;
  51. const Ci = Components.interfaces;
  52. const Cr = Components.results
  53. const Cu = Components.utils
  54.  
  55.  
  56. //------------------------------------------------------------------------------
  57. //
  58. // DOM utility services.
  59. //
  60. //------------------------------------------------------------------------------
  61.  
  62. var DOMUtils = {
  63.   //----------------------------------------------------------------------------
  64.   //
  65.   // DOM utility services.
  66.   //
  67.   //----------------------------------------------------------------------------
  68.  
  69.   /**
  70.    * Load a document using the URI string specified by aDocumentURI and return
  71.    * it as an nsIDOMDocument.
  72.    *
  73.    * \param aDocumentURI        Document URI string.
  74.    *
  75.    * \return                    An nsIDOMDocument document.
  76.    */
  77.  
  78.   loadDocument: function DOMUtils_loadDocument(aDocumentURI) {
  79.     // Open the document.
  80.     var ioSvc = Cc["@mozilla.org/network/io-service;1"]
  81.                   .getService(Ci.nsIIOService);
  82.     var channel = ioSvc.newChannel(aDocumentURI, null, null);
  83.     var inputStream = channel.open();
  84.  
  85.     // Load and return the document.
  86.     var domParser = Cc["@mozilla.org/xmlextras/domparser;1"]
  87.                       .createInstance(Ci.nsIDOMParser);
  88.     return domParser.parseFromStream(inputStream,
  89.                                      null,
  90.                                      channel.contentLength,
  91.                                      "text/xml");
  92.   },
  93.  
  94.  
  95.   /**
  96.    *   Import the child elements of the parent element with the ID specified by
  97.    * aSrcParentID within the document specified by aSrcDocument.  Import them
  98.    * as child elements of the node specified by aDstNode.
  99.    *   For each imported child, set attributes as specified by the
  100.    * aChildAttrList object.  Use the name of each field in aChildAttrList as the
  101.    * name of an attribute, and set the attribute value to the field value.
  102.    *   Note that only child elements are imported.  Non-element child nodes such
  103.    * as text nodes are are not imported.  The descendents of all imported child
  104.    * elements are also imported, including non-element descendents.
  105.    *
  106.    * \param aDstNode            Destination node into which to import child
  107.    *                            elements.
  108.    * \param aSrcDocument        Document from which to import child elements.
  109.    * \param aSrcParentID        ID of parent node from which to import child
  110.    *                            elements.
  111.    * \param aChildAttrList      List of child attributes to set.
  112.    */
  113.  
  114.   importChildElements: function DOMUtils_importChildElements(aDstNode,
  115.                                                              aSrcDocument,
  116.                                                              aSrcParentID,
  117.                                                              aChildAttrList) {
  118.     // Get the destination document and the list of source children.
  119.     var dstDoc = aDstNode.ownerDocument;
  120.     var srcChildList = aSrcDocument.getElementById(aSrcParentID).childNodes;
  121.  
  122.     // Import the source elements.
  123.     for (var i = 0; i < srcChildList.length; i++) {
  124.       // Get the next source child.  Skip if not an element.
  125.       var srcChild = srcChildList[i];
  126.       if (srcChild.nodeType != Ci.nsIDOMNode.ELEMENT_NODE)
  127.         continue;
  128.  
  129.       // Import the source child into the destination document.
  130.       var dstChild = dstDoc.importNode(srcChild, true);
  131.  
  132.       // Add the child to the destination node.
  133.       aDstNode.appendChild(dstChild);
  134.  
  135.       // Add the child attributes.
  136.       for (var attrName in aChildAttrList) {
  137.         dstChild.setAttribute(attrName, aChildAttrList[attrName]);
  138.       }
  139.     }
  140.   },
  141.  
  142.  
  143.   /**
  144.    * Copy the attributes specified by aAttributeList from the element specified
  145.    * by aSrcElem to the element specified by aDstElem.  If an attribute is not
  146.    * set in aSrcElem, do not change that attribute in aDstElem unless
  147.    * aRemoveAttributes is true; if aRemoveAttributes is true, remove the
  148.    * attribute from aDstElem.
  149.    *
  150.    * \param aSrcElem            Source element.
  151.    * \param aDstElem            Destination element.
  152.    * \param aAttributeList      Array of attribute names.
  153.    * \param aRemoveAttributes   Remove attributes from aDstElem that aren't set
  154.    *                            in aSrcElem.
  155.    */
  156.  
  157.   copyAttributes: function DOMUtils_copyAttributes(aSrcElem,
  158.                                                    aDstElem,
  159.                                                    aAttributeList,
  160.                                                    aRemoveAttributes) {
  161.     // Copy the attributes.
  162.     for (var i = 0; i < aAttributeList.length; i++) {
  163.       // Get attribute name.
  164.       var attribute = aAttributeList[i];
  165.  
  166.       // If source element does not have the attribute, do nothing or remove
  167.       // the attribute as specified.
  168.       if (!aSrcElem.hasAttribute(attribute)) {
  169.         if (aRemoveAttributes)
  170.           aDstElem.removeAttribute(attribute);
  171.         continue;
  172.       }
  173.  
  174.       // Copy the attribute from the source element to the destination if the
  175.       // attribute values differ.
  176.       var srcAttributeVal = aSrcElem.getAttribute(attribute);
  177.       var dstAttributeVal = aDstElem.getAttribute(attribute);
  178.       if (srcAttributeVal != dstAttributeVal)
  179.         aDstElem.setAttribute(attribute, srcAttributeVal);
  180.     }
  181.   },
  182.  
  183.  
  184.   /**
  185.    * Search the children of the element specified by aRootElem for elements
  186.    * with the attribute specified by aAttrName set to the value specified by
  187.    * aAttrValue.  Return an array containing all matching elements.
  188.    * If aAttrValue is not specified, return all elements with the specified
  189.    * attribute, regardless of value.
  190.    *
  191.    * \param aRootElem           Root element from which to start searching.
  192.    * \param aAttrName           Attribute name to search for.
  193.    * \param aAttrValue          Attribute value to search for.
  194.    *
  195.    * \return                    Array containing found elements.
  196.    */
  197.  
  198.   getElementsByAttribute: function DOMUtils_getElementsByAttribute(aRootElem,
  199.                                                                    aAttrName,
  200.                                                                    aAttrValue) {
  201.     // Start searching for elements from the root.
  202.     var matchList = [];
  203.     this._getElementsByAttribute(aRootElem, aAttrName, aAttrValue, matchList);
  204.  
  205.     return matchList;
  206.   },
  207.  
  208.   _getElementsByAttribute: function
  209.                              DOMUtils__getElementsByAttribute(aRootElem,
  210.                                                               aAttrName,
  211.                                                               aAttrValue,
  212.                                                               aMatchList) {
  213.     // Search each of the children.
  214.     var childList = aRootElem.childNodes;
  215.     for (var i = 0; i < childList.length; i++) {
  216.       // Check the child node for a match.
  217.       var child = childList[i];
  218.       if (child.hasAttributes() && child.hasAttribute(aAttrName)) {
  219.         if (!aAttrValue || (child.getAttribute(aAttrName) == aAttrValue))
  220.           aMatchList.push(child);
  221.       }
  222.  
  223.       // Check each of the child's children.
  224.       this._getElementsByAttribute(child, aAttrName, aAttrValue, aMatchList);
  225.     }
  226.   },
  227.  
  228.  
  229.   /**
  230.    * Encode the text specified by aText for use in an XML document and return
  231.    * the result (e.g., encode "this & that" to "this & that".
  232.    *
  233.    * \param aText               Text to encode.
  234.    *
  235.    * \return                    Text encoded for XML.
  236.    */
  237.  
  238.   encodeTextForXML: function DOMUtils_encodeTextForXML(aText) {
  239.     // Create an XML text node.
  240.     var xmlDocument = Cc["@mozilla.org/xml/xml-document;1"]
  241.                         .createInstance(Ci.nsIDOMDocument);
  242.     var xmlTextNode = xmlDocument.createTextNode(aText);
  243.  
  244.     // Produce the encoded XML text by serializing the XML text node.
  245.     var domSerializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"]
  246.                           .createInstance(Ci.nsIDOMSerializer);
  247.     var encodedXMLText = domSerializer.serializeToString(xmlTextNode);
  248.  
  249.     return encodedXMLText;
  250.   },
  251.  
  252.  
  253.   /**
  254.    * Rebind the XBL for the element specified by aElem.  This may be required if
  255.    * a change to the element causes its XBL binding to change (e.g., changing
  256.    * the element class attribute).
  257.    *
  258.    * Prior to xulrunner 1.9.2, XBL rebinding happened automatically.  However,
  259.    * this is no longer the case with 1.9.2, and the element must be explicitly
  260.    * rebound.  See "https://bugzilla.mozilla.org/show_bug.cgi?id=533905#c1".
  261.    *
  262.    * \param aElem               Element for which to rebind XBL.
  263.    */
  264.  
  265.   rebindXBL: function DOMUtils_rebindXBL(aElem) {
  266.     // Clone the node to force a rebinding.  The clone is not needed.
  267.     //XXXeps there may be a better way to do this, but this works for now
  268.     aElem.cloneNode(false);
  269.   },
  270.  
  271.  
  272.   //----------------------------------------------------------------------------
  273.   //
  274.   // DOM class attribute services.
  275.   //
  276.   //----------------------------------------------------------------------------
  277.  
  278.   /**
  279.    * Set the class specified by aClass within the class attribute of the element
  280.    * specified by aElem.
  281.    *
  282.    * \param aElem               Element for which to set class.
  283.    * \param aClass              Class to set.
  284.    */
  285.  
  286.   setClass: function DOMUtils_setClass(aElem, aClass) {
  287.     // Get the class attribute.  If it's empty, just set the class and return.
  288.     var classAttr = aElem.getAttribute("class");
  289.     if (!classAttr) {
  290.       aElem.setAttribute("class", aClass);
  291.       return;
  292.     }
  293.  
  294.     // Set class if not already set.
  295.     var classList = classAttr.split(" ");
  296.     if (classList.indexOf(aClass) < 0) {
  297.       classList.push(aClass);
  298.       aElem.setAttribute("class", classList.join(" "));
  299.     }
  300.   },
  301.  
  302.  
  303.   /**
  304.    * Clear the class specified by aClass from the class attribute of the element
  305.    * specified by aElem.
  306.    *
  307.    * \param aElem               Element for which to clear class.
  308.    * \param aClass              Class to clear.
  309.    */
  310.  
  311.   clearClass: function DOMUtils_clearClass(aElem, aClass) {
  312.     // Get the class attribute.  If it's empty, just return.
  313.     var classAttr = aElem.getAttribute("class");
  314.     if (!classAttr)
  315.       return;
  316.  
  317.     // Clear specified class from class attribute.
  318.     var classList = classAttr.split(" ");
  319.     var classCount = classList.length;
  320.     for (var i = classCount - 1; i >= 0; i--) {
  321.       if (classList[i] == aClass)
  322.         classList.splice(i, 1);
  323.     }
  324.     if (classList.length < classCount)
  325.       aElem.setAttribute("class", classList.join(" "));
  326.   },
  327.  
  328.  
  329.   /**
  330.    * Return true if the class specified by aClass is set within the class
  331.    * attribute of the element specified by aElem.
  332.    *
  333.    * \param aElem               Element for which to check if class is set.
  334.    * \param aClass              Class for which to check.
  335.    *
  336.    * \return                    True if class is set.
  337.    */
  338.  
  339.   isClassSet: function DOMUtils_isClassSet(aElem, aClass) {
  340.     var classAttr = aElem.getAttribute("class");
  341.     var classList = classAttr.split(" ");
  342.     if (classList.indexOf(aClass) < 0)
  343.       return false;
  344.  
  345.     return true;
  346.   },
  347.  
  348.  
  349.   //----------------------------------------------------------------------------
  350.   //
  351.   // DOM node destruction services.
  352.   //
  353.   //   When a node is removed from a DOM tree, any XBL bindings bound to that
  354.   // node or its children are not detached.  Thus, the binding destructors are
  355.   // not called.  The XBL bindings are not detached until the owner document is
  356.   // destroyed.  This can lead to memory leaks if XBL bound nodes are
  357.   // dynamically added and removed from a document.
  358.   //   The DOM node destruction services provide support for releasing XBL
  359.   // binding resources before the XBL binding is detached.  An XBL binding may
  360.   // add a destroy function by calling addNodeDestroyFunc.  Multiple destroy
  361.   // functions may be added for an XBL binding (e.g., for extended bindings).
  362.   //   Before a node is removed from a document, destroyNode should be called.
  363.   // This function recursively calls the destroy functions for all of the nodes
  364.   // children, including the anonymous children.  Note that destroyNode must be
  365.   // called before node removal since node removal removes XBL binding content.
  366.   // If destroyNode is called after node removal, anonymous child nodes will not
  367.   // be destroyed.
  368.   //
  369.   //----------------------------------------------------------------------------
  370.  
  371.   /**
  372.    * Add the destroy function specified by aFunc to the list of destroy
  373.    * functions for the node specified by aNode.  The destroy function is only
  374.    * called once and is removed from the destroy function list when called.
  375.    *
  376.    * \param aNode               Node for which to add destroy function.
  377.    * \param aFunc               Destroy function.
  378.    */
  379.  
  380.   addNodeDestroyFunc: function DOMUtils_addNodeDestroyFunc(aNode,
  381.                                                            aFunc) {
  382.     // Ensure the destroy function list exists.
  383.     if (!aNode.destroyFuncList)
  384.       aNode.destroyFuncList = [];
  385.  
  386.     // Push the destroy function on the end of the list.
  387.     aNode.destroyFuncList.push(aFunc);
  388.   },
  389.  
  390.  
  391.   /**
  392.    * Recursively call the destroy functions for all child nodes, including
  393.    * anonymous nodes, for the node specified by aNode.
  394.    *
  395.    * \param aNode               Node to destroy.
  396.    */
  397.  
  398.   destroyNode: function DOMUtils_destroyNode(aNode) {
  399.     /* Can only destroy element nodes. */
  400.     if (aNode.nodeType != Ci.nsIDOMNode.ELEMENT_NODE)
  401.       return;
  402.  
  403.     // Destroy all of the node's children, including the anonymous children.
  404.     var nodeDocument = aNode.ownerDocument;
  405.     this.destroyNodeList(nodeDocument.getAnonymousNodes(aNode));
  406.     this.destroyNodeList(aNode.childNodes);
  407.  
  408.     // Call the node destroy functions.
  409.     while (aNode.destroyFuncList) {
  410.       // Pop the next destroy function from the end of the list and call it.
  411.       var destroyFunc = aNode.destroyFuncList.pop();
  412.       try {
  413.         destroyFunc();
  414.       } catch (ex) {
  415.         var msg = "Exception: " + ex +
  416.                   " at " + ex.fileName +
  417.                   ", line " + ex.lineNumber;
  418.         Cu.reportError(msg);
  419.       }
  420.  
  421.       // If the destroy function list is empty, remove it.
  422.       if (!aNode.destroyFuncList.length)
  423.         aNode.destroyFuncList = null;
  424.     }
  425.   },
  426.  
  427.  
  428.   /**
  429.    * Destroy all nodes in the list specified by aNodeList.
  430.    *
  431.    * \param aNodeList           Array list of nodes to destroy.
  432.    */
  433.  
  434.   destroyNodeList: function DOMUtils_destroyNodeList(aNodeList) {
  435.     if (!aNodeList)
  436.       return;
  437.  
  438.     for (var i = 0; i < aNodeList.length; i++) {
  439.       this.destroyNode(aNodeList[i]);
  440.     }
  441.   }
  442. };
  443.  
  444.  
  445. //------------------------------------------------------------------------------
  446. //
  447. // DOM highlighter services.
  448. //
  449. //   These services may be used to apply highlighting effects to DOM elements.
  450. //   Highlighting is applied by setting a DOM element attribute to a particular
  451. // value.
  452. //   The highlighting effect blinks the highlighting for a specified period of
  453. // time and then leaves the highlight applied.  When the user clicks anywhere,
  454. // the highlighting is removed.
  455. //
  456. //------------------------------------------------------------------------------
  457.  
  458. /**
  459.  *   Construct a DOM highlighter object to highlight the element specified by
  460.  * aElement within the window specified by aWindow and return the highlighter
  461.  * object.
  462.  *   The element is highlighted by setting one of its attributes to a particular
  463.  * value.  A CSS rule can then be set up to apply highlight styling to the
  464.  * element based on the attribute and value.
  465.  *   The attribute to set is specified by aAttribute.  The attribute is set to
  466.  * the value specified by aValue.
  467.  *   If the attribute is "class", the value is added to the class attribute
  468.  * instead of replacing it entirely.
  469.  *   If aAttribute is not specified, the "class" attribute is used as the
  470.  * default.
  471.  *   If aValue is not specified, a default value is also used.  If the "class"
  472.  * attribute is being used, "highlight" is used as the value to add to the
  473.  * "class" attribute.  Otherwise, the attribute is set to the value "true".
  474.  *   Blink the highlighting for the duration specified by aBlinkDuration.  If
  475.  * the duration is not specified, choose a default duration.  If the duration is
  476.  * negative, do not blink.
  477.  *
  478.  * \param aWindow               Window containing element.
  479.  * \param aElement              Element to highlight.
  480.  * \param aValue                Value to set highlight.
  481.  * \param aAttribute            Attribute to use to highlight.
  482.  * \param aBlinkDuration        Duration of blinking in milliseconds.
  483.  */
  484.  
  485. function sbDOMHighlighter(aWindow,
  486.                           aElement,
  487.                           aValue,
  488.                           aAttribute,
  489.                           aBlinkDuration) {
  490.   // Get the attribute to highlight, defaulting to "class".
  491.   var attribute = aAttribute;
  492.   if (!attribute)
  493.     attribute = "class";
  494.  
  495.   // Get the attribute value with which to highlight.  Choose an appropriate
  496.   // default if no value was specified.
  497.   var value = aValue;
  498.   if (!value) {
  499.     if (attribute == "class") {
  500.       value = "highlight";
  501.     }
  502.     else {
  503.       value = "true";
  504.     }
  505.   }
  506.  
  507.   // Get the blink duration.
  508.   var blinkDuration = aBlinkDuration;
  509.   if (!blinkDuration)
  510.     blinkDuration = this.defaultBlinkDuration;
  511.  
  512.   // Save parameters.
  513.   this._window = aWindow;
  514.   this._element = aElement;
  515.   this._value = value;
  516.   this._attribute = attribute;
  517.   this._blinkDuration = blinkDuration;
  518. }
  519.  
  520.  
  521. /**
  522.  *   Highlight the element specified by aElement within the window specified by
  523.  * aWindow and return the highlighter object.
  524.  *   The element is highlighted by setting one of its attributes to a particular
  525.  * value.  A CSS rule can then be set up to apply highlight styling to the
  526.  * element based on the attribute and value.
  527.  *   The attribute to set is specified by aAttribute.  The attribute is set to
  528.  * the value specified by aValue.
  529.  *   If the attribute is "class", the value is added to the class attribute
  530.  * instead of replacing it entirely.
  531.  *   If aAttribute is not specified, the "class" attribute is used as the
  532.  * default.
  533.  *   If aValue is not specified, a default value is also used.  If the "class"
  534.  * attribute is being used, "highlight" is used as the value to add to the
  535.  * "class" attribute.  Otherwise, the attribute is set to the value "true".
  536.  *   Blink the highlighting for the duration specified by aBlinkDuration.  If
  537.  * the duration is not specified, choose a default duration.  If the duration is
  538.  * negative, do not blink.
  539.  *
  540.  * \param aWindow               Window containing element.
  541.  * \param aElement              Element to highlight.
  542.  * \param aValue                Value to set highlight.
  543.  * \param aAttribute            Attribute to use to highlight.
  544.  * \param aBlinkDuration        Duration of blinking in milliseconds.
  545.  *
  546.  * \return                      Highlighter object.
  547.  */
  548.  
  549. sbDOMHighlighter.highlightElement =
  550.   function sbDOMHighlighter_highlightElement(aWindow,
  551.                                              aElement,
  552.                                              aValue,
  553.                                              aAttribute,
  554.                                              aBlinkDuration) {
  555.   // Create and start a highlighter.
  556.   var highlighter = new sbDOMHighlighter(aWindow,
  557.                                          aElement,
  558.                                          aValue,
  559.                                          aAttribute,
  560.                                          aBlinkDuration);
  561.   highlighter.start();
  562.  
  563.   return highlighter;
  564. }
  565.  
  566.  
  567. // Define the class.
  568. sbDOMHighlighter.prototype = {
  569.   // Set the constructor.
  570.   constructor: sbDOMHighlighter,
  571.  
  572.   //
  573.   // DOM highlighter configuration.
  574.   //
  575.   //   blinkPeriod              Blink period in milliseconds.
  576.   //   defaultBlinkDuration     Default blink duration in milliseconds.
  577.   //
  578.  
  579.   blinkPeriod: 250,
  580.   defaultBlinkDuration: 2000,
  581.  
  582.  
  583.   //
  584.   // DOM highlighter fields.
  585.   //
  586.   //   _window                  Window containing element.
  587.   //   _element                 Element to highlight.
  588.   //   _attribute               Attribute to use to highlight.
  589.   //   _value                   Value to set highlight.
  590.   //   _blinkDuration           Duration of blinking.
  591.   //   _domEventListenerSet     Set of DOM event listeners.
  592.   //   _blinkTimer              Timer used for blinking.
  593.   //   _blinkStart              Timestamp of start of blinking.
  594.   //   _isHighlighted           If true, element is currently highlighted.
  595.   //
  596.  
  597.   _window: null,
  598.   _element: null,
  599.   _attribute: null,
  600.   _value: null,
  601.   _blinkDuration: -1,
  602.   _domEventListenerSet: null,
  603.   _blinkTimer: null,
  604.   _blinkStart: 0,
  605.   _isHighlighted: false,
  606.  
  607.  
  608.   /**
  609.    * Start the highlight effect.
  610.    */
  611.  
  612.   start: function sbDOMHighlighter_start() {
  613.     var self = this;
  614.     var func;
  615.  
  616.     // Create a DOM event listener set.
  617.     this._domEventListenerSet = new DOMEventListenerSet();
  618.  
  619.     // Listen to document click and window unload events.
  620.     func = function _onClickWrapper() { self._onClick(); };
  621.     this._domEventListenerSet.add(this._element.ownerDocument,
  622.                                   "click",
  623.                                   func,
  624.                                   true);
  625.     if (this._window) {
  626.       func = function _onUnloadWrapper() { self._onUnload(); };
  627.       this._domEventListenerSet.add(this._window, "unload", func, false);
  628.     }
  629.  
  630.     // Highlight element.
  631.     this.setHighlight();
  632.  
  633.     // Start blinking.
  634.     this._blinkStart = Date.now();
  635.     var self = this;
  636.     var func = function _blinkWrapper() { self._blink(); };
  637.     this._blinkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  638.     this._blinkTimer.initWithCallback(func,
  639.                                       this.blinkPeriod,
  640.                                       Ci.nsITimer.TYPE_REPEATING_SLACK);
  641.   },
  642.  
  643.  
  644.   /**
  645.    * Cancel the highlighter.
  646.    */
  647.  
  648.   cancel: function sbDOMHighlighter_cancel() {
  649.     // Remove DOM event listeners.
  650.     if (this._domEventListenerSet)
  651.       this._domEventListenerSet.removeAll();
  652.  
  653.     // Cancel blink timer.
  654.     if (this._blinkTimer)
  655.       this._blinkTimer.cancel();
  656.  
  657.     // Remove object references.
  658.     this._element = null;
  659.     this._domEventListenerSet = null;
  660.     this._blinkTimer = null;
  661.   },
  662.  
  663.  
  664.   /**
  665.    * Set the highlight on the element.
  666.    */
  667.  
  668.   setHighlight: function sbDOMHighlighter_setHighlight() {
  669.     // Apply highlight.
  670.     if (this._attribute == "class") {
  671.       DOMUtils.setClass(this._element, this._value);
  672.     }
  673.     else {
  674.       this._element.setAttribute(this._attribute, this._value);
  675.     }
  676.  
  677.     // Indicate that the element is now highlighted.
  678.     this._isHighlighted = true;
  679.   },
  680.  
  681.  
  682.   /**
  683.    * Clear the highlight on the element.
  684.    */
  685.  
  686.   clearHighlight: function sbDOMHighlighter_clearHighlight() {
  687.     // Remove highlight.
  688.     if (this._attribute == "class") {
  689.       DOMUtils.clearClass(this._element, this._value);
  690.     }
  691.     else {
  692.       this._element.removeAttribute(this._attribute);
  693.     }
  694.  
  695.     // Indicate that the element is no longer highlighted.
  696.     this._isHighlighted = false;
  697.   },
  698.  
  699.  
  700.   //----------------------------------------------------------------------------
  701.   //
  702.   // Internal DOM highlighter services.
  703.   //
  704.   //----------------------------------------------------------------------------
  705.  
  706.   /**
  707.    * Blink the element highlight.
  708.    */
  709.  
  710.   _blink: function sbDOMHighlighter__blink() {
  711.     // If the blink duration has been reached, set the highlight and stop the
  712.     // blinking.
  713.     var blinkTime = Date.now() - this._blinkStart;
  714.     if (blinkTime >= this._blinkDuration) {
  715.       this._blinkTimer.cancel();
  716.       this._blinkTimer = null;
  717.       this.setHighlight();
  718.       return;
  719.     }
  720.  
  721.     // Blink the element highlight.
  722.     if (this._isHighlighted) {
  723.       this.clearHighlight();
  724.     }
  725.     else {
  726.       this.setHighlight();
  727.     }
  728.   },
  729.  
  730.  
  731.   /**
  732.    * Handle a click event.
  733.    */
  734.  
  735.   _onClick: function sbDOMHighlighter__onClick() {
  736.     // Clear highlight and cancel highlighter.
  737.     this.clearHighlight();
  738.     this.cancel();
  739.   },
  740.  
  741.  
  742.   /**
  743.    * Handle an unload event.
  744.    */
  745.  
  746.   _onUnload: function sbDOMHighlighter__onUnload() {
  747.     // Cancel highlighter.
  748.     this.cancel();
  749.   }
  750. };
  751.  
  752.  
  753. //------------------------------------------------------------------------------
  754. //
  755. // DOM event listener set services.
  756. //
  757. //   These services may be used to maintain a set of DOM event listeners and to
  758. // facilitate the removal of DOM event listeners.
  759. //
  760. //------------------------------------------------------------------------------
  761.  
  762. /**
  763.  * Construct a DOM event listener set object.
  764.  */
  765.  
  766. function DOMEventListenerSet()
  767. {
  768.   // Initialize some fields.
  769.   this._eventListenerList = {};
  770. }
  771.  
  772. // Set constructor.
  773. DOMEventListenerSet.prototype.constructor = DOMEventListenerSet;
  774.  
  775. // Define the class.
  776. DOMEventListenerSet.prototype = {
  777.   //
  778.   // DOM event listener set fields.
  779.   //
  780.   //   _eventListenerList       List of event listeners.
  781.   //   _nextEventListenerID     Next event listener ID.
  782.   //
  783.  
  784.   _eventListenerList: null,
  785.   _nextEventListenerID: 0,
  786.  
  787.  
  788.   /**
  789.    * Add an event listener for the element specified by aElement with the
  790.    * parameters specified by aType, aListener, and aUseCapture.  If aOneShot is
  791.    * true, remove listener after it's called once.  Return an ID that may be
  792.    * used to reference the added listener.
  793.    *
  794.    * \param aElement            Element for which to add an event listener.
  795.    * \param aType               Type of event for which to listen.
  796.    * \param aListener           Listener function.
  797.    * \param aUseCapture         True if event capture should be used.
  798.    * \param aOneShot            True if listener is a one-shot listener.
  799.    * \param aWantsUntrusted     True if event can be triggered by untrusted
  800.    *                            content. non-standerd and Mozilla only. false
  801.    *                            by default to avoid security problems.
  802.    *
  803.    * \return                    Event listener ID.
  804.    */
  805.  
  806.   add: function DOMEventListenerSet_add(aElement,
  807.                                         aType,
  808.                                         aListener,
  809.                                         aUseCapture,
  810.                                         aOneShot,
  811.                                         aWantsUntrusted) {
  812.     // Create the event listener object.
  813.     var eventListener = {};
  814.     eventListener.id = this._nextEventListenerID++;
  815.     eventListener.element = aElement;
  816.     eventListener.type = aType;
  817.     eventListener.listener = aListener;
  818.     eventListener.useCapture = aUseCapture;
  819.     eventListener.oneShot = aOneShot;
  820.     eventListener.wantsUntrusted = aWantsUntrusted == true ? true : false;
  821.  
  822.     // Use one-shot function if listener is a one-shot listener.
  823.     var listenerFunc = eventListener.listener;
  824.     if (eventListener.oneShot) {
  825.       var _this = this;
  826.       listenerFunc =
  827.         function(aEvent) { return _this._doOneShot(aEvent, eventListener); };
  828.     }
  829.     eventListener.addedListener = listenerFunc;
  830.  
  831.     // Add the event listener.
  832.     eventListener.element.addEventListener(eventListener.type,
  833.                                            eventListener.addedListener,
  834.                                            eventListener.useCapture,
  835.                                            eventListener.wantsUntrusted);
  836.     this._eventListenerList[eventListener.id] = eventListener;
  837.  
  838.     return (eventListener.id);
  839.   },
  840.  
  841.  
  842.   /**
  843.    * Remove the event listener specified by aEventListenerID.
  844.    *
  845.    * \param aEventListenerID    ID of event listener to remove.
  846.    */
  847.  
  848.   remove: function DOMEventListenerSet_remove(aEventListenerID) {
  849.     // Get the event listener.  Do nothing if not found.
  850.     var eventListener = this._eventListenerList[aEventListenerID];
  851.     if (!eventListener)
  852.       return;
  853.  
  854.     // Remove the event listener.
  855.     eventListener.element.removeEventListener(eventListener.type,
  856.                                               eventListener.addedListener,
  857.                                               eventListener.useCapture);
  858.     delete this._eventListenerList[aEventListenerID];
  859.   },
  860.  
  861.  
  862.   /**
  863.    * Remove all event listeners.
  864.    */
  865.  
  866.   removeAll: function DOMEventListenerSet_removeAll() {
  867.     // Remove all event listeners.
  868.     for (var id in this._eventListenerList) {
  869.       this.remove(id);
  870.     }
  871.     this._eventListenerList = {};
  872.   },
  873.  
  874.  
  875.   //----------------------------------------------------------------------------
  876.   //
  877.   // Internal DOM event listener set services.
  878.   //
  879.   //----------------------------------------------------------------------------
  880.  
  881.   /**
  882.    * Dispatch the event specified by aEvent to the one-shot listener specified
  883.    * by aEventListener and remove the listener.
  884.    *
  885.    * \param aEvent              Event to dispatch.
  886.    * \param aEventListener      Event listener to which to dispatch event.
  887.    */
  888.  
  889.   _doOneShot: function DOMEventListenerSet__doOneShot(aEvent, aEventListener) {
  890.     // Remove event listener if one-shot.
  891.     if (aEventListener.oneShot)
  892.       this.remove(aEventListener.id);
  893.  
  894.     // Dispatch event to listener.
  895.     return aEventListener.listener(aEvent);
  896.   }
  897. };
  898.  
  899.  
  900. //------------------------------------------------------------------------------
  901. //
  902. // Color utility services.
  903. //
  904. //   These utilities provide services for handling colors specified in DOM
  905. // attributes or in CSS.
  906. //   These utilities make use of color objects with the following fields:
  907. //
  908. //     red                      Number from 0.0 to 1.0.
  909. //     green                    Number from 0.0 to 1.0.
  910. //     blue                     Number from 0.0 to 1.0.
  911. //     alpha                    Number from 0.0 to 1.0.
  912. //
  913. //------------------------------------------------------------------------------
  914.  
  915. var sbColorUtils = {
  916.   /**
  917.    * Return the color object for the CSS color string specified by aCSSColor.
  918.    *
  919.    * \param aCSSColor           CSS color string.
  920.    *
  921.    * \return                    Color object.
  922.    *
  923.    *XXXeps would be nice to also support #xxxxxx format.
  924.    */
  925.  
  926.   getColorFromCSSColor: function sbColorUtils_getCSSColor(aCSSColor) {
  927.     // Parse out the color components substring from the CSS color string.
  928.     var rgba = aCSSColor.match(/^rgba?\((.*)\)/)
  929.     if (!rgba || (rgba.length < 2))
  930.       return null;
  931.     rgba = rgba[1];
  932.  
  933.     // Split the color components substring into an array.
  934.     rgba = rgba.split(",");
  935.     if (rgba.length < 3)
  936.       return null;
  937.  
  938.     // Parse the color components into a color object.
  939.     var color = {
  940.       red:   parseFloat(rgba[0]) / 255.0,
  941.       green: parseFloat(rgba[1]) / 255.0,
  942.       blue:  parseFloat(rgba[2]) / 255.0
  943.     };
  944.     if (rgba.length > 3)
  945.       color.alpha = parseFloat(rgba[3])
  946.     else
  947.       color.alpha = 1.0;
  948.  
  949.     return color;
  950.   },
  951.  
  952.  
  953.   /**
  954.    * Return the DOM attribute color string for the color object specified by
  955.    * aColor.
  956.    *
  957.    * \param aColor              Color object.
  958.    *
  959.    * \return                    DOM attribute color string.
  960.    */
  961.  
  962.   getDOMColorString: function sbColorUtils_getDOMColorString(aColor) {
  963.     // Convert the colors to integer values from 0 to 255.
  964.     var red = Math.round(aColor.red * 255.0);
  965.     var green = Math.round(aColor.green * 255.0);
  966.     var blue = Math.round(aColor.blue * 255.0);
  967.  
  968.     // Combine the individual color values into a single 32-bit rgb value.
  969.     var colorValue = (red << 16) | (green << 8) | blue;
  970.  
  971.     // Convert the 32-bit rgb value to the form "#xxxxxx", adding leading zeros
  972.     // to ensure there are 6 digits.
  973.     var colorString = (0x01000000 + colorValue).toString(16);
  974.     colorString = "#" + colorString.slice(1);
  975.  
  976.     return colorString;
  977.   }
  978. };
  979.  
  980.