home *** CD-ROM | disk | FTP | other *** search
/ Computer Active 2010 July / CA07.iso / Multimedija / QuickTimeInstaller.exe / AppleApplicationSupport.msi / WebKit.resources_inspector_utilities.js < prev    next >
Encoding:
JavaScript  |  2010-03-15  |  35.0 KB  |  1,137 lines

  1. /*
  2.  * Copyright (C) 2007 Apple Inc.  All rights reserved.
  3.  *
  4.  * Redistribution and use in source and binary forms, with or without
  5.  * modification, are permitted provided that the following conditions
  6.  * are met:
  7.  *
  8.  * 1.  Redistributions of source code must retain the above copyright
  9.  *     notice, this list of conditions and the following disclaimer. 
  10.  * 2.  Redistributions in binary form must reproduce the above copyright
  11.  *     notice, this list of conditions and the following disclaimer in the
  12.  *     documentation and/or other materials provided with the distribution. 
  13.  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
  14.  *     its contributors may be used to endorse or promote products derived
  15.  *     from this software without specific prior written permission. 
  16.  *
  17.  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
  18.  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  19.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20.  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
  21.  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  22.  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  23.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  24.  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  26.  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27.  */
  28.  
  29. Object.type = function(obj, win)
  30. {
  31.     if (obj === null)
  32.         return "null";
  33.  
  34.     var type = typeof obj;
  35.     if (type !== "object" && type !== "function")
  36.         return type;
  37.  
  38.     win = win || window;
  39.  
  40.     if (obj instanceof win.Node)
  41.         return "node";
  42.     if (obj instanceof win.String)
  43.         return "string";
  44.     if (obj instanceof win.Array)
  45.         return "array";
  46.     if (obj instanceof win.Boolean)
  47.         return "boolean";
  48.     if (obj instanceof win.Number)
  49.         return "number";
  50.     if (obj instanceof win.Date)
  51.         return "date";
  52.     if (obj instanceof win.RegExp)
  53.         return "regexp";
  54.     if (obj instanceof win.Error)
  55.         return "error";
  56.     return type;
  57. }
  58.  
  59. Object.hasProperties = function(obj)
  60. {
  61.     if (typeof obj === "undefined" || typeof obj === "null")
  62.         return false;
  63.     for (var name in obj)
  64.         return true;
  65.     return false;
  66. }
  67.  
  68. Object.describe = function(obj, abbreviated)
  69. {
  70.     var type1 = Object.type(obj);
  71.     var type2 = Object.prototype.toString.call(obj).replace(/^\[object (.*)\]$/i, "$1");
  72.  
  73.     switch (type1) {
  74.     case "object":
  75.     case "node":
  76.         return type2;
  77.     case "array":
  78.         return "[" + obj.toString() + "]";
  79.     case "string":
  80.         if (obj.length > 100)
  81.             return "\"" + obj.substring(0, 100) + "\u2026\"";
  82.         return "\"" + obj + "\"";
  83.     case "function":
  84.         var objectText = String(obj);
  85.         if (!/^function /.test(objectText))
  86.             objectText = (type2 == "object") ? type1 : type2;
  87.         else if (abbreviated)
  88.             objectText = /.*/.exec(obj)[0].replace(/ +$/g, "");
  89.         return objectText;
  90.     case "regexp":
  91.         return String(obj).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1);
  92.     default:
  93.         return String(obj);
  94.     }
  95. }
  96.  
  97. Object.sortedProperties = function(obj)
  98. {
  99.     var properties = [];
  100.     for (var prop in obj)
  101.         properties.push(prop);
  102.     properties.sort();
  103.     return properties;
  104. }
  105.  
  106. Function.prototype.bind = function(thisObject)
  107. {
  108.     var func = this;
  109.     var args = Array.prototype.slice.call(arguments, 1);
  110.     return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))) };
  111. }
  112.  
  113. Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, direction)
  114. {
  115.     var startNode;
  116.     var startOffset = 0;
  117.     var endNode;
  118.     var endOffset = 0;
  119.  
  120.     if (!stayWithinNode)
  121.         stayWithinNode = this;
  122.  
  123.     if (!direction || direction === "backward" || direction === "both") {
  124.         var node = this;
  125.         while (node) {
  126.             if (node === stayWithinNode) {
  127.                 if (!startNode)
  128.                     startNode = stayWithinNode;
  129.                 break;
  130.             }
  131.  
  132.             if (node.nodeType === Node.TEXT_NODE) {
  133.                 var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1));
  134.                 for (var i = start; i >= 0; --i) {
  135.                     if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
  136.                         startNode = node;
  137.                         startOffset = i + 1;
  138.                         break;
  139.                     }
  140.                 }
  141.             }
  142.  
  143.             if (startNode)
  144.                 break;
  145.  
  146.             node = node.traversePreviousNode(false, stayWithinNode);
  147.         }
  148.  
  149.         if (!startNode) {
  150.             startNode = stayWithinNode;
  151.             startOffset = 0;
  152.         }
  153.     } else {
  154.         startNode = this;
  155.         startOffset = offset;
  156.     }
  157.  
  158.     if (!direction || direction === "forward" || direction === "both") {
  159.         node = this;
  160.         while (node) {
  161.             if (node === stayWithinNode) {
  162.                 if (!endNode)
  163.                     endNode = stayWithinNode;
  164.                 break;
  165.             }
  166.  
  167.             if (node.nodeType === Node.TEXT_NODE) {
  168.                 var start = (node === this ? offset : 0);
  169.                 for (var i = start; i < node.nodeValue.length; ++i) {
  170.                     if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
  171.                         endNode = node;
  172.                         endOffset = i;
  173.                         break;
  174.                     }
  175.                 }
  176.             }
  177.  
  178.             if (endNode)
  179.                 break;
  180.  
  181.             node = node.traverseNextNode(false, stayWithinNode);
  182.         }
  183.  
  184.         if (!endNode) {
  185.             endNode = stayWithinNode;
  186.             endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length;
  187.         }
  188.     } else {
  189.         endNode = this;
  190.         endOffset = offset;
  191.     }
  192.  
  193.     var result = this.ownerDocument.createRange();
  194.     result.setStart(startNode, startOffset);
  195.     result.setEnd(endNode, endOffset);
  196.  
  197.     return result;
  198. }
  199.  
  200. Element.prototype.removeStyleClass = function(className) 
  201. {
  202.     // Test for the simple case before using a RegExp.
  203.     if (this.className === className) {
  204.         this.className = "";
  205.         return;
  206.     }
  207.  
  208.     this.removeMatchingStyleClasses(className.escapeForRegExp());
  209. }
  210.  
  211. Element.prototype.removeMatchingStyleClasses = function(classNameRegex)
  212. {
  213.     var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)");
  214.     if (regex.test(this.className))
  215.         this.className = this.className.replace(regex, " ");
  216. }
  217.  
  218. Element.prototype.addStyleClass = function(className) 
  219. {
  220.     if (className && !this.hasStyleClass(className))
  221.         this.className += (this.className.length ? " " + className : className);
  222. }
  223.  
  224. Element.prototype.hasStyleClass = function(className) 
  225. {
  226.     if (!className)
  227.         return false;
  228.     // Test for the simple case before using a RegExp.
  229.     if (this.className === className)
  230.         return true;
  231.     var regex = new RegExp("(^|\\s)" + className.escapeForRegExp() + "($|\\s)");
  232.     return regex.test(this.className);
  233. }
  234.  
  235. Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray)
  236. {
  237.     for (var node = this; node && !objectsAreSame(node, this.ownerDocument); node = node.parentNode)
  238.         for (var i = 0; i < nameArray.length; ++i)
  239.             if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase())
  240.                 return node;
  241.     return null;
  242. }
  243.  
  244. Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName)
  245. {
  246.     return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]);
  247. }
  248.  
  249. Node.prototype.enclosingNodeOrSelfWithClass = function(className)
  250. {
  251.     for (var node = this; node && !objectsAreSame(node, this.ownerDocument); node = node.parentNode)
  252.         if (node.nodeType === Node.ELEMENT_NODE && node.hasStyleClass(className))
  253.             return node;
  254.     return null;
  255. }
  256.  
  257. Node.prototype.enclosingNodeWithClass = function(className)
  258. {
  259.     if (!this.parentNode)
  260.         return null;
  261.     return this.parentNode.enclosingNodeOrSelfWithClass(className);
  262. }
  263.  
  264. Element.prototype.query = function(query) 
  265. {
  266.     return this.ownerDocument.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  267. }
  268.  
  269. Element.prototype.removeChildren = function()
  270. {
  271.     while (this.firstChild) 
  272.         this.removeChild(this.firstChild);        
  273. }
  274.  
  275. Element.prototype.isInsertionCaretInside = function()
  276. {
  277.     var selection = window.getSelection();
  278.     if (!selection.rangeCount || !selection.isCollapsed)
  279.         return false;
  280.     var selectionRange = selection.getRangeAt(0);
  281.     return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this);
  282. }
  283.  
  284. Element.prototype.__defineGetter__("totalOffsetLeft", function()
  285. {
  286.     var total = 0;
  287.     for (var element = this; element; element = element.offsetParent)
  288.         total += element.offsetLeft;
  289.     return total;
  290. });
  291.  
  292. Element.prototype.__defineGetter__("totalOffsetTop", function()
  293. {
  294.     var total = 0;
  295.     for (var element = this; element; element = element.offsetParent)
  296.         total += element.offsetTop;
  297.     return total;
  298. });
  299.  
  300. Element.prototype.firstChildSkippingWhitespace = firstChildSkippingWhitespace;
  301. Element.prototype.lastChildSkippingWhitespace = lastChildSkippingWhitespace;
  302.  
  303. Node.prototype.isWhitespace = isNodeWhitespace;
  304. Node.prototype.nodeTypeName = nodeTypeName;
  305. Node.prototype.displayName = nodeDisplayName;
  306. Node.prototype.contentPreview = nodeContentPreview;
  307. Node.prototype.isAncestor = isAncestorNode;
  308. Node.prototype.isDescendant = isDescendantNode;
  309. Node.prototype.firstCommonAncestor = firstCommonNodeAncestor;
  310. Node.prototype.nextSiblingSkippingWhitespace = nextSiblingSkippingWhitespace;
  311. Node.prototype.previousSiblingSkippingWhitespace = previousSiblingSkippingWhitespace;
  312. Node.prototype.traverseNextNode = traverseNextNode;
  313. Node.prototype.traversePreviousNode = traversePreviousNode;
  314. Node.prototype.onlyTextChild = onlyTextChild;
  315.  
  316. String.prototype.hasSubstring = function(string, caseInsensitive)
  317. {
  318.     if (!caseInsensitive)
  319.         return this.indexOf(string) !== -1;
  320.     return this.match(new RegExp(string.escapeForRegExp(), "i"));
  321. }
  322.  
  323. String.prototype.escapeCharacters = function(chars)
  324. {
  325.     var foundChar = false;
  326.     for (var i = 0; i < chars.length; ++i) {
  327.         if (this.indexOf(chars.charAt(i)) !== -1) {
  328.             foundChar = true;
  329.             break;
  330.         }
  331.     }
  332.  
  333.     if (!foundChar)
  334.         return this;
  335.  
  336.     var result = "";
  337.     for (var i = 0; i < this.length; ++i) {
  338.         if (chars.indexOf(this.charAt(i)) !== -1)
  339.             result += "\\";
  340.         result += this.charAt(i);
  341.     }
  342.  
  343.     return result;
  344. }
  345.  
  346. String.prototype.escapeForRegExp = function()
  347. {
  348.     return this.escapeCharacters("^[]{}()\\.$*+?|");
  349. }
  350.  
  351. String.prototype.escapeHTML = function()
  352. {
  353.     return this.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
  354. }
  355.  
  356. String.prototype.collapseWhitespace = function()
  357. {
  358.     return this.replace(/[\s\xA0]+/g, " ");
  359. }
  360.  
  361. String.prototype.trimLeadingWhitespace = function()
  362. {
  363.     return this.replace(/^[\s\xA0]+/g, "");
  364. }
  365.  
  366. String.prototype.trimTrailingWhitespace = function()
  367. {
  368.     return this.replace(/[\s\xA0]+$/g, "");
  369. }
  370.  
  371. String.prototype.trimWhitespace = function()
  372. {
  373.     return this.replace(/^[\s\xA0]+|[\s\xA0]+$/g, "");
  374. }
  375.  
  376. String.prototype.trimURL = function(baseURLDomain)
  377. {
  378.     var result = this.replace(new RegExp("^http[s]?:\/\/", "i"), "");
  379.     if (baseURLDomain)
  380.         result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), "");
  381.     return result;
  382. }
  383.  
  384. function getStyleTextWithShorthands(style)
  385. {
  386.     var cssText = "";
  387.     var foundProperties = {};
  388.     for (var i = 0; i < style.length; ++i) {
  389.         var individualProperty = style[i];
  390.         var shorthandProperty = style.getPropertyShorthand(individualProperty);
  391.         var propertyName = (shorthandProperty || individualProperty);
  392.  
  393.         if (propertyName in foundProperties)
  394.             continue;
  395.  
  396.         if (shorthandProperty) {
  397.             var value = getShorthandValue(style, shorthandProperty);
  398.             var priority = getShorthandPriority(style, shorthandProperty);
  399.         } else {
  400.             var value = style.getPropertyValue(individualProperty);
  401.             var priority = style.getPropertyPriority(individualProperty);
  402.         }
  403.  
  404.         foundProperties[propertyName] = true;
  405.  
  406.         cssText += propertyName + ": " + value;
  407.         if (priority)
  408.             cssText += " !" + priority;
  409.         cssText += "; ";
  410.     }
  411.  
  412.     return cssText;
  413. }
  414.  
  415. function getShorthandValue(style, shorthandProperty)
  416. {
  417.     var value = style.getPropertyValue(shorthandProperty);
  418.     if (!value) {
  419.         // Some shorthands (like border) return a null value, so compute a shorthand value.
  420.         // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed.
  421.  
  422.         var foundProperties = {};
  423.         for (var i = 0; i < style.length; ++i) {
  424.             var individualProperty = style[i];
  425.             if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty)
  426.                 continue;
  427.  
  428.             var individualValue = style.getPropertyValue(individualProperty);
  429.             if (style.isPropertyImplicit(individualProperty) || individualValue === "initial")
  430.                 continue;
  431.  
  432.             foundProperties[individualProperty] = true;
  433.  
  434.             if (!value)
  435.                 value = "";
  436.             else if (value.length)
  437.                 value += " ";
  438.             value += individualValue;
  439.         }
  440.     }
  441.     return value;
  442. }
  443.  
  444. function getShorthandPriority(style, shorthandProperty)
  445. {
  446.     var priority = style.getPropertyPriority(shorthandProperty);
  447.     if (!priority) {
  448.         for (var i = 0; i < style.length; ++i) {
  449.             var individualProperty = style[i];
  450.             if (style.getPropertyShorthand(individualProperty) !== shorthandProperty)
  451.                 continue;
  452.             priority = style.getPropertyPriority(individualProperty);
  453.             break;
  454.         }
  455.     }
  456.     return priority;
  457. }
  458.  
  459. function getLonghandProperties(style, shorthandProperty)
  460. {
  461.     var properties = [];
  462.     var foundProperties = {};
  463.  
  464.     for (var i = 0; i < style.length; ++i) {
  465.         var individualProperty = style[i];
  466.         if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty)
  467.             continue;
  468.         foundProperties[individualProperty] = true;
  469.         properties.push(individualProperty);
  470.     }
  471.  
  472.     return properties;
  473. }
  474.  
  475. function getUniqueStyleProperties(style)
  476. {
  477.     var properties = [];
  478.     var foundProperties = {};
  479.  
  480.     for (var i = 0; i < style.length; ++i) {
  481.         var property = style[i];
  482.         if (property in foundProperties)
  483.             continue;
  484.         foundProperties[property] = true;
  485.         properties.push(property);
  486.     }
  487.  
  488.     return properties;
  489. }
  490.  
  491. function isNodeWhitespace()
  492. {
  493.     if (!this || this.nodeType !== Node.TEXT_NODE)
  494.         return false;
  495.     if (!this.nodeValue.length)
  496.         return true;
  497.     return this.nodeValue.match(/^[\s\xA0]+$/);
  498. }
  499.  
  500. function nodeTypeName()
  501. {
  502.     if (!this)
  503.         return "(unknown)";
  504.  
  505.     switch (this.nodeType) {
  506.         case Node.ELEMENT_NODE: return "Element";
  507.         case Node.ATTRIBUTE_NODE: return "Attribute";
  508.         case Node.TEXT_NODE: return "Text";
  509.         case Node.CDATA_SECTION_NODE: return "Character Data";
  510.         case Node.ENTITY_REFERENCE_NODE: return "Entity Reference";
  511.         case Node.ENTITY_NODE: return "Entity";
  512.         case Node.PROCESSING_INSTRUCTION_NODE: return "Processing Instruction";
  513.         case Node.COMMENT_NODE: return "Comment";
  514.         case Node.DOCUMENT_NODE: return "Document";
  515.         case Node.DOCUMENT_TYPE_NODE: return "Document Type";
  516.         case Node.DOCUMENT_FRAGMENT_NODE: return "Document Fragment";
  517.         case Node.NOTATION_NODE: return "Notation";
  518.     }
  519.  
  520.     return "(unknown)";
  521. }
  522.  
  523. function nodeDisplayName()
  524. {
  525.     if (!this)
  526.         return "";
  527.  
  528.     switch (this.nodeType) {
  529.         case Node.DOCUMENT_NODE:
  530.             return "Document";
  531.  
  532.         case Node.ELEMENT_NODE:
  533.             var name = "<" + this.nodeName.toLowerCase();
  534.  
  535.             if (this.hasAttributes()) {
  536.                 var value = this.getAttribute("id");
  537.                 if (value)
  538.                     name += " id=\"" + value + "\"";
  539.                 value = this.getAttribute("class");
  540.                 if (value)
  541.                     name += " class=\"" + value + "\"";
  542.                 if (this.nodeName.toLowerCase() === "a") {
  543.                     value = this.getAttribute("name");
  544.                     if (value)
  545.                         name += " name=\"" + value + "\"";
  546.                     value = this.getAttribute("href");
  547.                     if (value)
  548.                         name += " href=\"" + value + "\"";
  549.                 } else if (this.nodeName.toLowerCase() === "img") {
  550.                     value = this.getAttribute("src");
  551.                     if (value)
  552.                         name += " src=\"" + value + "\"";
  553.                 } else if (this.nodeName.toLowerCase() === "iframe") {
  554.                     value = this.getAttribute("src");
  555.                     if (value)
  556.                         name += " src=\"" + value + "\"";
  557.                 } else if (this.nodeName.toLowerCase() === "input") {
  558.                     value = this.getAttribute("name");
  559.                     if (value)
  560.                         name += " name=\"" + value + "\"";
  561.                     value = this.getAttribute("type");
  562.                     if (value)
  563.                         name += " type=\"" + value + "\"";
  564.                 } else if (this.nodeName.toLowerCase() === "form") {
  565.                     value = this.getAttribute("action");
  566.                     if (value)
  567.                         name += " action=\"" + value + "\"";
  568.                 }
  569.             }
  570.  
  571.             return name + ">";
  572.  
  573.         case Node.TEXT_NODE:
  574.             if (isNodeWhitespace.call(this))
  575.                 return "(whitespace)";
  576.             return "\"" + this.nodeValue + "\"";
  577.  
  578.         case Node.COMMENT_NODE:
  579.             return "<!--" + this.nodeValue + "-->";
  580.             
  581.         case Node.DOCUMENT_TYPE_NODE:
  582.             var docType = "<!DOCTYPE " + this.nodeName;
  583.             if (this.publicId) {
  584.                 docType += " PUBLIC \"" + this.publicId + "\"";
  585.                 if (this.systemId)
  586.                     docType += " \"" + this.systemId + "\"";
  587.             } else if (this.systemId)
  588.                 docType += " SYSTEM \"" + this.systemId + "\"";
  589.             if (this.internalSubset)
  590.                 docType += " [" + this.internalSubset + "]";
  591.             return docType + ">";
  592.     }
  593.  
  594.     return this.nodeName.toLowerCase().collapseWhitespace();
  595. }
  596.  
  597. function nodeContentPreview()
  598. {
  599.     if (!this || !this.hasChildNodes || !this.hasChildNodes())
  600.         return "";
  601.  
  602.     var limit = 0;
  603.     var preview = "";
  604.  
  605.     // always skip whitespace here
  606.     var currentNode = traverseNextNode.call(this, true, this);
  607.     while (currentNode) {
  608.         if (currentNode.nodeType === Node.TEXT_NODE)
  609.             preview += currentNode.nodeValue.escapeHTML();
  610.         else
  611.             preview += nodeDisplayName.call(currentNode).escapeHTML();
  612.  
  613.         currentNode = traverseNextNode.call(currentNode, true, this);
  614.  
  615.         if (++limit > 4) {
  616.             preview += "…"; // ellipsis
  617.             break;
  618.         }
  619.     }
  620.  
  621.     return preview.collapseWhitespace();
  622. }
  623.  
  624. function objectsAreSame(a, b)
  625. {
  626.     // FIXME: Make this more generic so is works with any wrapped object, not just nodes.
  627.     // This function is used to compare nodes that might be JSInspectedObjectWrappers, since
  628.     // JavaScript equality is not true for JSInspectedObjectWrappers of the same node wrapped
  629.     // with different global ExecStates, we use isSameNode to compare them.
  630.     if (a === b)
  631.         return true;
  632.     if (!a || !b)
  633.         return false;
  634.     if (a.isSameNode && b.isSameNode)
  635.         return a.isSameNode(b);
  636.     return false;
  637. }
  638.  
  639. function isAncestorNode(ancestor)
  640. {
  641.     if (!this || !ancestor)
  642.         return false;
  643.  
  644.     var currentNode = ancestor.parentNode;
  645.     while (currentNode) {
  646.         if (objectsAreSame(this, currentNode))
  647.             return true;
  648.         currentNode = currentNode.parentNode;
  649.     }
  650.  
  651.     return false;
  652. }
  653.  
  654. function isDescendantNode(descendant)
  655. {
  656.     return isAncestorNode.call(descendant, this);
  657. }
  658.  
  659. function firstCommonNodeAncestor(node)
  660. {
  661.     if (!this || !node)
  662.         return;
  663.  
  664.     var node1 = this.parentNode;
  665.     var node2 = node.parentNode;
  666.  
  667.     if ((!node1 || !node2) || !objectsAreSame(node1, node2))
  668.         return null;
  669.  
  670.     while (node1 && node2) {
  671.         if (!node1.parentNode || !node2.parentNode)
  672.             break;
  673.         if (!objectsAreSame(node1, node2))
  674.             break;
  675.  
  676.         node1 = node1.parentNode;
  677.         node2 = node2.parentNode;
  678.     }
  679.  
  680.     return node1;
  681. }
  682.  
  683. function nextSiblingSkippingWhitespace()
  684. {
  685.     if (!this)
  686.         return;
  687.     var node = this.nextSibling;
  688.     while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node))
  689.         node = node.nextSibling;
  690.     return node;
  691. }
  692.  
  693. function previousSiblingSkippingWhitespace()
  694. {
  695.     if (!this)
  696.         return;
  697.     var node = this.previousSibling;
  698.     while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node))
  699.         node = node.previousSibling;
  700.     return node;
  701. }
  702.  
  703. function firstChildSkippingWhitespace()
  704. {
  705.     if (!this)
  706.         return;
  707.     var node = this.firstChild;
  708.     while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node))
  709.         node = nextSiblingSkippingWhitespace.call(node);
  710.     return node;
  711. }
  712.  
  713. function lastChildSkippingWhitespace()
  714. {
  715.     if (!this)
  716.         return;
  717.     var node = this.lastChild;
  718.     while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node))
  719.         node = previousSiblingSkippingWhitespace.call(node);
  720.     return node;
  721. }
  722.  
  723. function traverseNextNode(skipWhitespace, stayWithin)
  724. {
  725.     if (!this)
  726.         return;
  727.  
  728.     var node = skipWhitespace ? firstChildSkippingWhitespace.call(this) : this.firstChild;
  729.     if (node)
  730.         return node;
  731.  
  732.     if (stayWithin && objectsAreSame(this, stayWithin))
  733.         return null;
  734.  
  735.     node = skipWhitespace ? nextSiblingSkippingWhitespace.call(this) : this.nextSibling;
  736.     if (node)
  737.         return node;
  738.  
  739.     node = this;
  740.     while (node && !(skipWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling) && (!stayWithin || !node.parentNode || !objectsAreSame(node.parentNode, stayWithin)))
  741.         node = node.parentNode;
  742.     if (!node)
  743.         return null;
  744.  
  745.     return skipWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling;
  746. }
  747.  
  748. function traversePreviousNode(skipWhitespace, stayWithin)
  749. {
  750.     if (!this)
  751.         return;
  752.     if (stayWithin && objectsAreSame(this, stayWithin))
  753.         return null;
  754.     var node = skipWhitespace ? previousSiblingSkippingWhitespace.call(this) : this.previousSibling;
  755.     while (node && (skipWhitespace ? lastChildSkippingWhitespace.call(node) : node.lastChild) )
  756.         node = skipWhitespace ? lastChildSkippingWhitespace.call(node) : node.lastChild;
  757.     if (node)
  758.         return node;
  759.     return this.parentNode;
  760. }
  761.  
  762. function onlyTextChild(ignoreWhitespace)
  763. {
  764.     if (!this)
  765.         return null;
  766.  
  767.     var firstChild = ignoreWhitespace ? firstChildSkippingWhitespace.call(this) : this.firstChild;
  768.     if (!firstChild || firstChild.nodeType !== Node.TEXT_NODE)
  769.         return null;
  770.  
  771.     var sibling = ignoreWhitespace ? nextSiblingSkippingWhitespace.call(firstChild) : firstChild.nextSibling;
  772.     return sibling ? null : firstChild;
  773. }
  774.  
  775. function nodeTitleInfo(hasChildren, linkify)
  776. {
  777.     var info = {title: "", hasChildren: hasChildren};
  778.  
  779.     switch (this.nodeType) {
  780.         case Node.DOCUMENT_NODE:
  781.             info.title = "Document";
  782.             break;
  783.  
  784.         case Node.ELEMENT_NODE:
  785.             info.title = "<span class=\"webkit-html-tag\"><" + this.nodeName.toLowerCase().escapeHTML();
  786.  
  787.             if (this.hasAttributes()) {
  788.                 for (var i = 0; i < this.attributes.length; ++i) {
  789.                     var attr = this.attributes[i];
  790.                     info.title += " <span class=\"webkit-html-attribute\"><span class=\"webkit-html-attribute-name\">" + attr.name.escapeHTML() + "</span>=​\"";
  791.  
  792.                     var value = attr.value;
  793.                     if (linkify && (attr.name === "src" || attr.name === "href")) {
  794.                         var value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B");
  795.                         info.title += linkify(attr.value, value, "webkit-html-attribute-value", this.nodeName.toLowerCase() == "a");
  796.                     } else {
  797.                         var value = value.escapeHTML();
  798.                         value = value.replace(/([\/;:\)\]\}])/g, "$1​");
  799.                         info.title += "<span class=\"webkit-html-attribute-value\">" + value + "</span>";
  800.                     }
  801.                     info.title += "\"</span>";
  802.                 }
  803.             }
  804.             info.title += "></span>​";
  805.  
  806.             // If this element only has a single child that is a text node,
  807.             // just show that text and the closing tag inline rather than
  808.             // create a subtree for them
  809.  
  810.             var textChild = onlyTextChild.call(this, Preferences.ignoreWhitespace);
  811.             var showInlineText = textChild && textChild.textContent.length < Preferences.maxInlineTextChildLength;
  812.  
  813.             if (showInlineText) {
  814.                 info.title += "<span class=\"webkit-html-text-node\">" + textChild.nodeValue.escapeHTML() + "</span>​<span class=\"webkit-html-tag\"></" + this.nodeName.toLowerCase().escapeHTML() + "></span>";
  815.                 info.hasChildren = false;
  816.             }
  817.             break;
  818.  
  819.         case Node.TEXT_NODE:
  820.             if (isNodeWhitespace.call(this))
  821.                 info.title = "(whitespace)";
  822.             else
  823.                 info.title = "\"<span class=\"webkit-html-text-node\">" + this.nodeValue.escapeHTML() + "</span>\"";
  824.             break
  825.  
  826.         case Node.COMMENT_NODE:
  827.             info.title = "<span class=\"webkit-html-comment\"><!--" + this.nodeValue.escapeHTML() + "--></span>";
  828.             break;
  829.  
  830.         case Node.DOCUMENT_TYPE_NODE:
  831.             info.title = "<span class=\"webkit-html-doctype\"><!DOCTYPE " + this.nodeName;
  832.             if (this.publicId) {
  833.                 info.title += " PUBLIC \"" + this.publicId + "\"";
  834.                 if (this.systemId)
  835.                     info.title += " \"" + this.systemId + "\"";
  836.             } else if (this.systemId)
  837.                 info.title += " SYSTEM \"" + this.systemId + "\"";
  838.             if (this.internalSubset)
  839.                 info.title += " [" + this.internalSubset + "]";
  840.             info.title += "></span>";
  841.             break;
  842.         default:
  843.             info.title = this.nodeName.toLowerCase().collapseWhitespace().escapeHTML();
  844.     }
  845.  
  846.     return info;
  847. }
  848.  
  849. function getDocumentForNode(node) {
  850.     return node.nodeType == Node.DOCUMENT_NODE ? node : node.ownerDocument;
  851. }
  852.  
  853. function parentNodeOrFrameElement(node) {
  854.     var parent = node.parentNode;
  855.     if (parent)
  856.         return parent;
  857.  
  858.     return getDocumentForNode(node).defaultView.frameElement;
  859. }
  860.  
  861. function isAncestorIncludingParentFrames(a, b) {
  862.     if (objectsAreSame(a, b))
  863.         return false;
  864.     for (var node = b; node; node = getDocumentForNode(node).defaultView.frameElement)
  865.         if (objectsAreSame(a, node) || isAncestorNode.call(a, node))
  866.             return true;
  867.     return false;
  868. }
  869.  
  870. Number.secondsToString = function(seconds, formatterFunction, higherResolution)
  871. {
  872.     if (!formatterFunction)
  873.         formatterFunction = String.sprintf;
  874.  
  875.     var ms = seconds * 1000;
  876.     if (higherResolution && ms < 1000)
  877.         return formatterFunction("%.3fms", ms);
  878.     else if (ms < 1000)
  879.         return formatterFunction("%.0fms", ms);
  880.  
  881.     if (seconds < 60)
  882.         return formatterFunction("%.2fs", seconds);
  883.  
  884.     var minutes = seconds / 60;
  885.     if (minutes < 60)
  886.         return formatterFunction("%.1fmin", minutes);
  887.  
  888.     var hours = minutes / 60;
  889.     if (hours < 24)
  890.         return formatterFunction("%.1fhrs", hours);
  891.  
  892.     var days = hours / 24;
  893.     return formatterFunction("%.1f days", days);
  894. }
  895.  
  896. Number.bytesToString = function(bytes, formatterFunction)
  897. {
  898.     if (!formatterFunction)
  899.         formatterFunction = String.sprintf;
  900.  
  901.     if (bytes < 1024)
  902.         return formatterFunction("%.0fB", bytes);
  903.  
  904.     var kilobytes = bytes / 1024;
  905.     if (kilobytes < 1024)
  906.         return formatterFunction("%.2fKB", kilobytes);
  907.  
  908.     var megabytes = kilobytes / 1024;
  909.     return formatterFunction("%.3fMB", megabytes);
  910. }
  911.  
  912. Number.constrain = function(num, min, max)
  913. {
  914.     if (num < min)
  915.         num = min;
  916.     else if (num > max)
  917.         num = max;
  918.     return num;
  919. }
  920.  
  921. HTMLTextAreaElement.prototype.moveCursorToEnd = function()
  922. {
  923.     var length = this.value.length;
  924.     this.setSelectionRange(length, length);
  925. }
  926.  
  927. Array.prototype.remove = function(value, onlyFirst)
  928. {
  929.     if (onlyFirst) {
  930.         var index = this.indexOf(value);
  931.         if (index !== -1)
  932.             this.splice(index, 1);
  933.         return;
  934.     }
  935.  
  936.     var length = this.length;
  937.     for (var i = 0; i < length; ++i) {
  938.         if (this[i] === value)
  939.             this.splice(i, 1);
  940.     }
  941. }
  942.  
  943. function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunction)
  944. {
  945.     // indexOf returns (-lowerBound - 1). Taking (-result - 1) works out to lowerBound.
  946.     return (-indexOfObjectInListSortedByFunction(anObject, aList, aFunction) - 1);
  947. }
  948.  
  949. function indexOfObjectInListSortedByFunction(anObject, aList, aFunction)
  950. {
  951.     var first = 0;
  952.     var last = aList.length - 1;
  953.     var floor = Math.floor;
  954.     var mid, c;
  955.  
  956.     while (first <= last) {
  957.         mid = floor((first + last) / 2);
  958.         c = aFunction(anObject, aList[mid]);
  959.  
  960.         if (c > 0)
  961.             first = mid + 1;
  962.         else if (c < 0)
  963.             last = mid - 1;
  964.         else {
  965.             // Return the first occurance of an item in the list.
  966.             while (mid > 0 && aFunction(anObject, aList[mid - 1]) === 0)
  967.                 mid--;
  968.             first = mid;
  969.             break;
  970.         }
  971.     }
  972.  
  973.     // By returning 1 less than the negative lower search bound, we can reuse this function
  974.     // for both indexOf and insertionIndexFor, with some simple arithmetic.
  975.     return (-first - 1);
  976. }
  977.  
  978. String.sprintf = function(format)
  979. {
  980.     return String.vsprintf(format, Array.prototype.slice.call(arguments, 1));
  981. }
  982.  
  983. String.tokenizeFormatString = function(format)
  984. {
  985.     var tokens = [];
  986.     var substitutionIndex = 0;
  987.  
  988.     function addStringToken(str)
  989.     {
  990.         tokens.push({ type: "string", value: str });
  991.     }
  992.  
  993.     function addSpecifierToken(specifier, precision, substitutionIndex)
  994.     {
  995.         tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex });
  996.     }
  997.  
  998.     var index = 0;
  999.     for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) {
  1000.         addStringToken(format.substring(index, precentIndex));
  1001.         index = precentIndex + 1;
  1002.  
  1003.         if (format[index] === "%") {
  1004.             addStringToken("%");
  1005.             ++index;
  1006.             continue;
  1007.         }
  1008.  
  1009.         if (!isNaN(format[index])) {
  1010.             // The first character is a number, it might be a substitution index.
  1011.             var number = parseInt(format.substring(index));
  1012.             while (!isNaN(format[index]))
  1013.                 ++index;
  1014.             // If the number is greater than zero and ends with a "$",
  1015.             // then this is a substitution index.
  1016.             if (number > 0 && format[index] === "$") {
  1017.                 substitutionIndex = (number - 1);
  1018.                 ++index;
  1019.             }
  1020.         }
  1021.  
  1022.         var precision = -1;
  1023.         if (format[index] === ".") {
  1024.             // This is a precision specifier. If no digit follows the ".",
  1025.             // then the precision should be zero.
  1026.             ++index;
  1027.             precision = parseInt(format.substring(index));
  1028.             if (isNaN(precision))
  1029.                 precision = 0;
  1030.             while (!isNaN(format[index]))
  1031.                 ++index;
  1032.         }
  1033.  
  1034.         addSpecifierToken(format[index], precision, substitutionIndex);
  1035.  
  1036.         ++substitutionIndex;
  1037.         ++index;
  1038.     }
  1039.  
  1040.     addStringToken(format.substring(index));
  1041.  
  1042.     return tokens;
  1043. }
  1044.  
  1045. String.standardFormatters = {
  1046.     d: function(substitution)
  1047.     {
  1048.         substitution = parseInt(substitution);
  1049.         return !isNaN(substitution) ? substitution : 0;
  1050.     },
  1051.  
  1052.     f: function(substitution, token)
  1053.     {
  1054.         substitution = parseFloat(substitution);
  1055.         if (substitution && token.precision > -1)
  1056.             substitution = substitution.toFixed(token.precision);
  1057.         return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0);
  1058.     },
  1059.  
  1060.     s: function(substitution)
  1061.     {
  1062.         return substitution;
  1063.     },
  1064. };
  1065.  
  1066. String.vsprintf = function(format, substitutions)
  1067. {
  1068.     return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
  1069. }
  1070.  
  1071. String.format = function(format, substitutions, formatters, initialValue, append)
  1072. {
  1073.     if (!format || !substitutions || !substitutions.length)
  1074.         return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions };
  1075.  
  1076.     function prettyFunctionName()
  1077.     {
  1078.         return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")";
  1079.     }
  1080.  
  1081.     function warn(msg)
  1082.     {
  1083.         console.warn(prettyFunctionName() + ": " + msg);
  1084.     }
  1085.  
  1086.     function error(msg)
  1087.     {
  1088.         console.error(prettyFunctionName() + ": " + msg);
  1089.     }
  1090.  
  1091.     var result = initialValue;
  1092.     var tokens = String.tokenizeFormatString(format);
  1093.     var usedSubstitutionIndexes = {};
  1094.  
  1095.     for (var i = 0; i < tokens.length; ++i) {
  1096.         var token = tokens[i];
  1097.  
  1098.         if (token.type === "string") {
  1099.             result = append(result, token.value);
  1100.             continue;
  1101.         }
  1102.  
  1103.         if (token.type !== "specifier") {
  1104.             error("Unknown token type \"" + token.type + "\" found.");
  1105.             continue;
  1106.         }
  1107.  
  1108.         if (token.substitutionIndex >= substitutions.length) {
  1109.             // If there are not enough substitutions for the current substitutionIndex
  1110.             // just output the format specifier literally and move on.
  1111.             error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped.");
  1112.             result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier);
  1113.             continue;
  1114.         }
  1115.  
  1116.         usedSubstitutionIndexes[token.substitutionIndex] = true;
  1117.  
  1118.         if (!(token.specifier in formatters)) {
  1119.             // Encountered an unsupported format character, treat as a string.
  1120.             warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string.");
  1121.             result = append(result, substitutions[token.substitutionIndex]);
  1122.             continue;
  1123.         }
  1124.  
  1125.         result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token));
  1126.     }
  1127.  
  1128.     var unusedSubstitutions = [];
  1129.     for (var i = 0; i < substitutions.length; ++i) {
  1130.         if (i in usedSubstitutionIndexes)
  1131.             continue;
  1132.         unusedSubstitutions.push(substitutions[i]);
  1133.     }
  1134.  
  1135.     return { formattedResult: result, unusedSubstitutions: unusedSubstitutions };
  1136. }
  1137.