Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / ui / 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 {string=} direction
35  */
36 Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, direction)
37 {
38     var startNode;
39     var startOffset = 0;
40     var endNode;
41     var endOffset = 0;
42
43     if (!stayWithinNode)
44         stayWithinNode = this;
45
46     if (!direction || direction === "backward" || direction === "both") {
47         var node = this;
48         while (node) {
49             if (node === stayWithinNode) {
50                 if (!startNode)
51                     startNode = stayWithinNode;
52                 break;
53             }
54
55             if (node.nodeType === Node.TEXT_NODE) {
56                 var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1));
57                 for (var i = start; i >= 0; --i) {
58                     if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
59                         startNode = node;
60                         startOffset = i + 1;
61                         break;
62                     }
63                 }
64             }
65
66             if (startNode)
67                 break;
68
69             node = node.traversePreviousNode(stayWithinNode);
70         }
71
72         if (!startNode) {
73             startNode = stayWithinNode;
74             startOffset = 0;
75         }
76     } else {
77         startNode = this;
78         startOffset = offset;
79     }
80
81     if (!direction || direction === "forward" || direction === "both") {
82         node = this;
83         while (node) {
84             if (node === stayWithinNode) {
85                 if (!endNode)
86                     endNode = stayWithinNode;
87                 break;
88             }
89
90             if (node.nodeType === Node.TEXT_NODE) {
91                 var start = (node === this ? offset : 0);
92                 for (var i = start; i < node.nodeValue.length; ++i) {
93                     if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
94                         endNode = node;
95                         endOffset = i;
96                         break;
97                     }
98                 }
99             }
100
101             if (endNode)
102                 break;
103
104             node = node.traverseNextNode(stayWithinNode);
105         }
106
107         if (!endNode) {
108             endNode = stayWithinNode;
109             endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length;
110         }
111     } else {
112         endNode = this;
113         endOffset = offset;
114     }
115
116     var result = this.ownerDocument.createRange();
117     result.setStart(startNode, startOffset);
118     result.setEnd(endNode, endOffset);
119
120     return result;
121 }
122
123 Node.prototype.traverseNextTextNode = function(stayWithin)
124 {
125     var node = this.traverseNextNode(stayWithin);
126     if (!node)
127         return;
128
129     while (node && node.nodeType !== Node.TEXT_NODE)
130         node = node.traverseNextNode(stayWithin);
131
132     return node;
133 }
134
135 Node.prototype.rangeBoundaryForOffset = function(offset)
136 {
137     var node = this.traverseNextTextNode(this);
138     while (node && offset > node.nodeValue.length) {
139         offset -= node.nodeValue.length;
140         node = node.traverseNextTextNode(this);
141     }
142     if (!node)
143         return { container: this, offset: 0 };
144     return { container: node, offset: offset };
145 }
146
147 Element.prototype.removeMatchingStyleClasses = function(classNameRegex)
148 {
149     var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)");
150     if (regex.test(this.className))
151         this.className = this.className.replace(regex, " ");
152 }
153
154 /**
155  * @param {number|undefined} x
156  * @param {number|undefined} y
157  * @param {!Element=} relativeTo
158  */
159 Element.prototype.positionAt = function(x, y, relativeTo)
160 {
161     var shift = {x: 0, y: 0};
162     if (relativeTo)
163        shift = relativeTo.boxInWindow(this.ownerDocument.defaultView);
164
165     if (typeof x === "number")
166         this.style.setProperty("left", (shift.x + x) + "px");
167     else
168         this.style.removeProperty("left");
169
170     if (typeof y === "number")
171         this.style.setProperty("top", (shift.y + y) + "px");
172     else
173         this.style.removeProperty("top");
174 }
175
176 Element.prototype.isScrolledToBottom = function()
177 {
178     // This code works only for 0-width border.
179     // Both clientHeight and scrollHeight are rounded to integer values, so we tolerate
180     // one pixel error.
181     return Math.abs(this.scrollTop + this.clientHeight - this.scrollHeight) <= 1;
182 }
183
184 /**
185  * @param {!Node} fromNode
186  * @param {!Node} toNode
187  */
188 function removeSubsequentNodes(fromNode, toNode)
189 {
190     for (var node = fromNode; node && node !== toNode; ) {
191         var nodeToRemove = node;
192         node = node.nextSibling;
193         nodeToRemove.remove();
194     }
195 }
196
197 /**
198  * @constructor
199  * @param {number} width
200  * @param {number} height
201  */
202 function Size(width, height)
203 {
204     this.width = width;
205     this.height = height;
206 }
207
208 /**
209  * @param {?Size} size
210  * @return {boolean}
211  */
212 Size.prototype.isEqual = function(size)
213 {
214     return !!size && this.width === size.width && this.height === size.height;
215 };
216
217 /**
218  * @param {!Size|number} size
219  * @return {!Size}
220  */
221 Size.prototype.widthToMax = function(size)
222 {
223     return new Size(Math.max(this.width, (typeof size === "number" ? size : size.width)), this.height);
224 };
225
226 /**
227  * @param {!Size|number} size
228  * @return {!Size}
229  */
230 Size.prototype.addWidth = function(size)
231 {
232     return new Size(this.width + (typeof size === "number" ? size : size.width), this.height);
233 };
234
235 /**
236  * @param {!Size|number} size
237  * @return {!Size}
238  */
239 Size.prototype.heightToMax = function(size)
240 {
241     return new Size(this.width, Math.max(this.height, (typeof size === "number" ? size : size.height)));
242 };
243
244 /**
245  * @param {!Size|number} size
246  * @return {!Size}
247  */
248 Size.prototype.addHeight = function(size)
249 {
250     return new Size(this.width, this.height + (typeof size === "number" ? size : size.height));
251 };
252
253 /**
254  * @constructor
255  * @param {!Size} minimum
256  * @param {?Size=} preferred
257  */
258 function Constraints(minimum, preferred)
259 {
260     /**
261      * @type {!Size}
262      */
263     this.minimum = minimum;
264
265     /**
266      * @type {!Size}
267      */
268     this.preferred = preferred || minimum;
269
270     if (this.minimum.width > this.preferred.width || this.minimum.height > this.preferred.height)
271         throw new Error("Minimum size is greater than preferred.");
272 }
273
274 /**
275  * @param {?Constraints} constraints
276  * @return {boolean}
277  */
278 Constraints.prototype.isEqual = function(constraints)
279 {
280     return !!constraints && this.minimum.isEqual(constraints.minimum) && this.preferred.isEqual(constraints.preferred);
281 };
282
283 /**
284  * @param {!Constraints|number} value
285  * @return {!Constraints}
286  */
287 Constraints.prototype.widthToMax = function(value)
288 {
289     if (typeof value === "number")
290         return new Constraints(this.minimum.widthToMax(value), this.preferred.widthToMax(value));
291     return new Constraints(this.minimum.widthToMax(value.minimum), this.preferred.widthToMax(value.preferred));
292 };
293
294 /**
295  * @param {!Constraints|number} value
296  * @return {!Constraints}
297  */
298 Constraints.prototype.addWidth = function(value)
299 {
300     if (typeof value === "number")
301         return new Constraints(this.minimum.addWidth(value), this.preferred.addWidth(value));
302     return new Constraints(this.minimum.addWidth(value.minimum), this.preferred.addWidth(value.preferred));
303 };
304
305 /**
306  * @param {!Constraints|number} value
307  * @return {!Constraints}
308  */
309 Constraints.prototype.heightToMax = function(value)
310 {
311     if (typeof value === "number")
312         return new Constraints(this.minimum.heightToMax(value), this.preferred.heightToMax(value));
313     return new Constraints(this.minimum.heightToMax(value.minimum), this.preferred.heightToMax(value.preferred));
314 };
315
316 /**
317  * @param {!Constraints|number} value
318  * @return {!Constraints}
319  */
320 Constraints.prototype.addHeight = function(value)
321 {
322     if (typeof value === "number")
323         return new Constraints(this.minimum.addHeight(value), this.preferred.addHeight(value));
324     return new Constraints(this.minimum.addHeight(value.minimum), this.preferred.addHeight(value.preferred));
325 };
326
327 /**
328  * @param {?Element=} containerElement
329  * @return {!Size}
330  */
331 Element.prototype.measurePreferredSize = function(containerElement)
332 {
333     containerElement = containerElement || document.body;
334     containerElement.appendChild(this);
335     this.positionAt(0, 0);
336     var result = new Size(this.offsetWidth, this.offsetHeight);
337     this.positionAt(undefined, undefined);
338     this.remove();
339     return result;
340 }
341
342 /**
343  * @param {!Event} event
344  * @return {boolean}
345  */
346 Element.prototype.containsEventPoint = function(event)
347 {
348     var box = this.getBoundingClientRect();
349     return box.left < event.x  && event.x < box.right &&
350            box.top < event.y && event.y < box.bottom;
351 }
352
353 Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray)
354 {
355     for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
356         for (var i = 0; i < nameArray.length; ++i)
357             if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase())
358                 return node;
359     return null;
360 }
361
362 Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName)
363 {
364     return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]);
365 }
366
367 /**
368  * @param {string} className
369  * @param {!Element=} stayWithin
370  */
371 Node.prototype.enclosingNodeOrSelfWithClass = function(className, stayWithin)
372 {
373     for (var node = this; node && node !== stayWithin && node !== this.ownerDocument; node = node.parentNode)
374         if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains(className))
375             return node;
376     return null;
377 }
378
379 Element.prototype.query = function(query)
380 {
381     return this.ownerDocument.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
382 }
383
384 Element.prototype.removeChildren = function()
385 {
386     if (this.firstChild)
387         this.textContent = "";
388 }
389
390 Element.prototype.isInsertionCaretInside = function()
391 {
392     var selection = window.getSelection();
393     if (!selection.rangeCount || !selection.isCollapsed)
394         return false;
395     var selectionRange = selection.getRangeAt(0);
396     return selectionRange.startContainer.isSelfOrDescendant(this);
397 }
398
399 /**
400  * @param {string} elementName
401  * @param {string=} className
402  */
403 Document.prototype.createElementWithClass = function(elementName, className)
404 {
405     var element = this.createElement(elementName);
406     if (className)
407         element.className = className;
408     return element;
409 }
410
411 /**
412  * @param {string=} className
413  */
414 Element.prototype.createChild = function(elementName, className)
415 {
416     var element = this.ownerDocument.createElementWithClass(elementName, className);
417     this.appendChild(element);
418     return element;
419 }
420
421 DocumentFragment.prototype.createChild = Element.prototype.createChild;
422
423 /**
424  * @param {string} text
425  */
426 Element.prototype.createTextChild = function(text)
427 {
428     var element = this.ownerDocument.createTextNode(text);
429     this.appendChild(element);
430     return element;
431 }
432
433 DocumentFragment.prototype.createTextChild = Element.prototype.createTextChild;
434
435 /**
436  * @return {number}
437  */
438 Element.prototype.totalOffsetLeft = function()
439 {
440     return this.totalOffset().left;
441 }
442
443 /**
444  * @return {number}
445  */
446 Element.prototype.totalOffsetTop = function()
447 {
448     return this.totalOffset().top;
449
450 }
451
452 Element.prototype.totalOffset = function()
453 {
454     var rect = this.getBoundingClientRect();
455     return { left: rect.left, top: rect.top };
456 }
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 Text.prototype.select = function(start, end)
589 {
590     start = start || 0;
591     end = end || this.textContent.length;
592
593     if (start < 0)
594         start = end + start;
595
596     var selection = this.ownerDocument.defaultView.getSelection();
597     selection.removeAllRanges();
598     var range = this.ownerDocument.createRange();
599     range.setStart(this, start);
600     range.setEnd(this, end);
601     selection.addRange(range);
602     return this;
603 }
604
605 Element.prototype.selectionLeftOffset = function()
606 {
607     // Calculate selection offset relative to the current element.
608
609     var selection = window.getSelection();
610     if (!selection.containsNode(this, true))
611         return null;
612
613     var leftOffset = selection.anchorOffset;
614     var node = selection.anchorNode;
615
616     while (node !== this) {
617         while (node.previousSibling) {
618             node = node.previousSibling;
619             leftOffset += node.textContent.length;
620         }
621         node = node.parentNode;
622     }
623
624     return leftOffset;
625 }
626
627 Node.prototype.isAncestor = function(node)
628 {
629     if (!node)
630         return false;
631
632     var currentNode = node.parentNode;
633     while (currentNode) {
634         if (this === currentNode)
635             return true;
636         currentNode = currentNode.parentNode;
637     }
638     return false;
639 }
640
641 Node.prototype.isDescendant = function(descendant)
642 {
643     return !!descendant && descendant.isAncestor(this);
644 }
645
646 Node.prototype.isSelfOrAncestor = function(node)
647 {
648     return !!node && (node === this || this.isAncestor(node));
649 }
650
651 Node.prototype.isSelfOrDescendant = function(node)
652 {
653     return !!node && (node === this || this.isDescendant(node));
654 }
655
656 Node.prototype.traverseNextNode = function(stayWithin)
657 {
658     var node = this.firstChild;
659     if (node)
660         return node;
661
662     if (stayWithin && this === stayWithin)
663         return null;
664
665     node = this.nextSibling;
666     if (node)
667         return node;
668
669     node = this;
670     while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin))
671         node = node.parentNode;
672     if (!node)
673         return null;
674
675     return node.nextSibling;
676 }
677
678 Node.prototype.traversePreviousNode = function(stayWithin)
679 {
680     if (stayWithin && this === stayWithin)
681         return null;
682     var node = this.previousSibling;
683     while (node && node.lastChild)
684         node = node.lastChild;
685     if (node)
686         return node;
687     return this.parentNode;
688 }
689
690 /**
691  * @param {*} text
692  * @param {string=} placeholder
693  * @return {boolean} true if was truncated
694  */
695 Node.prototype.setTextContentTruncatedIfNeeded = function(text, placeholder)
696 {
697     // Huge texts in the UI reduce rendering performance drastically.
698     // Moreover, Blink/WebKit uses <unsigned short> internally for storing text content
699     // length, so texts longer than 65535 are inherently displayed incorrectly.
700     const maxTextContentLength = 65535;
701
702     if (typeof text === "string" && text.length > maxTextContentLength) {
703         this.textContent = typeof placeholder === "string" ? placeholder : text.trimEnd(maxTextContentLength);
704         return true;
705     }
706
707     this.textContent = text;
708     return false;
709 }
710
711 /**
712  * @return {boolean}
713  */
714 function isEnterKey(event) {
715     // Check if in IME.
716     return event.keyCode !== 229 && event.keyIdentifier === "Enter";
717 }
718
719 function consumeEvent(e)
720 {
721     e.consume();
722 }