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 |