Upstream version 9.37.197.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / common / DOMExtension.js
1 /*
2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
3  * Copyright (C) 2012 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  * Contains diff method based on Javascript Diff Algorithm By John Resig
30  * http://ejohn.org/files/jsdiff.js (released under the MIT license).
31  */
32
33 /**
34  * @param {number} offset
35  * @param {string} stopCharacters
36  * @param {!Node} stayWithinNode
37  * @param {string=} direction
38  * @return {!Range}
39  */
40 Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, direction)
41 {
42     var startNode;
43     var startOffset = 0;
44     var endNode;
45     var endOffset = 0;
46
47     if (!stayWithinNode)
48         stayWithinNode = this;
49
50     if (!direction || direction === "backward" || direction === "both") {
51         var node = this;
52         while (node) {
53             if (node === stayWithinNode) {
54                 if (!startNode)
55                     startNode = stayWithinNode;
56                 break;
57             }
58
59             if (node.nodeType === Node.TEXT_NODE) {
60                 var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1));
61                 for (var i = start; i >= 0; --i) {
62                     if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
63                         startNode = node;
64                         startOffset = i + 1;
65                         break;
66                     }
67                 }
68             }
69
70             if (startNode)
71                 break;
72
73             node = node.traversePreviousNode(stayWithinNode);
74         }
75
76         if (!startNode) {
77             startNode = stayWithinNode;
78             startOffset = 0;
79         }
80     } else {
81         startNode = this;
82         startOffset = offset;
83     }
84
85     if (!direction || direction === "forward" || direction === "both") {
86         node = this;
87         while (node) {
88             if (node === stayWithinNode) {
89                 if (!endNode)
90                     endNode = stayWithinNode;
91                 break;
92             }
93
94             if (node.nodeType === Node.TEXT_NODE) {
95                 var start = (node === this ? offset : 0);
96                 for (var i = start; i < node.nodeValue.length; ++i) {
97                     if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
98                         endNode = node;
99                         endOffset = i;
100                         break;
101                     }
102                 }
103             }
104
105             if (endNode)
106                 break;
107
108             node = node.traverseNextNode(stayWithinNode);
109         }
110
111         if (!endNode) {
112             endNode = stayWithinNode;
113             endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length;
114         }
115     } else {
116         endNode = this;
117         endOffset = offset;
118     }
119
120     var result = this.ownerDocument.createRange();
121     result.setStart(startNode, startOffset);
122     result.setEnd(endNode, endOffset);
123
124     return result;
125 }
126
127 /**
128  * @param {!Node=} stayWithin
129  * @return {?Node}
130  */
131 Node.prototype.traverseNextTextNode = function(stayWithin)
132 {
133     var node = this.traverseNextNode(stayWithin);
134     if (!node)
135         return null;
136
137     while (node && node.nodeType !== Node.TEXT_NODE)
138         node = node.traverseNextNode(stayWithin);
139
140     return node;
141 }
142
143 /**
144  * @param {number} offset
145  * @return {!{container: !Node, offset: number}}
146  */
147 Node.prototype.rangeBoundaryForOffset = function(offset)
148 {
149     var node = this.traverseNextTextNode(this);
150     while (node && offset > node.nodeValue.length) {
151         offset -= node.nodeValue.length;
152         node = node.traverseNextTextNode(this);
153     }
154     if (!node)
155         return { container: this, offset: 0 };
156     return { container: node, offset: offset };
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 /**
167  * @param {number|undefined} x
168  * @param {number|undefined} y
169  * @param {!Element=} relativeTo
170  */
171 Element.prototype.positionAt = function(x, y, relativeTo)
172 {
173     var shift = {x: 0, y: 0};
174     if (relativeTo)
175        shift = relativeTo.boxInWindow(this.ownerDocument.defaultView);
176
177     if (typeof x === "number")
178         this.style.setProperty("left", (shift.x + x) + "px");
179     else
180         this.style.removeProperty("left");
181
182     if (typeof y === "number")
183         this.style.setProperty("top", (shift.y + y) + "px");
184     else
185         this.style.removeProperty("top");
186 }
187
188 /**
189  * @return {boolean}
190  */
191 Element.prototype.isScrolledToBottom = function()
192 {
193     // This code works only for 0-width border.
194     // Both clientHeight and scrollHeight are rounded to integer values, so we tolerate
195     // one pixel error.
196     return Math.abs(this.scrollTop + this.clientHeight - this.scrollHeight) <= 1;
197 }
198
199 /**
200  * @param {!Node} fromNode
201  * @param {!Node} toNode
202  */
203 function removeSubsequentNodes(fromNode, toNode)
204 {
205     for (var node = fromNode; node && node !== toNode; ) {
206         var nodeToRemove = node;
207         node = node.nextSibling;
208         nodeToRemove.remove();
209     }
210 }
211
212 /**
213  * @constructor
214  * @param {!Size} minimum
215  * @param {?Size=} preferred
216  */
217 function Constraints(minimum, preferred)
218 {
219     /**
220      * @type {!Size}
221      */
222     this.minimum = minimum;
223
224     /**
225      * @type {!Size}
226      */
227     this.preferred = preferred || minimum;
228
229     if (this.minimum.width > this.preferred.width || this.minimum.height > this.preferred.height)
230         throw new Error("Minimum size is greater than preferred.");
231 }
232
233 /**
234  * @param {?Constraints} constraints
235  * @return {boolean}
236  */
237 Constraints.prototype.isEqual = function(constraints)
238 {
239     return !!constraints && this.minimum.isEqual(constraints.minimum) && this.preferred.isEqual(constraints.preferred);
240 }
241
242 /**
243  * @param {!Constraints|number} value
244  * @return {!Constraints}
245  */
246 Constraints.prototype.widthToMax = function(value)
247 {
248     if (typeof value === "number")
249         return new Constraints(this.minimum.widthToMax(value), this.preferred.widthToMax(value));
250     return new Constraints(this.minimum.widthToMax(value.minimum), this.preferred.widthToMax(value.preferred));
251 }
252
253 /**
254  * @param {!Constraints|number} value
255  * @return {!Constraints}
256  */
257 Constraints.prototype.addWidth = function(value)
258 {
259     if (typeof value === "number")
260         return new Constraints(this.minimum.addWidth(value), this.preferred.addWidth(value));
261     return new Constraints(this.minimum.addWidth(value.minimum), this.preferred.addWidth(value.preferred));
262 }
263
264 /**
265  * @param {!Constraints|number} value
266  * @return {!Constraints}
267  */
268 Constraints.prototype.heightToMax = function(value)
269 {
270     if (typeof value === "number")
271         return new Constraints(this.minimum.heightToMax(value), this.preferred.heightToMax(value));
272     return new Constraints(this.minimum.heightToMax(value.minimum), this.preferred.heightToMax(value.preferred));
273 }
274
275 /**
276  * @param {!Constraints|number} value
277  * @return {!Constraints}
278  */
279 Constraints.prototype.addHeight = function(value)
280 {
281     if (typeof value === "number")
282         return new Constraints(this.minimum.addHeight(value), this.preferred.addHeight(value));
283     return new Constraints(this.minimum.addHeight(value.minimum), this.preferred.addHeight(value.preferred));
284 }
285
286 /**
287  * @param {?Element=} containerElement
288  * @return {!Size}
289  */
290 Element.prototype.measurePreferredSize = function(containerElement)
291 {
292     containerElement = containerElement || document.body;
293     containerElement.appendChild(this);
294     this.positionAt(0, 0);
295     var result = new Size(this.offsetWidth, this.offsetHeight);
296     this.positionAt(undefined, undefined);
297     this.remove();
298     return result;
299 }
300
301 /**
302  * @param {!Event} event
303  * @return {boolean}
304  */
305 Element.prototype.containsEventPoint = function(event)
306 {
307     var box = this.getBoundingClientRect();
308     return box.left < event.x  && event.x < box.right &&
309            box.top < event.y && event.y < box.bottom;
310 }
311
312 /**
313  * @param {!Array.<string>} nameArray
314  * @return {?Node}
315  */
316 Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray)
317 {
318     for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) {
319         for (var i = 0; i < nameArray.length; ++i) {
320             if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase())
321                 return node;
322         }
323     }
324     return null;
325 }
326
327 /**
328  * @param {string} nodeName
329  * @return {?Node}
330  */
331 Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName)
332 {
333     return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]);
334 }
335
336 /**
337  * @param {string} className
338  * @param {!Element=} stayWithin
339  * @return {?Element}
340  */
341 Node.prototype.enclosingNodeOrSelfWithClass = function(className, stayWithin)
342 {
343     for (var node = this; node && node !== stayWithin && node !== this.ownerDocument; node = node.parentNode) {
344         if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains(className))
345             return /** @type {!Element} */ (node);
346     }
347     return null;
348 }
349
350 /**
351  * @param {string} query
352  * @return {?Node}
353  */
354 Element.prototype.query = function(query)
355 {
356     return this.ownerDocument.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
357 }
358
359 Element.prototype.removeChildren = function()
360 {
361     if (this.firstChild)
362         this.textContent = "";
363 }
364
365 Element.prototype.appendChildren = function(children)
366 {
367     for (var i = 0; i < children.length; ++i)
368         this.appendChild(children[i]);
369 }
370
371 Element.prototype.setChildren = function(children)
372 {
373     this.removeChildren();
374     this.appendChildren(children);
375 }
376
377 /**
378  * @return {boolean}
379  */
380 Element.prototype.isInsertionCaretInside = function()
381 {
382     var selection = window.getSelection();
383     if (!selection.rangeCount || !selection.isCollapsed)
384         return false;
385     var selectionRange = selection.getRangeAt(0);
386     return selectionRange.startContainer.isSelfOrDescendant(this);
387 }
388
389 /**
390  * @param {string} elementName
391  * @param {string=} className
392  * @return {!Element}
393  */
394 Document.prototype.createElementWithClass = function(elementName, className)
395 {
396     var element = this.createElement(elementName);
397     if (className)
398         element.className = className;
399     return element;
400 }
401
402 /**
403  * @param {string} elementName
404  * @param {string=} className
405  * @return {!Element}
406  */
407 Element.prototype.createChild = function(elementName, className)
408 {
409     var element = this.ownerDocument.createElementWithClass(elementName, className);
410     this.appendChild(element);
411     return element;
412 }
413
414 DocumentFragment.prototype.createChild = Element.prototype.createChild;
415
416 /**
417  * @param {string} text
418  * @return {!Text}
419  */
420 Element.prototype.createTextChild = function(text)
421 {
422     var element = this.ownerDocument.createTextNode(text);
423     this.appendChild(element);
424     return element;
425 }
426
427 DocumentFragment.prototype.createTextChild = Element.prototype.createTextChild;
428
429 /**
430  * @return {number}
431  */
432 Element.prototype.totalOffsetLeft = function()
433 {
434     return this.totalOffset().left;
435 }
436
437 /**
438  * @return {number}
439  */
440 Element.prototype.totalOffsetTop = function()
441 {
442     return this.totalOffset().top;
443
444 }
445
446 /**
447  * @return {!{left: number, top: number}}
448  */
449 Element.prototype.totalOffset = function()
450 {
451     var rect = this.getBoundingClientRect();
452     return { left: rect.left, top: rect.top };
453 }
454
455 /**
456  * @return {!{left: number, top: number}}
457  */
458 Element.prototype.scrollOffset = function()
459 {
460     var curLeft = 0;
461     var curTop = 0;
462     for (var element = this; element; element = element.scrollParent) {
463         curLeft += element.scrollLeft;
464         curTop += element.scrollTop;
465     }
466     return { left: curLeft, top: curTop };
467 }
468
469 /**
470  * @constructor
471  * @param {number=} x
472  * @param {number=} y
473  * @param {number=} width
474  * @param {number=} height
475  */
476 function AnchorBox(x, y, width, height)
477 {
478     this.x = x || 0;
479     this.y = y || 0;
480     this.width = width || 0;
481     this.height = height || 0;
482 }
483
484 /**
485  * @param {!AnchorBox} box
486  * @return {!AnchorBox}
487  */
488 AnchorBox.prototype.relativeTo = function(box)
489 {
490     return new AnchorBox(
491         this.x - box.x, this.y - box.y, this.width, this.height);
492 }
493
494 /**
495  * @param {!Element} element
496  * @return {!AnchorBox}
497  */
498 AnchorBox.prototype.relativeToElement = function(element)
499 {
500     return this.relativeTo(element.boxInWindow(element.ownerDocument.defaultView));
501 }
502
503 /**
504  * @param {?AnchorBox} anchorBox
505  * @return {boolean}
506  */
507 AnchorBox.prototype.equals = function(anchorBox)
508 {
509     return !!anchorBox && this.x === anchorBox.x && this.y === anchorBox.y && this.width === anchorBox.width && this.height === anchorBox.height;
510 }
511
512 /**
513  * @param {!Window} targetWindow
514  * @return {!AnchorBox}
515  */
516 Element.prototype.offsetRelativeToWindow = function(targetWindow)
517 {
518     var elementOffset = new AnchorBox();
519     var curElement = this;
520     var curWindow = this.ownerDocument.defaultView;
521     while (curWindow && curElement) {
522         elementOffset.x += curElement.totalOffsetLeft();
523         elementOffset.y += curElement.totalOffsetTop();
524         if (curWindow === targetWindow)
525             break;
526
527         curElement = curWindow.frameElement;
528         curWindow = curWindow.parent;
529     }
530
531     return elementOffset;
532 }
533
534 /**
535  * @param {!Window=} targetWindow
536  * @return {!AnchorBox}
537  */
538 Element.prototype.boxInWindow = function(targetWindow)
539 {
540     targetWindow = targetWindow || this.ownerDocument.defaultView;
541
542     var anchorBox = this.offsetRelativeToWindow(window);
543     anchorBox.width = Math.min(this.offsetWidth, window.innerWidth - anchorBox.x);
544     anchorBox.height = Math.min(this.offsetHeight, window.innerHeight - anchorBox.y);
545
546     return anchorBox;
547 }
548
549 /**
550  * @param {string} text
551  */
552 Element.prototype.setTextAndTitle = function(text)
553 {
554     this.textContent = text;
555     this.title = text;
556 }
557
558 KeyboardEvent.prototype.__defineGetter__("data", function()
559 {
560     // Emulate "data" attribute from DOM 3 TextInput event.
561     // See http://www.w3.org/TR/DOM-Level-3-Events/#events-Events-TextEvent-data
562     switch (this.type) {
563         case "keypress":
564             if (!this.ctrlKey && !this.metaKey)
565                 return String.fromCharCode(this.charCode);
566             else
567                 return "";
568         case "keydown":
569         case "keyup":
570             if (!this.ctrlKey && !this.metaKey && !this.altKey)
571                 return String.fromCharCode(this.which);
572             else
573                 return "";
574     }
575 });
576
577 /**
578  * @param {boolean=} preventDefault
579  */
580 Event.prototype.consume = function(preventDefault)
581 {
582     this.stopImmediatePropagation();
583     if (preventDefault)
584         this.preventDefault();
585     this.handled = true;
586 }
587
588 /**
589  * @param {number=} start
590  * @param {number=} end
591  * @return {!Text}
592  */
593 Text.prototype.select = function(start, end)
594 {
595     start = start || 0;
596     end = end || this.textContent.length;
597
598     if (start < 0)
599         start = end + start;
600
601     var selection = this.ownerDocument.defaultView.getSelection();
602     selection.removeAllRanges();
603     var range = this.ownerDocument.createRange();
604     range.setStart(this, start);
605     range.setEnd(this, end);
606     selection.addRange(range);
607     return this;
608 }
609
610 /**
611  * @return {?number}
612  */
613 Element.prototype.selectionLeftOffset = function()
614 {
615     // Calculate selection offset relative to the current element.
616
617     var selection = window.getSelection();
618     if (!selection.containsNode(this, true))
619         return null;
620
621     var leftOffset = selection.anchorOffset;
622     var node = selection.anchorNode;
623
624     while (node !== this) {
625         while (node.previousSibling) {
626             node = node.previousSibling;
627             leftOffset += node.textContent.length;
628         }
629         node = node.parentNode;
630     }
631
632     return leftOffset;
633 }
634
635 /**
636  * @param {?Node} node
637  * @return {boolean}
638  */
639 Node.prototype.isAncestor = function(node)
640 {
641     if (!node)
642         return false;
643
644     var currentNode = node.parentNode;
645     while (currentNode) {
646         if (this === currentNode)
647             return true;
648         currentNode = currentNode.parentNode;
649     }
650     return false;
651 }
652
653 /**
654  * @param {?Node} descendant
655  * @return {boolean}
656  */
657 Node.prototype.isDescendant = function(descendant)
658 {
659     return !!descendant && descendant.isAncestor(this);
660 }
661
662 /**
663  * @param {?Node} node
664  * @return {boolean}
665  */
666 Node.prototype.isSelfOrAncestor = function(node)
667 {
668     return !!node && (node === this || this.isAncestor(node));
669 }
670
671 /**
672  * @param {?Node} node
673  * @return {boolean}
674  */
675 Node.prototype.isSelfOrDescendant = function(node)
676 {
677     return !!node && (node === this || this.isDescendant(node));
678 }
679
680 /**
681  * @param {!Node=} stayWithin
682  * @return {?Node}
683  */
684 Node.prototype.traverseNextNode = function(stayWithin)
685 {
686     var node = this.firstChild;
687     if (node)
688         return node;
689
690     if (stayWithin && this === stayWithin)
691         return null;
692
693     node = this.nextSibling;
694     if (node)
695         return node;
696
697     node = this;
698     while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin))
699         node = node.parentNode;
700     if (!node)
701         return null;
702
703     return node.nextSibling;
704 }
705
706 /**
707  * @param {!Node=} stayWithin
708  * @return {?Node}
709  */
710 Node.prototype.traversePreviousNode = function(stayWithin)
711 {
712     if (stayWithin && this === stayWithin)
713         return null;
714     var node = this.previousSibling;
715     while (node && node.lastChild)
716         node = node.lastChild;
717     if (node)
718         return node;
719     return this.parentNode;
720 }
721
722 /**
723  * @param {*} text
724  * @param {string=} placeholder
725  * @return {boolean} true if was truncated
726  */
727 Node.prototype.setTextContentTruncatedIfNeeded = function(text, placeholder)
728 {
729     // Huge texts in the UI reduce rendering performance drastically.
730     // Moreover, Blink/WebKit uses <unsigned short> internally for storing text content
731     // length, so texts longer than 65535 are inherently displayed incorrectly.
732     const maxTextContentLength = 65535;
733
734     if (typeof text === "string" && text.length > maxTextContentLength) {
735         this.textContent = typeof placeholder === "string" ? placeholder : text.trimEnd(maxTextContentLength);
736         return true;
737     }
738
739     this.textContent = text;
740     return false;
741 }
742
743 /**
744  * @return {boolean}
745  */
746 function isEnterKey(event) {
747     // Check if in IME.
748     return event.keyCode !== 229 && event.keyIdentifier === "Enter";
749 }
750
751 function consumeEvent(e)
752 {
753     e.consume();
754 }