| OLD | NEW | 
 | (Empty) | 
|    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.prototyp
     e.slice.call(arguments, 0))) }; |  | 
|   59 } |  | 
|   60  |  | 
|   61 Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, di
     rection) |  | 
|   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.leng
     th - 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 ? stayWithinN
     ode.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 Element.prototype.removeStyleClass = function(className)  |  | 
|  149 { |  | 
|  150     // Test for the simple case before using a RegExp. |  | 
|  151     if (this.className === className) { |  | 
|  152         this.className = ""; |  | 
|  153         return; |  | 
|  154     } |  | 
|  155  |  | 
|  156     this.removeMatchingStyleClasses(className.escapeForRegExp()); |  | 
|  157 } |  | 
|  158  |  | 
|  159 Element.prototype.removeMatchingStyleClasses = function(classNameRegex) |  | 
|  160 { |  | 
|  161     var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)"); |  | 
|  162     if (regex.test(this.className)) |  | 
|  163         this.className = this.className.replace(regex, " "); |  | 
|  164 } |  | 
|  165  |  | 
|  166 Element.prototype.addStyleClass = function(className)  |  | 
|  167 { |  | 
|  168     if (className && !this.hasStyleClass(className)) |  | 
|  169         this.className += (this.className.length ? " " + className : className); |  | 
|  170 } |  | 
|  171  |  | 
|  172 Element.prototype.hasStyleClass = function(className)  |  | 
|  173 { |  | 
|  174     if (!className) |  | 
|  175         return false; |  | 
|  176     // Test for the simple case before using a RegExp. |  | 
|  177     if (this.className === className) |  | 
|  178         return true; |  | 
|  179     var regex = new RegExp("(^|\\s)" + className.escapeForRegExp() + "($|\\s)"); |  | 
|  180     return regex.test(this.className); |  | 
|  181 } |  | 
|  182  |  | 
|  183 Element.prototype.positionAt = function(x, y) |  | 
|  184 { |  | 
|  185     this.style.left = x + "px"; |  | 
|  186     this.style.top = y + "px"; |  | 
|  187 } |  | 
|  188  |  | 
|  189 Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray) |  | 
|  190 { |  | 
|  191     for (var node = this; node && node !== this.ownerDocument; node = node.paren
     tNode) |  | 
|  192         for (var i = 0; i < nameArray.length; ++i) |  | 
|  193             if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase()) |  | 
|  194                 return node; |  | 
|  195     return null; |  | 
|  196 } |  | 
|  197  |  | 
|  198 Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName) |  | 
|  199 { |  | 
|  200     return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]); |  | 
|  201 } |  | 
|  202  |  | 
|  203 Node.prototype.enclosingNodeOrSelfWithClass = function(className) |  | 
|  204 { |  | 
|  205     for (var node = this; node && node !== this.ownerDocument; node = node.paren
     tNode) |  | 
|  206         if (node.nodeType === Node.ELEMENT_NODE && node.hasStyleClass(className)
     ) |  | 
|  207             return node; |  | 
|  208     return null; |  | 
|  209 } |  | 
|  210  |  | 
|  211 Node.prototype.enclosingNodeWithClass = function(className) |  | 
|  212 { |  | 
|  213     if (!this.parentNode) |  | 
|  214         return null; |  | 
|  215     return this.parentNode.enclosingNodeOrSelfWithClass(className); |  | 
|  216 } |  | 
|  217  |  | 
|  218 Element.prototype.query = function(query)  |  | 
|  219 { |  | 
|  220     return this.ownerDocument.evaluate(query, this, null, XPathResult.FIRST_ORDE
     RED_NODE_TYPE, null).singleNodeValue; |  | 
|  221 } |  | 
|  222  |  | 
|  223 Element.prototype.removeChildren = function() |  | 
|  224 { |  | 
|  225     while (this.firstChild)  |  | 
|  226         this.removeChild(this.firstChild);         |  | 
|  227 } |  | 
|  228  |  | 
|  229 Element.prototype.isInsertionCaretInside = function() |  | 
|  230 { |  | 
|  231     var selection = window.getSelection(); |  | 
|  232     if (!selection.rangeCount || !selection.isCollapsed) |  | 
|  233         return false; |  | 
|  234     var selectionRange = selection.getRangeAt(0); |  | 
|  235     return selectionRange.startContainer === this || selectionRange.startContain
     er.isDescendant(this); |  | 
|  236 } |  | 
|  237  |  | 
|  238 Element.prototype.__defineGetter__("totalOffsetLeft", function() |  | 
|  239 { |  | 
|  240     var total = 0; |  | 
|  241     for (var element = this; element; element = element.offsetParent) |  | 
|  242         total += element.offsetLeft; |  | 
|  243     return total; |  | 
|  244 }); |  | 
|  245  |  | 
|  246 Element.prototype.__defineGetter__("totalOffsetTop", function() |  | 
|  247 { |  | 
|  248     var total = 0; |  | 
|  249     for (var element = this; element; element = element.offsetParent) |  | 
|  250         total += element.offsetTop; |  | 
|  251     return total; |  | 
|  252 }); |  | 
|  253  |  | 
|  254 Element.prototype.offsetRelativeToWindow = function(targetWindow) |  | 
|  255 { |  | 
|  256     var elementOffset = {x: 0, y: 0}; |  | 
|  257     var curElement = this; |  | 
|  258     var curWindow = this.ownerDocument.defaultView; |  | 
|  259     while (curWindow && curElement) { |  | 
|  260         elementOffset.x += curElement.totalOffsetLeft; |  | 
|  261         elementOffset.y += curElement.totalOffsetTop; |  | 
|  262         if (curWindow === targetWindow) |  | 
|  263             break; |  | 
|  264  |  | 
|  265         curElement = curWindow.frameElement; |  | 
|  266         curWindow = curWindow.parent; |  | 
|  267     } |  | 
|  268  |  | 
|  269     return elementOffset; |  | 
|  270 } |  | 
|  271  |  | 
|  272 Node.prototype.isWhitespace = isNodeWhitespace; |  | 
|  273 Node.prototype.displayName = nodeDisplayName; |  | 
|  274 Node.prototype.isAncestor = function(node) |  | 
|  275 { |  | 
|  276     return isAncestorNode(this, node); |  | 
|  277 }; |  | 
|  278 Node.prototype.isDescendant = isDescendantNode; |  | 
|  279 Node.prototype.traverseNextNode = traverseNextNode; |  | 
|  280 Node.prototype.traversePreviousNode = traversePreviousNode; |  | 
|  281 Node.prototype.onlyTextChild = onlyTextChild; |  | 
|  282  |  | 
|  283 String.prototype.hasSubstring = function(string, caseInsensitive) |  | 
|  284 { |  | 
|  285     if (!caseInsensitive) |  | 
|  286         return this.indexOf(string) !== -1; |  | 
|  287     return this.match(new RegExp(string.escapeForRegExp(), "i")); |  | 
|  288 } |  | 
|  289  |  | 
|  290 String.prototype.escapeCharacters = function(chars) |  | 
|  291 { |  | 
|  292     var foundChar = false; |  | 
|  293     for (var i = 0; i < chars.length; ++i) { |  | 
|  294         if (this.indexOf(chars.charAt(i)) !== -1) { |  | 
|  295             foundChar = true; |  | 
|  296             break; |  | 
|  297         } |  | 
|  298     } |  | 
|  299  |  | 
|  300     if (!foundChar) |  | 
|  301         return this; |  | 
|  302  |  | 
|  303     var result = ""; |  | 
|  304     for (var i = 0; i < this.length; ++i) { |  | 
|  305         if (chars.indexOf(this.charAt(i)) !== -1) |  | 
|  306             result += "\\"; |  | 
|  307         result += this.charAt(i); |  | 
|  308     } |  | 
|  309  |  | 
|  310     return result; |  | 
|  311 } |  | 
|  312  |  | 
|  313 String.prototype.escapeForRegExp = function() |  | 
|  314 { |  | 
|  315     return this.escapeCharacters("^[]{}()\\.$*+?|"); |  | 
|  316 } |  | 
|  317  |  | 
|  318 String.prototype.escapeHTML = function() |  | 
|  319 { |  | 
|  320     return this.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">
     "); |  | 
|  321 } |  | 
|  322  |  | 
|  323 String.prototype.collapseWhitespace = function() |  | 
|  324 { |  | 
|  325     return this.replace(/[\s\xA0]+/g, " "); |  | 
|  326 } |  | 
|  327  |  | 
|  328 String.prototype.trimLeadingWhitespace = function() |  | 
|  329 { |  | 
|  330     return this.replace(/^[\s\xA0]+/g, ""); |  | 
|  331 } |  | 
|  332  |  | 
|  333 String.prototype.trimTrailingWhitespace = function() |  | 
|  334 { |  | 
|  335     return this.replace(/[\s\xA0]+$/g, ""); |  | 
|  336 } |  | 
|  337  |  | 
|  338 String.prototype.trimWhitespace = function() |  | 
|  339 { |  | 
|  340     return this.replace(/^[\s\xA0]+|[\s\xA0]+$/g, ""); |  | 
|  341 } |  | 
|  342  |  | 
|  343 String.prototype.trimURL = function(baseURLDomain) |  | 
|  344 { |  | 
|  345     var result = this.replace(new RegExp("^http[s]?:\/\/", "i"), ""); |  | 
|  346     if (baseURLDomain) |  | 
|  347         result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp()
     , "i"), ""); |  | 
|  348     return result; |  | 
|  349 } |  | 
|  350  |  | 
|  351 function isNodeWhitespace() |  | 
|  352 { |  | 
|  353     if (!this || this.nodeType !== Node.TEXT_NODE) |  | 
|  354         return false; |  | 
|  355     if (!this.nodeValue.length) |  | 
|  356         return true; |  | 
|  357     return this.nodeValue.match(/^[\s\xA0]+$/); |  | 
|  358 } |  | 
|  359  |  | 
|  360 function nodeDisplayName() |  | 
|  361 { |  | 
|  362     if (!this) |  | 
|  363         return ""; |  | 
|  364  |  | 
|  365     switch (this.nodeType) { |  | 
|  366         case Node.DOCUMENT_NODE: |  | 
|  367             return "Document"; |  | 
|  368  |  | 
|  369         case Node.ELEMENT_NODE: |  | 
|  370             var name = "<" + this.nodeName.toLowerCase(); |  | 
|  371  |  | 
|  372             if (this.hasAttributes()) { |  | 
|  373                 var value = this.getAttribute("id"); |  | 
|  374                 if (value) |  | 
|  375                     name += " id=\"" + value + "\""; |  | 
|  376                 value = this.getAttribute("class"); |  | 
|  377                 if (value) |  | 
|  378                     name += " class=\"" + value + "\""; |  | 
|  379                 if (this.nodeName.toLowerCase() === "a") { |  | 
|  380                     value = this.getAttribute("name"); |  | 
|  381                     if (value) |  | 
|  382                         name += " name=\"" + value + "\""; |  | 
|  383                     value = this.getAttribute("href"); |  | 
|  384                     if (value) |  | 
|  385                         name += " href=\"" + value + "\""; |  | 
|  386                 } else if (this.nodeName.toLowerCase() === "img") { |  | 
|  387                     value = this.getAttribute("src"); |  | 
|  388                     if (value) |  | 
|  389                         name += " src=\"" + value + "\""; |  | 
|  390                 } else if (this.nodeName.toLowerCase() === "iframe") { |  | 
|  391                     value = this.getAttribute("src"); |  | 
|  392                     if (value) |  | 
|  393                         name += " src=\"" + value + "\""; |  | 
|  394                 } else if (this.nodeName.toLowerCase() === "input") { |  | 
|  395                     value = this.getAttribute("name"); |  | 
|  396                     if (value) |  | 
|  397                         name += " name=\"" + value + "\""; |  | 
|  398                     value = this.getAttribute("type"); |  | 
|  399                     if (value) |  | 
|  400                         name += " type=\"" + value + "\""; |  | 
|  401                 } else if (this.nodeName.toLowerCase() === "form") { |  | 
|  402                     value = this.getAttribute("action"); |  | 
|  403                     if (value) |  | 
|  404                         name += " action=\"" + value + "\""; |  | 
|  405                 } |  | 
|  406             } |  | 
|  407  |  | 
|  408             return name + ">"; |  | 
|  409  |  | 
|  410         case Node.TEXT_NODE: |  | 
|  411             if (isNodeWhitespace.call(this)) |  | 
|  412                 return "(whitespace)"; |  | 
|  413             return "\"" + this.nodeValue + "\""; |  | 
|  414  |  | 
|  415         case Node.COMMENT_NODE: |  | 
|  416             return "<!--" + this.nodeValue + "-->"; |  | 
|  417              |  | 
|  418         case Node.DOCUMENT_TYPE_NODE: |  | 
|  419             var docType = "<!DOCTYPE " + this.nodeName; |  | 
|  420             if (this.publicId) { |  | 
|  421                 docType += " PUBLIC \"" + this.publicId + "\""; |  | 
|  422                 if (this.systemId) |  | 
|  423                     docType += " \"" + this.systemId + "\""; |  | 
|  424             } else if (this.systemId) |  | 
|  425                 docType += " SYSTEM \"" + this.systemId + "\""; |  | 
|  426             if (this.internalSubset) |  | 
|  427                 docType += " [" + this.internalSubset + "]"; |  | 
|  428             return docType + ">"; |  | 
|  429     } |  | 
|  430  |  | 
|  431     return this.nodeName.toLowerCase().collapseWhitespace(); |  | 
|  432 } |  | 
|  433  |  | 
|  434 function isAncestorNode(ancestor, node) |  | 
|  435 { |  | 
|  436     if (!node || !ancestor) |  | 
|  437         return false; |  | 
|  438  |  | 
|  439     var currentNode = node.parentNode; |  | 
|  440     while (currentNode) { |  | 
|  441         if (ancestor === currentNode) |  | 
|  442             return true; |  | 
|  443         currentNode = currentNode.parentNode; |  | 
|  444     } |  | 
|  445     return false; |  | 
|  446 } |  | 
|  447  |  | 
|  448 function isDescendantNode(descendant) |  | 
|  449 { |  | 
|  450     return isAncestorNode(descendant, this); |  | 
|  451 } |  | 
|  452  |  | 
|  453 function traverseNextNode(stayWithin) |  | 
|  454 { |  | 
|  455     if (!this) |  | 
|  456         return; |  | 
|  457  |  | 
|  458     var node = this.firstChild; |  | 
|  459     if (node) |  | 
|  460         return node; |  | 
|  461  |  | 
|  462     if (stayWithin && this === stayWithin) |  | 
|  463         return null; |  | 
|  464  |  | 
|  465     node = this.nextSibling; |  | 
|  466     if (node) |  | 
|  467         return node; |  | 
|  468  |  | 
|  469     node = this; |  | 
|  470     while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node
     .parentNode !== stayWithin)) |  | 
|  471         node = node.parentNode; |  | 
|  472     if (!node) |  | 
|  473         return null; |  | 
|  474  |  | 
|  475     return node.nextSibling; |  | 
|  476 } |  | 
|  477  |  | 
|  478 function traversePreviousNode(stayWithin) |  | 
|  479 { |  | 
|  480     if (!this) |  | 
|  481         return; |  | 
|  482     if (stayWithin && this === stayWithin) |  | 
|  483         return null; |  | 
|  484     var node = this.previousSibling; |  | 
|  485     while (node && node.lastChild) |  | 
|  486         node = node.lastChild; |  | 
|  487     if (node) |  | 
|  488         return node; |  | 
|  489     return this.parentNode; |  | 
|  490 } |  | 
|  491  |  | 
|  492 function onlyTextChild() |  | 
|  493 { |  | 
|  494     if (!this) |  | 
|  495         return null; |  | 
|  496  |  | 
|  497     var firstChild = this.firstChild; |  | 
|  498     if (!firstChild || firstChild.nodeType !== Node.TEXT_NODE) |  | 
|  499         return null; |  | 
|  500  |  | 
|  501     var sibling = firstChild.nextSibling; |  | 
|  502     return sibling ? null : firstChild; |  | 
|  503 } |  | 
|  504  |  | 
|  505 function appropriateSelectorForNode(node, justSelector) |  | 
|  506 { |  | 
|  507     if (!node) |  | 
|  508         return ""; |  | 
|  509  |  | 
|  510     var lowerCaseName = node.localName || node.nodeName.toLowerCase(); |  | 
|  511  |  | 
|  512     var id = node.getAttribute("id"); |  | 
|  513     if (id) { |  | 
|  514         var selector = "#" + id; |  | 
|  515         return (justSelector ? selector : lowerCaseName + selector); |  | 
|  516     } |  | 
|  517  |  | 
|  518     var className = node.getAttribute("class"); |  | 
|  519     if (className) { |  | 
|  520         var selector = "." + className.replace(/\s+/, "."); |  | 
|  521         return (justSelector ? selector : lowerCaseName + selector); |  | 
|  522     } |  | 
|  523  |  | 
|  524     if (lowerCaseName === "input" && node.getAttribute("type")) |  | 
|  525         return lowerCaseName + "[type=\"" + node.getAttribute("type") + "\"]"; |  | 
|  526  |  | 
|  527     return lowerCaseName; |  | 
|  528 } |  | 
|  529  |  | 
|  530 function getDocumentForNode(node) |  | 
|  531 { |  | 
|  532     return node.nodeType == Node.DOCUMENT_NODE ? node : node.ownerDocument; |  | 
|  533 } |  | 
|  534  |  | 
|  535 function parentNode(node) |  | 
|  536 { |  | 
|  537     return node.parentNode; |  | 
|  538 } |  | 
|  539  |  | 
|  540 Number.secondsToString = function(seconds, formatterFunction, higherResolution) |  | 
|  541 { |  | 
|  542     if (!formatterFunction) |  | 
|  543         formatterFunction = String.sprintf; |  | 
|  544  |  | 
|  545     var ms = seconds * 1000; |  | 
|  546     if (higherResolution && ms < 1000) |  | 
|  547         return formatterFunction("%.3fms", ms); |  | 
|  548     else if (ms < 1000) |  | 
|  549         return formatterFunction("%.0fms", ms); |  | 
|  550  |  | 
|  551     if (seconds < 60) |  | 
|  552         return formatterFunction("%.2fs", seconds); |  | 
|  553  |  | 
|  554     var minutes = seconds / 60; |  | 
|  555     if (minutes < 60) |  | 
|  556         return formatterFunction("%.1fmin", minutes); |  | 
|  557  |  | 
|  558     var hours = minutes / 60; |  | 
|  559     if (hours < 24) |  | 
|  560         return formatterFunction("%.1fhrs", hours); |  | 
|  561  |  | 
|  562     var days = hours / 24; |  | 
|  563     return formatterFunction("%.1f days", days); |  | 
|  564 } |  | 
|  565  |  | 
|  566 Number.bytesToString = function(bytes, formatterFunction, higherResolution) |  | 
|  567 { |  | 
|  568     if (!formatterFunction) |  | 
|  569         formatterFunction = String.sprintf; |  | 
|  570     if (typeof higherResolution === "undefined") |  | 
|  571         higherResolution = true; |  | 
|  572  |  | 
|  573     if (bytes < 1024) |  | 
|  574         return formatterFunction("%.0fB", bytes); |  | 
|  575  |  | 
|  576     var kilobytes = bytes / 1024; |  | 
|  577     if (higherResolution && kilobytes < 1024) |  | 
|  578         return formatterFunction("%.2fKB", kilobytes); |  | 
|  579     else if (kilobytes < 1024) |  | 
|  580         return formatterFunction("%.0fKB", kilobytes); |  | 
|  581  |  | 
|  582     var megabytes = kilobytes / 1024; |  | 
|  583     if (higherResolution) |  | 
|  584         return formatterFunction("%.3fMB", megabytes); |  | 
|  585     else |  | 
|  586         return formatterFunction("%.0fMB", megabytes); |  | 
|  587 } |  | 
|  588  |  | 
|  589 Number.constrain = function(num, min, max) |  | 
|  590 { |  | 
|  591     if (num < min) |  | 
|  592         num = min; |  | 
|  593     else if (num > max) |  | 
|  594         num = max; |  | 
|  595     return num; |  | 
|  596 } |  | 
|  597  |  | 
|  598 HTMLTextAreaElement.prototype.moveCursorToEnd = function() |  | 
|  599 { |  | 
|  600     var length = this.value.length; |  | 
|  601     this.setSelectionRange(length, length); |  | 
|  602 } |  | 
|  603  |  | 
|  604 Array.prototype.remove = function(value, onlyFirst) |  | 
|  605 { |  | 
|  606     if (onlyFirst) { |  | 
|  607         var index = this.indexOf(value); |  | 
|  608         if (index !== -1) |  | 
|  609             this.splice(index, 1); |  | 
|  610         return; |  | 
|  611     } |  | 
|  612  |  | 
|  613     var length = this.length; |  | 
|  614     for (var i = 0; i < length; ++i) { |  | 
|  615         if (this[i] === value) |  | 
|  616             this.splice(i, 1); |  | 
|  617     } |  | 
|  618 } |  | 
|  619  |  | 
|  620 function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunctio
     n) |  | 
|  621 { |  | 
|  622     // indexOf returns (-lowerBound - 1). Taking (-result - 1) works out to lowe
     rBound. |  | 
|  623     return (-indexOfObjectInListSortedByFunction(anObject, aList, aFunction) - 1
     ); |  | 
|  624 } |  | 
|  625  |  | 
|  626 function indexOfObjectInListSortedByFunction(anObject, aList, aFunction) |  | 
|  627 { |  | 
|  628     var first = 0; |  | 
|  629     var last = aList.length - 1; |  | 
|  630     var floor = Math.floor; |  | 
|  631     var mid, c; |  | 
|  632  |  | 
|  633     while (first <= last) { |  | 
|  634         mid = floor((first + last) / 2); |  | 
|  635         c = aFunction(anObject, aList[mid]); |  | 
|  636  |  | 
|  637         if (c > 0) |  | 
|  638             first = mid + 1; |  | 
|  639         else if (c < 0) |  | 
|  640             last = mid - 1; |  | 
|  641         else { |  | 
|  642             // Return the first occurance of an item in the list. |  | 
|  643             while (mid > 0 && aFunction(anObject, aList[mid - 1]) === 0) |  | 
|  644                 mid--; |  | 
|  645             first = mid; |  | 
|  646             break; |  | 
|  647         } |  | 
|  648     } |  | 
|  649  |  | 
|  650     // By returning 1 less than the negative lower search bound, we can reuse th
     is function |  | 
|  651     // for both indexOf and insertionIndexFor, with some simple arithmetic. |  | 
|  652     return (-first - 1); |  | 
|  653 } |  | 
|  654  |  | 
|  655 String.sprintf = function(format) |  | 
|  656 { |  | 
|  657     return String.vsprintf(format, Array.prototype.slice.call(arguments, 1)); |  | 
|  658 } |  | 
|  659  |  | 
|  660 String.tokenizeFormatString = function(format) |  | 
|  661 { |  | 
|  662     var tokens = []; |  | 
|  663     var substitutionIndex = 0; |  | 
|  664  |  | 
|  665     function addStringToken(str) |  | 
|  666     { |  | 
|  667         tokens.push({ type: "string", value: str }); |  | 
|  668     } |  | 
|  669  |  | 
|  670     function addSpecifierToken(specifier, precision, substitutionIndex) |  | 
|  671     { |  | 
|  672         tokens.push({ type: "specifier", specifier: specifier, precision: precis
     ion, substitutionIndex: substitutionIndex }); |  | 
|  673     } |  | 
|  674  |  | 
|  675     var index = 0; |  | 
|  676     for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; pre
     centIndex = format.indexOf("%", index)) { |  | 
|  677         addStringToken(format.substring(index, precentIndex)); |  | 
|  678         index = precentIndex + 1; |  | 
|  679  |  | 
|  680         if (format[index] === "%") { |  | 
|  681             addStringToken("%"); |  | 
|  682             ++index; |  | 
|  683             continue; |  | 
|  684         } |  | 
|  685  |  | 
|  686         if (!isNaN(format[index])) { |  | 
|  687             // The first character is a number, it might be a substitution index
     . |  | 
|  688             var number = parseInt(format.substring(index)); |  | 
|  689             while (!isNaN(format[index])) |  | 
|  690                 ++index; |  | 
|  691             // If the number is greater than zero and ends with a "$", |  | 
|  692             // then this is a substitution index. |  | 
|  693             if (number > 0 && format[index] === "$") { |  | 
|  694                 substitutionIndex = (number - 1); |  | 
|  695                 ++index; |  | 
|  696             } |  | 
|  697         } |  | 
|  698  |  | 
|  699         var precision = -1; |  | 
|  700         if (format[index] === ".") { |  | 
|  701             // This is a precision specifier. If no digit follows the ".", |  | 
|  702             // then the precision should be zero. |  | 
|  703             ++index; |  | 
|  704             precision = parseInt(format.substring(index)); |  | 
|  705             if (isNaN(precision)) |  | 
|  706                 precision = 0; |  | 
|  707             while (!isNaN(format[index])) |  | 
|  708                 ++index; |  | 
|  709         } |  | 
|  710  |  | 
|  711         addSpecifierToken(format[index], precision, substitutionIndex); |  | 
|  712  |  | 
|  713         ++substitutionIndex; |  | 
|  714         ++index; |  | 
|  715     } |  | 
|  716  |  | 
|  717     addStringToken(format.substring(index)); |  | 
|  718  |  | 
|  719     return tokens; |  | 
|  720 } |  | 
|  721  |  | 
|  722 String.standardFormatters = { |  | 
|  723     d: function(substitution) |  | 
|  724     { |  | 
|  725         if (typeof substitution == "object" && Object.proxyType(substitution) ==
     = "number") |  | 
|  726             substitution = substitution.description; |  | 
|  727         substitution = parseInt(substitution); |  | 
|  728         return !isNaN(substitution) ? substitution : 0; |  | 
|  729     }, |  | 
|  730  |  | 
|  731     f: function(substitution, token) |  | 
|  732     { |  | 
|  733         if (typeof substitution == "object" && Object.proxyType(substitution) ==
     = "number") |  | 
|  734             substitution = substitution.description; |  | 
|  735         substitution = parseFloat(substitution); |  | 
|  736         if (substitution && token.precision > -1) |  | 
|  737             substitution = substitution.toFixed(token.precision); |  | 
|  738         return !isNaN(substitution) ? substitution : (token.precision > -1 ? Num
     ber(0).toFixed(token.precision) : 0); |  | 
|  739     }, |  | 
|  740  |  | 
|  741     s: function(substitution) |  | 
|  742     { |  | 
|  743         if (typeof substitution == "object" && Object.proxyType(substitution) !=
     = "null") |  | 
|  744             substitution = substitution.description; |  | 
|  745         return substitution; |  | 
|  746     }, |  | 
|  747 }; |  | 
|  748  |  | 
|  749 String.vsprintf = function(format, substitutions) |  | 
|  750 { |  | 
|  751     return String.format(format, substitutions, String.standardFormatters, "", f
     unction(a, b) { return a + b; }).formattedResult; |  | 
|  752 } |  | 
|  753  |  | 
|  754 String.format = function(format, substitutions, formatters, initialValue, append
     ) |  | 
|  755 { |  | 
|  756     if (!format || !substitutions || !substitutions.length) |  | 
|  757         return { formattedResult: append(initialValue, format), unusedSubstituti
     ons: substitutions }; |  | 
|  758  |  | 
|  759     function prettyFunctionName() |  | 
|  760     { |  | 
|  761         return "String.format(\"" + format + "\", \"" + substitutions.join("\", 
     \"") + "\")"; |  | 
|  762     } |  | 
|  763  |  | 
|  764     function warn(msg) |  | 
|  765     { |  | 
|  766         console.warn(prettyFunctionName() + ": " + msg); |  | 
|  767     } |  | 
|  768  |  | 
|  769     function error(msg) |  | 
|  770     { |  | 
|  771         console.error(prettyFunctionName() + ": " + msg); |  | 
|  772     } |  | 
|  773  |  | 
|  774     var result = initialValue; |  | 
|  775     var tokens = String.tokenizeFormatString(format); |  | 
|  776     var usedSubstitutionIndexes = {}; |  | 
|  777  |  | 
|  778     for (var i = 0; i < tokens.length; ++i) { |  | 
|  779         var token = tokens[i]; |  | 
|  780  |  | 
|  781         if (token.type === "string") { |  | 
|  782             result = append(result, token.value); |  | 
|  783             continue; |  | 
|  784         } |  | 
|  785  |  | 
|  786         if (token.type !== "specifier") { |  | 
|  787             error("Unknown token type \"" + token.type + "\" found."); |  | 
|  788             continue; |  | 
|  789         } |  | 
|  790  |  | 
|  791         if (token.substitutionIndex >= substitutions.length) { |  | 
|  792             // If there are not enough substitutions for the current substitutio
     nIndex |  | 
|  793             // just output the format specifier literally and move on. |  | 
|  794             error("not enough substitution arguments. Had " + substitutions.leng
     th + " but needed " + (token.substitutionIndex + 1) + ", so substitution was ski
     pped."); |  | 
|  795             result = append(result, "%" + (token.precision > -1 ? token.precisio
     n : "") + token.specifier); |  | 
|  796             continue; |  | 
|  797         } |  | 
|  798  |  | 
|  799         usedSubstitutionIndexes[token.substitutionIndex] = true; |  | 
|  800  |  | 
|  801         if (!(token.specifier in formatters)) { |  | 
|  802             // Encountered an unsupported format character, treat as a string. |  | 
|  803             warn("unsupported format character \u201C" + token.specifier + "\u20
     1D. Treating as a string."); |  | 
|  804             result = append(result, substitutions[token.substitutionIndex]); |  | 
|  805             continue; |  | 
|  806         } |  | 
|  807  |  | 
|  808         result = append(result, formatters[token.specifier](substitutions[token.
     substitutionIndex], token)); |  | 
|  809     } |  | 
|  810  |  | 
|  811     var unusedSubstitutions = []; |  | 
|  812     for (var i = 0; i < substitutions.length; ++i) { |  | 
|  813         if (i in usedSubstitutionIndexes) |  | 
|  814             continue; |  | 
|  815         unusedSubstitutions.push(substitutions[i]); |  | 
|  816     } |  | 
|  817  |  | 
|  818     return { formattedResult: result, unusedSubstitutions: unusedSubstitutions }
     ; |  | 
|  819 } |  | 
| OLD | NEW |