home *** CD-ROM | disk | FTP | other *** search
/ Computer Active Guide 2009 July / CAG7.ISO / Internetas / SafariSetup.exe / AppleApplicationSupport.msi / WebKit.resources_inspector_utilities.js < prev    next >
Encoding:
Text File  |  2010-06-03  |  27.0 KB  |  921 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.proxyType = function(objectProxy)
  30. {
  31.     if (objectProxy === null)
  32.         return "null";
  33.  
  34.     var type = typeof objectProxy;
  35.     if (type !== "object" && type !== "function")
  36.         return type;
  37.  
  38.     return objectProxy.type;
  39. }
  40.  
  41. Object.properties = function(obj)
  42. {
  43.     var properties = [];
  44.     for (var prop in obj)
  45.         properties.push(prop);
  46.     return properties;
  47. }
  48.  
  49. Object.sortedProperties = function(obj, sortFunc)
  50. {
  51.     return Object.properties(obj).sort(sortFunc);
  52. }
  53.  
  54. Function.prototype.bind = function(thisObject)
  55. {
  56.     var func = this;
  57.     var args = Array.prototype.slice.call(arguments, 1);
  58.     return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))) };
  59. }
  60.  
  61. Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, direction)
  62. {
  63.     var startNode;
  64.     var startOffset = 0;
  65.     var endNode;
  66.     var endOffset = 0;
  67.  
  68.     if (!stayWithinNode)
  69.         stayWithinNode = this;
  70.  
  71.     if (!direction || direction === "backward" || direction === "both") {
  72.         var node = this;
  73.         while (node) {
  74.             if (node === stayWithinNode) {
  75.                 if (!startNode)
  76.                     startNode = stayWithinNode;
  77.                 break;
  78.             }
  79.  
  80.             if (node.nodeType === Node.TEXT_NODE) {
  81.                 var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1));
  82.                 for (var i = start; i >= 0; --i) {
  83.                     if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
  84.                         startNode = node;
  85.                         startOffset = i + 1;
  86.                         break;
  87.                     }
  88.                 }
  89.             }
  90.  
  91.             if (startNode)
  92.                 break;
  93.  
  94.             node = node.traversePreviousNode(stayWithinNode);
  95.         }
  96.  
  97.         if (!startNode) {
  98.             startNode = stayWithinNode;
  99.             startOffset = 0;
  100.         }
  101.     } else {
  102.         startNode = this;
  103.         startOffset = offset;
  104.     }
  105.  
  106.     if (!direction || direction === "forward" || direction === "both") {
  107.         node = this;
  108.         while (node) {
  109.             if (node === stayWithinNode) {
  110.                 if (!endNode)
  111.                     endNode = stayWithinNode;
  112.                 break;
  113.             }
  114.  
  115.             if (node.nodeType === Node.TEXT_NODE) {
  116.                 var start = (node === this ? offset : 0);
  117.                 for (var i = start; i < node.nodeValue.length; ++i) {
  118.                     if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
  119.                         endNode = node;
  120.                         endOffset = i;
  121.                         break;
  122.                     }
  123.                 }
  124.             }
  125.  
  126.             if (endNode)
  127.                 break;
  128.  
  129.             node = node.traverseNextNode(stayWithinNode);
  130.         }
  131.  
  132.         if (!endNode) {
  133.             endNode = stayWithinNode;
  134.             endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length;
  135.         }
  136.     } else {
  137.         endNode = this;
  138.         endOffset = offset;
  139.     }
  140.  
  141.     var result = this.ownerDocument.createRange();
  142.     result.setStart(startNode, startOffset);
  143.     result.setEnd(endNode, endOffset);
  144.  
  145.     return result;
  146. }
  147.  
  148. Node.prototype.traverseNextTextNode = function(stayWithin)
  149. {
  150.     var node = this.traverseNextNode(stayWithin);
  151.     if (!node)
  152.         return;
  153.  
  154.     while (node && node.nodeType !== Node.TEXT_NODE)
  155.         node = node.traverseNextNode(stayWithin);
  156.  
  157.     return node;
  158. }
  159.  
  160. Node.prototype.rangeBoundaryForOffset = function(offset)
  161. {
  162.     var node = this.traverseNextTextNode(this);
  163.     while (node && offset > node.nodeValue.length) {
  164.         offset -= node.nodeValue.length;
  165.         node = node.traverseNextTextNode(this);
  166.     }
  167.     if (!node)
  168.         return { container: this, offset: 0 };
  169.     return { container: node, offset: offset };
  170. }
  171.  
  172. Element.prototype.removeStyleClass = function(className) 
  173. {
  174.     // Test for the simple case first.
  175.     if (this.className === className) {
  176.         this.className = "";
  177.         return;
  178.     }
  179.  
  180.     var index = this.className.indexOf(className);
  181.     if (index === -1)
  182.         return;
  183.  
  184.     var newClassName = " " + this.className + " ";
  185.     this.className = newClassName.replace(" " + className + " ", " ");
  186. }
  187.  
  188. Element.prototype.removeMatchingStyleClasses = function(classNameRegex)
  189. {
  190.     var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)");
  191.     if (regex.test(this.className))
  192.         this.className = this.className.replace(regex, " ");
  193. }
  194.  
  195. Element.prototype.addStyleClass = function(className) 
  196. {
  197.     if (className && !this.hasStyleClass(className))
  198.         this.className += (this.className.length ? " " + className : className);
  199. }
  200.  
  201. Element.prototype.hasStyleClass = function(className) 
  202. {
  203.     if (!className)
  204.         return false;
  205.     // Test for the simple case
  206.     if (this.className === className)
  207.         return true;
  208.  
  209.     var index = this.className.indexOf(className);
  210.     if (index === -1)
  211.         return false;
  212.     var toTest = " " + this.className + " ";
  213.     return toTest.indexOf(" " + className + " ", index) !== -1;
  214. }
  215.  
  216. Element.prototype.positionAt = function(x, y)
  217. {
  218.     this.style.left = x + "px";
  219.     this.style.top = y + "px";
  220. }
  221.  
  222. Element.prototype.pruneEmptyTextNodes = function()
  223. {
  224.     var sibling = this.firstChild;
  225.     while (sibling) {
  226.         var nextSibling = sibling.nextSibling;
  227.         if (sibling.nodeType === this.TEXT_NODE && sibling.nodeValue === "")
  228.             this.removeChild(sibling);
  229.         sibling = nextSibling;
  230.     }
  231. }
  232.  
  233. Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray)
  234. {
  235.     for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
  236.         for (var i = 0; i < nameArray.length; ++i)
  237.             if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase())
  238.                 return node;
  239.     return null;
  240. }
  241.  
  242. Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName)
  243. {
  244.     return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]);
  245. }
  246.  
  247. Node.prototype.enclosingNodeOrSelfWithClass = function(className)
  248. {
  249.     for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
  250.         if (node.nodeType === Node.ELEMENT_NODE && node.hasStyleClass(className))
  251.             return node;
  252.     return null;
  253. }
  254.  
  255. Node.prototype.enclosingNodeWithClass = function(className)
  256. {
  257.     if (!this.parentNode)
  258.         return null;
  259.     return this.parentNode.enclosingNodeOrSelfWithClass(className);
  260. }
  261.  
  262. Element.prototype.query = function(query) 
  263. {
  264.     return this.ownerDocument.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  265. }
  266.  
  267. Element.prototype.removeChildren = function()
  268. {
  269.     this.innerHTML = "";
  270. }
  271.  
  272. Element.prototype.isInsertionCaretInside = function()
  273. {
  274.     var selection = window.getSelection();
  275.     if (!selection.rangeCount || !selection.isCollapsed)
  276.         return false;
  277.     var selectionRange = selection.getRangeAt(0);
  278.     return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this);
  279. }
  280.  
  281. Element.prototype.__defineGetter__("totalOffsetLeft", function()
  282. {
  283.     var total = 0;
  284.     for (var element = this; element; element = element.offsetParent)
  285.         total += element.offsetLeft + (this !== element ? element.clientLeft : 0);
  286.     return total;
  287. });
  288.  
  289. Element.prototype.__defineGetter__("totalOffsetTop", function()
  290. {
  291.     var total = 0;
  292.     for (var element = this; element; element = element.offsetParent)
  293.         total += element.offsetTop + (this !== element ? element.clientTop : 0);
  294.     return total;
  295. });
  296.  
  297. Element.prototype.offsetRelativeToWindow = function(targetWindow)
  298. {
  299.     var elementOffset = {x: 0, y: 0};
  300.     var curElement = this;
  301.     var curWindow = this.ownerDocument.defaultView;
  302.     while (curWindow && curElement) {
  303.         elementOffset.x += curElement.totalOffsetLeft;
  304.         elementOffset.y += curElement.totalOffsetTop;
  305.         if (curWindow === targetWindow)
  306.             break;
  307.  
  308.         curElement = curWindow.frameElement;
  309.         curWindow = curWindow.parent;
  310.     }
  311.  
  312.     return elementOffset;
  313. }
  314.  
  315. Node.prototype.isWhitespace = isNodeWhitespace;
  316. Node.prototype.displayName = nodeDisplayName;
  317. Node.prototype.isAncestor = function(node)
  318. {
  319.     return isAncestorNode(this, node);
  320. };
  321. Node.prototype.isDescendant = isDescendantNode;
  322. Node.prototype.traverseNextNode = traverseNextNode;
  323. Node.prototype.traversePreviousNode = traversePreviousNode;
  324. Node.prototype.onlyTextChild = onlyTextChild;
  325.  
  326. String.prototype.hasSubstring = function(string, caseInsensitive)
  327. {
  328.     if (!caseInsensitive)
  329.         return this.indexOf(string) !== -1;
  330.     return this.match(new RegExp(string.escapeForRegExp(), "i"));
  331. }
  332.  
  333. String.prototype.escapeCharacters = function(chars)
  334. {
  335.     var foundChar = false;
  336.     for (var i = 0; i < chars.length; ++i) {
  337.         if (this.indexOf(chars.charAt(i)) !== -1) {
  338.             foundChar = true;
  339.             break;
  340.         }
  341.     }
  342.  
  343.     if (!foundChar)
  344.         return this;
  345.  
  346.     var result = "";
  347.     for (var i = 0; i < this.length; ++i) {
  348.         if (chars.indexOf(this.charAt(i)) !== -1)
  349.             result += "\\";
  350.         result += this.charAt(i);
  351.     }
  352.  
  353.     return result;
  354. }
  355.  
  356. String.prototype.escapeForRegExp = function()
  357. {
  358.     return this.escapeCharacters("^[]{}()\\.$*+?|");
  359. }
  360.  
  361. String.prototype.escapeHTML = function()
  362. {
  363.     return this.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
  364. }
  365.  
  366. String.prototype.collapseWhitespace = function()
  367. {
  368.     return this.replace(/[\s\xA0]+/g, " ");
  369. }
  370.  
  371. String.prototype.trimURL = function(baseURLDomain)
  372. {
  373.     var result = this.replace(/^(https|http|file):\/\//i, "");
  374.     if (baseURLDomain)
  375.         result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), "");
  376.     return result;
  377. }
  378.  
  379. function isNodeWhitespace()
  380. {
  381.     if (!this || this.nodeType !== Node.TEXT_NODE)
  382.         return false;
  383.     if (!this.nodeValue.length)
  384.         return true;
  385.     return this.nodeValue.match(/^[\s\xA0]+$/);
  386. }
  387.  
  388. function nodeDisplayName()
  389. {
  390.     if (!this)
  391.         return "";
  392.  
  393.     switch (this.nodeType) {
  394.         case Node.DOCUMENT_NODE:
  395.             return "Document";
  396.  
  397.         case Node.ELEMENT_NODE:
  398.             var name = "<" + this.nodeName.toLowerCase();
  399.  
  400.             if (this.hasAttributes()) {
  401.                 var value = this.getAttribute("id");
  402.                 if (value)
  403.                     name += " id=\"" + value + "\"";
  404.                 value = this.getAttribute("class");
  405.                 if (value)
  406.                     name += " class=\"" + value + "\"";
  407.                 if (this.nodeName.toLowerCase() === "a") {
  408.                     value = this.getAttribute("name");
  409.                     if (value)
  410.                         name += " name=\"" + value + "\"";
  411.                     value = this.getAttribute("href");
  412.                     if (value)
  413.                         name += " href=\"" + value + "\"";
  414.                 } else if (this.nodeName.toLowerCase() === "img") {
  415.                     value = this.getAttribute("src");
  416.                     if (value)
  417.                         name += " src=\"" + value + "\"";
  418.                 } else if (this.nodeName.toLowerCase() === "iframe") {
  419.                     value = this.getAttribute("src");
  420.                     if (value)
  421.                         name += " src=\"" + value + "\"";
  422.                 } else if (this.nodeName.toLowerCase() === "input") {
  423.                     value = this.getAttribute("name");
  424.                     if (value)
  425.                         name += " name=\"" + value + "\"";
  426.                     value = this.getAttribute("type");
  427.                     if (value)
  428.                         name += " type=\"" + value + "\"";
  429.                 } else if (this.nodeName.toLowerCase() === "form") {
  430.                     value = this.getAttribute("action");
  431.                     if (value)
  432.                         name += " action=\"" + value + "\"";
  433.                 }
  434.             }
  435.  
  436.             return name + ">";
  437.  
  438.         case Node.TEXT_NODE:
  439.             if (isNodeWhitespace.call(this))
  440.                 return "(whitespace)";
  441.             return "\"" + this.nodeValue + "\"";
  442.  
  443.         case Node.COMMENT_NODE:
  444.             return "<!--" + this.nodeValue + "-->";
  445.             
  446.         case Node.DOCUMENT_TYPE_NODE:
  447.             var docType = "<!DOCTYPE " + this.nodeName;
  448.             if (this.publicId) {
  449.                 docType += " PUBLIC \"" + this.publicId + "\"";
  450.                 if (this.systemId)
  451.                     docType += " \"" + this.systemId + "\"";
  452.             } else if (this.systemId)
  453.                 docType += " SYSTEM \"" + this.systemId + "\"";
  454.             if (this.internalSubset)
  455.                 docType += " [" + this.internalSubset + "]";
  456.             return docType + ">";
  457.     }
  458.  
  459.     return this.nodeName.toLowerCase().collapseWhitespace();
  460. }
  461.  
  462. function isAncestorNode(ancestor, node)
  463. {
  464.     if (!node || !ancestor)
  465.         return false;
  466.  
  467.     var currentNode = node.parentNode;
  468.     while (currentNode) {
  469.         if (ancestor === currentNode)
  470.             return true;
  471.         currentNode = currentNode.parentNode;
  472.     }
  473.     return false;
  474. }
  475.  
  476. function isDescendantNode(descendant)
  477. {
  478.     return isAncestorNode(descendant, this);
  479. }
  480.  
  481. function traverseNextNode(stayWithin)
  482. {
  483.     if (!this)
  484.         return;
  485.  
  486.     var node = this.firstChild;
  487.     if (node)
  488.         return node;
  489.  
  490.     if (stayWithin && this === stayWithin)
  491.         return null;
  492.  
  493.     node = this.nextSibling;
  494.     if (node)
  495.         return node;
  496.  
  497.     node = this;
  498.     while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin))
  499.         node = node.parentNode;
  500.     if (!node)
  501.         return null;
  502.  
  503.     return node.nextSibling;
  504. }
  505.  
  506. function traversePreviousNode(stayWithin)
  507. {
  508.     if (!this)
  509.         return;
  510.     if (stayWithin && this === stayWithin)
  511.         return null;
  512.     var node = this.previousSibling;
  513.     while (node && node.lastChild)
  514.         node = node.lastChild;
  515.     if (node)
  516.         return node;
  517.     return this.parentNode;
  518. }
  519.  
  520. function onlyTextChild()
  521. {
  522.     if (!this)
  523.         return null;
  524.  
  525.     var firstChild = this.firstChild;
  526.     if (!firstChild || firstChild.nodeType !== Node.TEXT_NODE)
  527.         return null;
  528.  
  529.     var sibling = firstChild.nextSibling;
  530.     return sibling ? null : firstChild;
  531. }
  532.  
  533. function appropriateSelectorForNode(node, justSelector)
  534. {
  535.     if (!node)
  536.         return "";
  537.  
  538.     var lowerCaseName = node.localName || node.nodeName.toLowerCase();
  539.  
  540.     var id = node.getAttribute("id");
  541.     if (id) {
  542.         var selector = "#" + id;
  543.         return (justSelector ? selector : lowerCaseName + selector);
  544.     }
  545.  
  546.     var className = node.getAttribute("class");
  547.     if (className) {
  548.         var selector = "." + className.replace(/\s+/, ".");
  549.         return (justSelector ? selector : lowerCaseName + selector);
  550.     }
  551.  
  552.     if (lowerCaseName === "input" && node.getAttribute("type"))
  553.         return lowerCaseName + "[type=\"" + node.getAttribute("type") + "\"]";
  554.  
  555.     return lowerCaseName;
  556. }
  557.  
  558. function getDocumentForNode(node)
  559. {
  560.     return node.nodeType == Node.DOCUMENT_NODE ? node : node.ownerDocument;
  561. }
  562.  
  563. function parentNode(node)
  564. {
  565.     return node.parentNode;
  566. }
  567.  
  568. Number.secondsToString = function(seconds, formatterFunction, higherResolution)
  569. {
  570.     if (!formatterFunction)
  571.         formatterFunction = String.sprintf;
  572.  
  573.     if (seconds === 0)
  574.         return "0";
  575.  
  576.     var ms = seconds * 1000;
  577.     if (higherResolution && ms < 1000)
  578.         return formatterFunction("%.3fms", ms);
  579.     else if (ms < 1000)
  580.         return formatterFunction("%.0fms", ms);
  581.  
  582.     if (seconds < 60)
  583.         return formatterFunction("%.2fs", seconds);
  584.  
  585.     var minutes = seconds / 60;
  586.     if (minutes < 60)
  587.         return formatterFunction("%.1fmin", minutes);
  588.  
  589.     var hours = minutes / 60;
  590.     if (hours < 24)
  591.         return formatterFunction("%.1fhrs", hours);
  592.  
  593.     var days = hours / 24;
  594.     return formatterFunction("%.1f days", days);
  595. }
  596.  
  597. Number.bytesToString = function(bytes, formatterFunction, higherResolution)
  598. {
  599.     if (!formatterFunction)
  600.         formatterFunction = String.sprintf;
  601.     if (typeof higherResolution === "undefined")
  602.         higherResolution = true;
  603.  
  604.     if (bytes < 1024)
  605.         return formatterFunction("%.0fB", bytes);
  606.  
  607.     var kilobytes = bytes / 1024;
  608.     if (higherResolution && kilobytes < 1024)
  609.         return formatterFunction("%.2fKB", kilobytes);
  610.     else if (kilobytes < 1024)
  611.         return formatterFunction("%.0fKB", kilobytes);
  612.  
  613.     var megabytes = kilobytes / 1024;
  614.     if (higherResolution)
  615.         return formatterFunction("%.3fMB", megabytes);
  616.     else
  617.         return formatterFunction("%.0fMB", megabytes);
  618. }
  619.  
  620. Number.constrain = function(num, min, max)
  621. {
  622.     if (num < min)
  623.         num = min;
  624.     else if (num > max)
  625.         num = max;
  626.     return num;
  627. }
  628.  
  629. HTMLTextAreaElement.prototype.moveCursorToEnd = function()
  630. {
  631.     var length = this.value.length;
  632.     this.setSelectionRange(length, length);
  633. }
  634.  
  635. Array.prototype.remove = function(value, onlyFirst)
  636. {
  637.     if (onlyFirst) {
  638.         var index = this.indexOf(value);
  639.         if (index !== -1)
  640.             this.splice(index, 1);
  641.         return;
  642.     }
  643.  
  644.     var length = this.length;
  645.     for (var i = 0; i < length; ++i) {
  646.         if (this[i] === value)
  647.             this.splice(i, 1);
  648.     }
  649. }
  650.  
  651. Array.prototype.keySet = function()
  652. {
  653.     var keys = {};
  654.     for (var i = 0; i < this.length; ++i)
  655.         keys[this[i]] = true;
  656.     return keys;
  657. }
  658.  
  659. function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunction)
  660. {
  661.     // indexOf returns (-lowerBound - 1). Taking (-result - 1) works out to lowerBound.
  662.     return (-indexOfObjectInListSortedByFunction(anObject, aList, aFunction) - 1);
  663. }
  664.  
  665. function indexOfObjectInListSortedByFunction(anObject, aList, aFunction)
  666. {
  667.     var first = 0;
  668.     var last = aList.length - 1;
  669.     var floor = Math.floor;
  670.     var mid, c;
  671.  
  672.     while (first <= last) {
  673.         mid = floor((first + last) / 2);
  674.         c = aFunction(anObject, aList[mid]);
  675.  
  676.         if (c > 0)
  677.             first = mid + 1;
  678.         else if (c < 0)
  679.             last = mid - 1;
  680.         else {
  681.             // Return the first occurance of an item in the list.
  682.             while (mid > 0 && aFunction(anObject, aList[mid - 1]) === 0)
  683.                 mid--;
  684.             first = mid;
  685.             break;
  686.         }
  687.     }
  688.  
  689.     // By returning 1 less than the negative lower search bound, we can reuse this function
  690.     // for both indexOf and insertionIndexFor, with some simple arithmetic.
  691.     return (-first - 1);
  692. }
  693.  
  694. String.sprintf = function(format)
  695. {
  696.     return String.vsprintf(format, Array.prototype.slice.call(arguments, 1));
  697. }
  698.  
  699. String.tokenizeFormatString = function(format)
  700. {
  701.     var tokens = [];
  702.     var substitutionIndex = 0;
  703.  
  704.     function addStringToken(str)
  705.     {
  706.         tokens.push({ type: "string", value: str });
  707.     }
  708.  
  709.     function addSpecifierToken(specifier, precision, substitutionIndex)
  710.     {
  711.         tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex });
  712.     }
  713.  
  714.     var index = 0;
  715.     for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) {
  716.         addStringToken(format.substring(index, precentIndex));
  717.         index = precentIndex + 1;
  718.  
  719.         if (format[index] === "%") {
  720.             addStringToken("%");
  721.             ++index;
  722.             continue;
  723.         }
  724.  
  725.         if (!isNaN(format[index])) {
  726.             // The first character is a number, it might be a substitution index.
  727.             var number = parseInt(format.substring(index));
  728.             while (!isNaN(format[index]))
  729.                 ++index;
  730.             // If the number is greater than zero and ends with a "$",
  731.             // then this is a substitution index.
  732.             if (number > 0 && format[index] === "$") {
  733.                 substitutionIndex = (number - 1);
  734.                 ++index;
  735.             }
  736.         }
  737.  
  738.         var precision = -1;
  739.         if (format[index] === ".") {
  740.             // This is a precision specifier. If no digit follows the ".",
  741.             // then the precision should be zero.
  742.             ++index;
  743.             precision = parseInt(format.substring(index));
  744.             if (isNaN(precision))
  745.                 precision = 0;
  746.             while (!isNaN(format[index]))
  747.                 ++index;
  748.         }
  749.  
  750.         addSpecifierToken(format[index], precision, substitutionIndex);
  751.  
  752.         ++substitutionIndex;
  753.         ++index;
  754.     }
  755.  
  756.     addStringToken(format.substring(index));
  757.  
  758.     return tokens;
  759. }
  760.  
  761. String.standardFormatters = {
  762.     d: function(substitution)
  763.     {
  764.         if (typeof substitution == "object" && Object.proxyType(substitution) === "number")
  765.             substitution = substitution.description;
  766.         substitution = parseInt(substitution);
  767.         return !isNaN(substitution) ? substitution : 0;
  768.     },
  769.  
  770.     f: function(substitution, token)
  771.     {
  772.         if (typeof substitution == "object" && Object.proxyType(substitution) === "number")
  773.             substitution = substitution.description;
  774.         substitution = parseFloat(substitution);
  775.         if (substitution && token.precision > -1)
  776.             substitution = substitution.toFixed(token.precision);
  777.         return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0);
  778.     },
  779.  
  780.     s: function(substitution)
  781.     {
  782.         if (typeof substitution == "object" && Object.proxyType(substitution) !== "null")
  783.             substitution = substitution.description;
  784.         return substitution;
  785.     },
  786. };
  787.  
  788. String.vsprintf = function(format, substitutions)
  789. {
  790.     return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
  791. }
  792.  
  793. String.format = function(format, substitutions, formatters, initialValue, append)
  794. {
  795.     if (!format || !substitutions || !substitutions.length)
  796.         return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions };
  797.  
  798.     function prettyFunctionName()
  799.     {
  800.         return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")";
  801.     }
  802.  
  803.     function warn(msg)
  804.     {
  805.         console.warn(prettyFunctionName() + ": " + msg);
  806.     }
  807.  
  808.     function error(msg)
  809.     {
  810.         console.error(prettyFunctionName() + ": " + msg);
  811.     }
  812.  
  813.     var result = initialValue;
  814.     var tokens = String.tokenizeFormatString(format);
  815.     var usedSubstitutionIndexes = {};
  816.  
  817.     for (var i = 0; i < tokens.length; ++i) {
  818.         var token = tokens[i];
  819.  
  820.         if (token.type === "string") {
  821.             result = append(result, token.value);
  822.             continue;
  823.         }
  824.  
  825.         if (token.type !== "specifier") {
  826.             error("Unknown token type \"" + token.type + "\" found.");
  827.             continue;
  828.         }
  829.  
  830.         if (token.substitutionIndex >= substitutions.length) {
  831.             // If there are not enough substitutions for the current substitutionIndex
  832.             // just output the format specifier literally and move on.
  833.             error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped.");
  834.             result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier);
  835.             continue;
  836.         }
  837.  
  838.         usedSubstitutionIndexes[token.substitutionIndex] = true;
  839.  
  840.         if (!(token.specifier in formatters)) {
  841.             // Encountered an unsupported format character, treat as a string.
  842.             warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string.");
  843.             result = append(result, substitutions[token.substitutionIndex]);
  844.             continue;
  845.         }
  846.  
  847.         result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token));
  848.     }
  849.  
  850.     var unusedSubstitutions = [];
  851.     for (var i = 0; i < substitutions.length; ++i) {
  852.         if (i in usedSubstitutionIndexes)
  853.             continue;
  854.         unusedSubstitutions.push(substitutions[i]);
  855.     }
  856.  
  857.     return { formattedResult: result, unusedSubstitutions: unusedSubstitutions };
  858. }
  859.  
  860. function isEnterKey(event) {
  861.     // Check if in IME.
  862.     return event.keyCode !== 229 && event.keyIdentifier === "Enter";
  863. }
  864.  
  865.  
  866. function highlightSearchResult(element, offset, length)
  867. {
  868.     var lineText = element.textContent;
  869.     var endOffset = offset + length;
  870.     var highlightNode = document.createElement("span");
  871.     highlightNode.className = "webkit-search-result";
  872.     highlightNode.textContent = lineText.substring(offset, endOffset);
  873.  
  874.     var boundary = element.rangeBoundaryForOffset(offset);
  875.     var textNode = boundary.container;
  876.     var text = textNode.textContent;
  877.  
  878.     if (boundary.offset + length < text.length) {
  879.         // Selection belong to a single split mode.
  880.         textNode.textContent = text.substring(boundary.offset + length);
  881.         textNode.parentElement.insertBefore(highlightNode, textNode);
  882.         var prefixNode = document.createTextNode(text.substring(0, boundary.offset));
  883.         textNode.parentElement.insertBefore(prefixNode, highlightNode);
  884.         return highlightNode;
  885.     }
  886.  
  887.     var parentElement = textNode.parentElement;
  888.     var anchorElement = textNode.nextSibling;
  889.  
  890.     length -= text.length - boundary.offset;
  891.     textNode.textContent = text.substring(0, boundary.offset);
  892.     textNode = textNode.traverseNextTextNode(element);
  893.  
  894.     while (textNode) {
  895.         var text = textNode.textContent;
  896.         if (length < text.length) {
  897.             textNode.textContent = text.substring(length);
  898.             break;
  899.         }
  900.  
  901.         length -= text.length;
  902.         textNode.textContent = "";
  903.         textNode = textNode.traverseNextTextNode(element);
  904.     }
  905.  
  906.     parentElement.insertBefore(highlightNode, anchorElement);
  907.     return highlightNode;
  908. }
  909.  
  910. function createSearchRegex(query)
  911. {
  912.     var regex = "";
  913.     for (var i = 0; i < query.length; ++i) {
  914.         var char = query.charAt(i);
  915.         if (char === "]")
  916.             char = "\\]";
  917.         regex += "[" + char + "]";
  918.     }
  919.     return new RegExp(regex, "i");
  920. }
  921.