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