Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / ui / View.js
1 /*
2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3  * Copyright (C) 2011 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  * 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  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 /**
28  * @constructor
29  * @extends {WebInspector.Object}
30  * @param {boolean=} isWebComponent
31  */
32 WebInspector.View = function(isWebComponent)
33 {
34     this.contentElement = createElementWithClass("div", "view");
35     if (isWebComponent) {
36         WebInspector.installComponentRootStyles(this.contentElement);
37         this.element = createElementWithClass("div", "vbox flex-auto");
38         this._shadowRoot = this.element.createShadowRoot();
39         this._shadowRoot.appendChild(this.contentElement);
40     } else {
41         this.element = this.contentElement;
42     }
43     this.element.__view = this;
44     this._visible = true;
45     this._isRoot = false;
46     this._isShowing = false;
47     this._children = [];
48     this._hideOnDetach = false;
49     this._cssFiles = [];
50     this._notificationDepth = 0;
51 }
52
53 WebInspector.View._buildSourceURL = function(cssFile)
54 {
55     return "\n/*# sourceURL=" + WebInspector.ParsedURL.completeURL(window.location.href, cssFile) + " */";
56 }
57
58 /**
59  * @param {string} cssFile
60  * @return {!Element}
61  */
62 WebInspector.View.createStyleElement = function(cssFile)
63 {
64     var content = Runtime.cachedResources[cssFile];
65     if (!content) {
66         if (Runtime.isReleaseMode())
67             console.error(cssFile + " not preloaded, loading from the remote. Check module.json");
68         content = loadResource(cssFile) + WebInspector.View._buildSourceURL(cssFile);
69         Runtime.cachedResources[cssFile] = content;
70     }
71     var styleElement = createElement("style");
72     styleElement.type = "text/css";
73     styleElement.textContent = content;
74     return styleElement;
75 }
76
77 WebInspector.View.prototype = {
78     markAsRoot: function()
79     {
80         WebInspector.installComponentRootStyles(this.element);
81         WebInspector.View.__assert(!this.element.parentElement, "Attempt to mark as root attached node");
82         this._isRoot = true;
83     },
84
85     /**
86      * @return {?WebInspector.View}
87      */
88     parentView: function()
89     {
90         return this._parentView;
91     },
92
93     /**
94      * @return {!Array.<!WebInspector.View>}
95      */
96     children: function()
97     {
98         return this._children;
99     },
100
101     /**
102      * @return {boolean}
103      */
104     isShowing: function()
105     {
106         return this._isShowing;
107     },
108
109     /**
110      * @return {boolean}
111      */
112     _shouldHideOnDetach: function()
113     {
114         if (this._hideOnDetach)
115             return true;
116         for (var child of this._children) {
117             if (child._shouldHideOnDetach())
118                 return true;
119         }
120         return false;
121     },
122
123     setHideOnDetach: function()
124     {
125         this._hideOnDetach = true;
126     },
127
128     /**
129      * @return {boolean}
130      */
131     _inNotification: function()
132     {
133         return !!this._notificationDepth || (this._parentView && this._parentView._inNotification());
134     },
135
136     _parentIsShowing: function()
137     {
138         if (this._isRoot)
139             return true;
140         return this._parentView && this._parentView.isShowing();
141     },
142
143     /**
144      * @param {function(this:WebInspector.View)} method
145      */
146     _callOnVisibleChildren: function(method)
147     {
148         var copy = this._children.slice();
149         for (var i = 0; i < copy.length; ++i) {
150             if (copy[i]._parentView === this && copy[i]._visible)
151                 method.call(copy[i]);
152         }
153     },
154
155     _processWillShow: function()
156     {
157         this._callOnVisibleChildren(this._processWillShow);
158         this._isShowing = true;
159     },
160
161     _processWasShown: function()
162     {
163         if (this._inNotification())
164             return;
165         this.restoreScrollPositions();
166         this._notify(this.wasShown);
167         this._callOnVisibleChildren(this._processWasShown);
168     },
169
170     _processWillHide: function()
171     {
172         if (this._inNotification())
173             return;
174         this.storeScrollPositions();
175
176         this._callOnVisibleChildren(this._processWillHide);
177         this._notify(this.willHide);
178         this._isShowing = false;
179     },
180
181     _processWasHidden: function()
182     {
183         this._callOnVisibleChildren(this._processWasHidden);
184     },
185
186     _processOnResize: function()
187     {
188         if (this._inNotification())
189             return;
190         if (!this.isShowing())
191             return;
192         this._notify(this.onResize);
193         this._callOnVisibleChildren(this._processOnResize);
194     },
195
196     /**
197      * @param {function(this:WebInspector.View)} notification
198      */
199     _notify: function(notification)
200     {
201         ++this._notificationDepth;
202         try {
203             notification.call(this);
204         } finally {
205             --this._notificationDepth;
206         }
207     },
208
209     wasShown: function()
210     {
211     },
212
213     willHide: function()
214     {
215     },
216
217     onResize: function()
218     {
219     },
220
221     onLayout: function()
222     {
223     },
224
225     /**
226      * @param {?Element} parentElement
227      * @param {?Element=} insertBefore
228      */
229     show: function(parentElement, insertBefore)
230     {
231         WebInspector.View.__assert(parentElement, "Attempt to attach view with no parent element");
232
233         // Update view hierarchy
234         if (this.element.parentElement !== parentElement) {
235             if (this.element.parentElement)
236                 this.detach();
237
238             var currentParent = parentElement;
239             while (currentParent && !currentParent.__view)
240                 currentParent = currentParent.parentElementOrShadowHost();
241
242             if (currentParent) {
243                 this._parentView = currentParent.__view;
244                 this._parentView._children.push(this);
245                 this._isRoot = false;
246             } else
247                 WebInspector.View.__assert(this._isRoot, "Attempt to attach view to orphan node");
248         } else if (this._visible) {
249             return;
250         }
251
252         this._visible = true;
253
254         if (this._parentIsShowing())
255             this._processWillShow();
256
257         this.element.classList.add("visible");
258
259         // Reparent
260         if (this.element.parentElement !== parentElement) {
261             WebInspector.View._incrementViewCounter(parentElement, this.element);
262             if (insertBefore)
263                 WebInspector.View._originalInsertBefore.call(parentElement, this.element, insertBefore);
264             else
265                 WebInspector.View._originalAppendChild.call(parentElement, this.element);
266         }
267
268         if (this._parentIsShowing())
269             this._processWasShown();
270
271         if (this._parentView && this._hasNonZeroConstraints())
272             this._parentView.invalidateConstraints();
273         else
274             this._processOnResize();
275     },
276
277     /**
278      * @param {boolean=} overrideHideOnDetach
279      */
280     detach: function(overrideHideOnDetach)
281     {
282         var parentElement = this.element.parentElement;
283         if (!parentElement)
284             return;
285
286         if (this._parentIsShowing())
287             this._processWillHide();
288
289         if (!overrideHideOnDetach && this._shouldHideOnDetach()) {
290             this.element.classList.remove("visible");
291             this._visible = false;
292             if (this._parentIsShowing())
293                 this._processWasHidden();
294             if (this._parentView && this._hasNonZeroConstraints())
295                 this._parentView.invalidateConstraints();
296             return;
297         }
298
299         // Force legal removal
300         WebInspector.View._decrementViewCounter(parentElement, this.element);
301         WebInspector.View._originalRemoveChild.call(parentElement, this.element);
302
303         this._visible = false;
304         if (this._parentIsShowing())
305             this._processWasHidden();
306
307         // Update view hierarchy
308         if (this._parentView) {
309             var childIndex = this._parentView._children.indexOf(this);
310             WebInspector.View.__assert(childIndex >= 0, "Attempt to remove non-child view");
311             this._parentView._children.splice(childIndex, 1);
312             var parent = this._parentView;
313             this._parentView = null;
314             if (this._hasNonZeroConstraints())
315                 parent.invalidateConstraints();
316         } else
317             WebInspector.View.__assert(this._isRoot, "Removing non-root view from DOM");
318     },
319
320     detachChildViews: function()
321     {
322         var children = this._children.slice();
323         for (var i = 0; i < children.length; ++i)
324             children[i].detach();
325     },
326
327     /**
328      * @return {!Array.<!Element>}
329      */
330     elementsToRestoreScrollPositionsFor: function()
331     {
332         return [this.element];
333     },
334
335     storeScrollPositions: function()
336     {
337         var elements = this.elementsToRestoreScrollPositionsFor();
338         for (var i = 0; i < elements.length; ++i) {
339             var container = elements[i];
340             container._scrollTop = container.scrollTop;
341             container._scrollLeft = container.scrollLeft;
342         }
343     },
344
345     restoreScrollPositions: function()
346     {
347         var elements = this.elementsToRestoreScrollPositionsFor();
348         for (var i = 0; i < elements.length; ++i) {
349             var container = elements[i];
350             if (container._scrollTop)
351                 container.scrollTop = container._scrollTop;
352             if (container._scrollLeft)
353                 container.scrollLeft = container._scrollLeft;
354         }
355     },
356
357     doResize: function()
358     {
359         if (!this.isShowing())
360             return;
361         // No matter what notification we are in, dispatching onResize is not needed.
362         if (!this._inNotification())
363             this._callOnVisibleChildren(this._processOnResize);
364     },
365
366     doLayout: function()
367     {
368         if (!this.isShowing())
369             return;
370         this._notify(this.onLayout);
371         this.doResize();
372     },
373
374     registerRequiredCSS: function(cssFile)
375     {
376         this.element.appendChild(WebInspector.View.createStyleElement(cssFile));
377     },
378
379     printViewHierarchy: function()
380     {
381         var lines = [];
382         this._collectViewHierarchy("", lines);
383         console.log(lines.join("\n"));
384     },
385
386     _collectViewHierarchy: function(prefix, lines)
387     {
388         lines.push(prefix + "[" + this.element.className + "]" + (this._children.length ? " {" : ""));
389
390         for (var i = 0; i < this._children.length; ++i)
391             this._children[i]._collectViewHierarchy(prefix + "    ", lines);
392
393         if (this._children.length)
394             lines.push(prefix + "}");
395     },
396
397     /**
398      * @return {!Element}
399      */
400     defaultFocusedElement: function()
401     {
402         return this._defaultFocusedElement || this.element;
403     },
404
405     /**
406      * @param {!Element} element
407      */
408     setDefaultFocusedElement: function(element)
409     {
410         this._defaultFocusedElement = element;
411     },
412
413     focus: function()
414     {
415         var element = this.defaultFocusedElement();
416         if (!element || element.isAncestor(this.element.ownerDocument.activeElement))
417             return;
418
419         WebInspector.setCurrentFocusElement(element);
420     },
421
422     /**
423      * @return {boolean}
424      */
425     hasFocus: function()
426     {
427         var activeElement = this.element.ownerDocument.activeElement;
428         return activeElement && activeElement.isSelfOrDescendant(this.element);
429     },
430
431     /**
432      * @return {!Size}
433      */
434     measurePreferredSize: function()
435     {
436         var document = this.element.ownerDocument;
437         WebInspector.View._originalAppendChild.call(document.body, this.element);
438         this.element.positionAt(0, 0);
439         var result = new Size(this.element.offsetWidth, this.element.offsetHeight);
440         this.element.positionAt(undefined, undefined);
441         WebInspector.View._originalRemoveChild.call(document.body, this.element);
442         return result;
443     },
444
445     /**
446      * @return {!Constraints}
447      */
448     calculateConstraints: function()
449     {
450         return new Constraints(new Size(0, 0));
451     },
452
453     /**
454      * @return {!Constraints}
455      */
456     constraints: function()
457     {
458         if (typeof this._constraints !== "undefined")
459             return this._constraints;
460         if (typeof this._cachedConstraints === "undefined")
461             this._cachedConstraints = this.calculateConstraints();
462         return this._cachedConstraints;
463     },
464
465     /**
466      * @param {number} width
467      * @param {number} height
468      * @param {number} preferredWidth
469      * @param {number} preferredHeight
470      */
471     setMinimumAndPreferredSizes: function(width, height, preferredWidth, preferredHeight)
472     {
473         this._constraints = new Constraints(new Size(width, height), new Size(preferredWidth, preferredHeight));
474         this.invalidateConstraints();
475     },
476
477     /**
478      * @param {number} width
479      * @param {number} height
480      */
481     setMinimumSize: function(width, height)
482     {
483         this._constraints = new Constraints(new Size(width, height));
484         this.invalidateConstraints();
485     },
486
487     /**
488      * @return {boolean}
489      */
490     _hasNonZeroConstraints: function()
491     {
492         var constraints = this.constraints();
493         return !!(constraints.minimum.width || constraints.minimum.height || constraints.preferred.width || constraints.preferred.height);
494     },
495
496     invalidateConstraints: function()
497     {
498         var cached = this._cachedConstraints;
499         delete this._cachedConstraints;
500         var actual = this.constraints();
501         if (!actual.isEqual(cached) && this._parentView)
502             this._parentView.invalidateConstraints();
503         else
504             this.doLayout();
505     },
506
507     __proto__: WebInspector.Object.prototype
508 }
509
510 WebInspector.View._originalAppendChild = Element.prototype.appendChild;
511 WebInspector.View._originalInsertBefore = Element.prototype.insertBefore;
512 WebInspector.View._originalRemoveChild = Element.prototype.removeChild;
513 WebInspector.View._originalRemoveChildren = Element.prototype.removeChildren;
514
515 WebInspector.View._incrementViewCounter = function(parentElement, childElement)
516 {
517     var count = (childElement.__viewCounter || 0) + (childElement.__view ? 1 : 0);
518     if (!count)
519         return;
520
521     while (parentElement) {
522         parentElement.__viewCounter = (parentElement.__viewCounter || 0) + count;
523         parentElement = parentElement.parentElementOrShadowHost();
524     }
525 }
526
527 WebInspector.View._decrementViewCounter = function(parentElement, childElement)
528 {
529     var count = (childElement.__viewCounter || 0) + (childElement.__view ? 1 : 0);
530     if (!count)
531         return;
532
533     while (parentElement) {
534         parentElement.__viewCounter -= count;
535         parentElement = parentElement.parentElementOrShadowHost();
536     }
537 }
538
539 WebInspector.View.__assert = function(condition, message)
540 {
541     if (!condition) {
542         console.trace();
543         throw new Error(message);
544     }
545 }
546
547 /**
548  * @constructor
549  * @extends {WebInspector.View}
550  * @param {boolean=} isWebComponent
551  */
552 WebInspector.VBox = function(isWebComponent)
553 {
554     WebInspector.View.call(this, isWebComponent);
555     this.contentElement.classList.add("vbox");
556 };
557
558 WebInspector.VBox.prototype = {
559     /**
560      * @return {!Constraints}
561      */
562     calculateConstraints: function()
563     {
564         var constraints = new Constraints(new Size(0, 0));
565
566         /**
567          * @this {!WebInspector.View}
568          * @suppressReceiverCheck
569          */
570         function updateForChild()
571         {
572             var child = this.constraints();
573             constraints = constraints.widthToMax(child);
574             constraints = constraints.addHeight(child);
575         }
576
577         this._callOnVisibleChildren(updateForChild);
578         return constraints;
579     },
580
581     __proto__: WebInspector.View.prototype
582 };
583
584 /**
585  * @constructor
586  * @extends {WebInspector.View}
587  * @param {boolean=} isWebComponent
588  */
589 WebInspector.HBox = function(isWebComponent)
590 {
591     WebInspector.View.call(this, isWebComponent);
592     this.contentElement.classList.add("hbox");
593 };
594
595 WebInspector.HBox.prototype = {
596     /**
597      * @return {!Constraints}
598      */
599     calculateConstraints: function()
600     {
601         var constraints = new Constraints(new Size(0, 0));
602
603         /**
604          * @this {!WebInspector.View}
605          * @suppressReceiverCheck
606          */
607         function updateForChild()
608         {
609             var child = this.constraints();
610             constraints = constraints.addWidth(child);
611             constraints = constraints.heightToMax(child);
612         }
613
614         this._callOnVisibleChildren(updateForChild);
615         return constraints;
616     },
617
618     __proto__: WebInspector.View.prototype
619 };
620
621 /**
622  * @constructor
623  * @extends {WebInspector.VBox}
624  * @param {function()} resizeCallback
625  */
626 WebInspector.VBoxWithResizeCallback = function(resizeCallback)
627 {
628     WebInspector.VBox.call(this);
629     this._resizeCallback = resizeCallback;
630 }
631
632 WebInspector.VBoxWithResizeCallback.prototype = {
633     onResize: function()
634     {
635         this._resizeCallback();
636     },
637
638     __proto__: WebInspector.VBox.prototype
639 }
640
641 /**
642  * @param {?Node} child
643  * @return {?Node}
644  * @suppress {duplicate}
645  */
646 Element.prototype.appendChild = function(child)
647 {
648     WebInspector.View.__assert(!child.__view || child.parentElement === this, "Attempt to add view via regular DOM operation.");
649     return WebInspector.View._originalAppendChild.call(this, child);
650 }
651
652 /**
653  * @param {?Node} child
654  * @param {?Node} anchor
655  * @return {!Node}
656  * @suppress {duplicate}
657  */
658 Element.prototype.insertBefore = function(child, anchor)
659 {
660     WebInspector.View.__assert(!child.__view || child.parentElement === this, "Attempt to add view via regular DOM operation.");
661     return WebInspector.View._originalInsertBefore.call(this, child, anchor);
662 }
663
664 /**
665  * @param {?Node} child
666  * @return {!Node}
667  * @suppress {duplicate}
668  */
669 Element.prototype.removeChild = function(child)
670 {
671     WebInspector.View.__assert(!child.__viewCounter && !child.__view, "Attempt to remove element containing view via regular DOM operation");
672     return WebInspector.View._originalRemoveChild.call(this, child);
673 }
674
675 Element.prototype.removeChildren = function()
676 {
677     WebInspector.View.__assert(!this.__viewCounter, "Attempt to remove element containing view via regular DOM operation");
678     WebInspector.View._originalRemoveChildren.call(this);
679 }