2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 * Copyright (C) 2011 Google Inc. All Rights Reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
29 * @extends {WebInspector.Object}
30 * @param {boolean=} isWebComponent
32 WebInspector
.Widget = function(isWebComponent
)
34 this.contentElement
= createElementWithClass("div", "widget");
36 this.element
= createElementWithClass("div", "vbox flex-auto");
37 this._shadowRoot
= WebInspector
.createShadowRootWithCoreStyles(this.element
);
38 this._shadowRoot
.appendChild(this.contentElement
);
40 this.element
= this.contentElement
;
42 this._isWebComponent
= isWebComponent
;
43 this.element
.__widget
= this;
46 this._isShowing
= false;
48 this._hideOnDetach
= false;
49 this._notificationDepth
= 0;
53 * @param {string} cssFile
56 WebInspector
.Widget
.createStyleElement = function(cssFile
)
58 var content
= Runtime
.cachedResources
[cssFile
] || "";
60 console
.error(cssFile
+ " not preloaded. Check module.json");
61 var styleElement
= createElement("style");
62 styleElement
.type
= "text/css";
63 styleElement
.textContent
= content
;
67 WebInspector
.Widget
.prototype = {
68 markAsRoot: function()
70 WebInspector
.Widget
.__assert(!this.element
.parentElement
, "Attempt to mark as root attached node");
75 * @return {?WebInspector.Widget}
77 parentWidget: function()
79 return this._parentWidget
;
83 * @return {!Array.<!WebInspector.Widget>}
87 return this._children
;
91 * @param {!WebInspector.Widget} widget
94 childWasDetached: function(widget
)
101 isShowing: function()
103 return this._isShowing
;
109 shouldHideOnDetach: function()
111 if (this._hideOnDetach
)
113 for (var child
of this._children
) {
114 if (child
.shouldHideOnDetach())
120 setHideOnDetach: function()
122 this._hideOnDetach
= true;
128 _inNotification: function()
130 return !!this._notificationDepth
|| (this._parentWidget
&& this._parentWidget
._inNotification());
133 _parentIsShowing: function()
137 return this._parentWidget
&& this._parentWidget
.isShowing();
141 * @param {function(this:WebInspector.Widget)} method
143 _callOnVisibleChildren: function(method
)
145 var copy
= this._children
.slice();
146 for (var i
= 0; i
< copy
.length
; ++i
) {
147 if (copy
[i
]._parentWidget
=== this && copy
[i
]._visible
)
148 method
.call(copy
[i
]);
152 _processWillShow: function()
154 this._callOnVisibleChildren(this._processWillShow
);
155 this._isShowing
= true;
158 _processWasShown: function()
160 if (this._inNotification())
162 this.restoreScrollPositions();
163 this._notify(this.wasShown
);
164 this._callOnVisibleChildren(this._processWasShown
);
167 _processWillHide: function()
169 if (this._inNotification())
171 this.storeScrollPositions();
173 this._callOnVisibleChildren(this._processWillHide
);
174 this._notify(this.willHide
);
175 this._isShowing
= false;
178 _processWasHidden: function()
180 this._callOnVisibleChildren(this._processWasHidden
);
183 _processOnResize: function()
185 if (this._inNotification())
187 if (!this.isShowing())
189 this._notify(this.onResize
);
190 this._callOnVisibleChildren(this._processOnResize
);
194 * @param {function(this:WebInspector.Widget)} notification
196 _notify: function(notification
)
198 ++this._notificationDepth
;
200 notification
.call(this);
202 --this._notificationDepth
;
223 * @param {?Element} parentElement
224 * @param {?Element=} insertBefore
226 show: function(parentElement
, insertBefore
)
228 WebInspector
.Widget
.__assert(parentElement
, "Attempt to attach widget with no parent element");
230 // Update widget hierarchy.
231 if (this.element
.parentElement
!== parentElement
) {
232 if (this.element
.parentElement
)
235 var currentParent
= parentElement
;
236 while (currentParent
&& !currentParent
.__widget
)
237 currentParent
= currentParent
.parentElementOrShadowHost();
240 this._parentWidget
= currentParent
.__widget
;
241 this._parentWidget
._children
.push(this);
242 this._isRoot
= false;
244 WebInspector
.Widget
.__assert(this._isRoot
, "Attempt to attach widget to orphan node");
245 } else if (this._visible
) {
249 this._visible
= true;
251 if (this._parentIsShowing())
252 this._processWillShow();
254 this.element
.classList
.remove("hidden");
257 if (this.element
.parentElement
!== parentElement
) {
258 WebInspector
.Widget
._incrementWidgetCounter(parentElement
, this.element
);
260 WebInspector
.Widget
._originalInsertBefore
.call(parentElement
, this.element
, insertBefore
);
262 WebInspector
.Widget
._originalAppendChild
.call(parentElement
, this.element
);
265 if (this._parentIsShowing())
266 this._processWasShown();
268 if (this._parentWidget
&& this._hasNonZeroConstraints())
269 this._parentWidget
.invalidateConstraints();
271 this._processOnResize();
275 * @param {boolean=} overrideHideOnDetach
277 detach: function(overrideHideOnDetach
)
279 var parentElement
= this.element
.parentElement
;
283 if (this._parentIsShowing())
284 this._processWillHide();
286 if (!overrideHideOnDetach
&& this.shouldHideOnDetach()) {
287 this.element
.classList
.add("hidden");
288 this._visible
= false;
289 if (this._parentIsShowing())
290 this._processWasHidden();
291 if (this._parentWidget
&& this._hasNonZeroConstraints())
292 this._parentWidget
.invalidateConstraints();
296 // Force legal removal
297 WebInspector
.Widget
._decrementWidgetCounter(parentElement
, this.element
);
298 WebInspector
.Widget
._originalRemoveChild
.call(parentElement
, this.element
);
300 this._visible
= false;
301 if (this._parentIsShowing())
302 this._processWasHidden();
304 // Update widget hierarchy.
305 if (this._parentWidget
) {
306 var childIndex
= this._parentWidget
._children
.indexOf(this);
307 WebInspector
.Widget
.__assert(childIndex
>= 0, "Attempt to remove non-child widget");
308 this._parentWidget
._children
.splice(childIndex
, 1);
309 this._parentWidget
.childWasDetached(this);
310 var parent
= this._parentWidget
;
311 this._parentWidget
= null;
312 if (this._hasNonZeroConstraints())
313 parent
.invalidateConstraints();
315 WebInspector
.Widget
.__assert(this._isRoot
, "Removing non-root widget from DOM");
318 detachChildWidgets: function()
320 var children
= this._children
.slice();
321 for (var i
= 0; i
< children
.length
; ++i
)
322 children
[i
].detach();
326 * @return {!Array.<!Element>}
328 elementsToRestoreScrollPositionsFor: function()
330 return [this.element
];
333 storeScrollPositions: function()
335 var elements
= this.elementsToRestoreScrollPositionsFor();
336 for (var i
= 0; i
< elements
.length
; ++i
) {
337 var container
= elements
[i
];
338 container
._scrollTop
= container
.scrollTop
;
339 container
._scrollLeft
= container
.scrollLeft
;
343 restoreScrollPositions: function()
345 var elements
= this.elementsToRestoreScrollPositionsFor();
346 for (var i
= 0; i
< elements
.length
; ++i
) {
347 var container
= elements
[i
];
348 if (container
._scrollTop
)
349 container
.scrollTop
= container
._scrollTop
;
350 if (container
._scrollLeft
)
351 container
.scrollLeft
= container
._scrollLeft
;
357 if (!this.isShowing())
359 // No matter what notification we are in, dispatching onResize is not needed.
360 if (!this._inNotification())
361 this._callOnVisibleChildren(this._processOnResize
);
366 if (!this.isShowing())
368 this._notify(this.onLayout
);
373 * @param {string} cssFile
375 registerRequiredCSS: function(cssFile
)
377 (this._isWebComponent
? this._shadowRoot
: this.element
).appendChild(WebInspector
.Widget
.createStyleElement(cssFile
));
380 printWidgetHierarchy: function()
383 this._collectWidgetHierarchy("", lines
);
384 console
.log(lines
.join("\n"));
387 _collectWidgetHierarchy: function(prefix
, lines
)
389 lines
.push(prefix
+ "[" + this.element
.className
+ "]" + (this._children
.length
? " {" : ""));
391 for (var i
= 0; i
< this._children
.length
; ++i
)
392 this._children
[i
]._collectWidgetHierarchy(prefix
+ " ", lines
);
394 if (this._children
.length
)
395 lines
.push(prefix
+ "}");
401 defaultFocusedElement: function()
403 return this._defaultFocusedElement
|| this.element
;
407 * @param {!Element} element
409 setDefaultFocusedElement: function(element
)
411 this._defaultFocusedElement
= element
;
416 var element
= this.defaultFocusedElement();
417 if (!element
|| element
.isAncestor(this.element
.ownerDocument
.activeElement
))
420 WebInspector
.setCurrentFocusElement(element
);
428 var activeElement
= this.element
.ownerDocument
.activeElement
;
429 return activeElement
&& activeElement
.isSelfOrDescendant(this.element
);
435 measurePreferredSize: function()
437 var document
= this.element
.ownerDocument
;
438 WebInspector
.Widget
._originalAppendChild
.call(document
.body
, this.element
);
439 this.element
.positionAt(0, 0);
440 var result
= new Size(this.element
.offsetWidth
, this.element
.offsetHeight
);
441 this.element
.positionAt(undefined, undefined);
442 WebInspector
.Widget
._originalRemoveChild
.call(document
.body
, this.element
);
447 * @return {!Constraints}
449 calculateConstraints: function()
451 return new Constraints();
455 * @return {!Constraints}
457 constraints: function()
459 if (typeof this._constraints
!== "undefined")
460 return this._constraints
;
461 if (typeof this._cachedConstraints
=== "undefined")
462 this._cachedConstraints
= this.calculateConstraints();
463 return this._cachedConstraints
;
467 * @param {number} width
468 * @param {number} height
469 * @param {number} preferredWidth
470 * @param {number} preferredHeight
472 setMinimumAndPreferredSizes: function(width
, height
, preferredWidth
, preferredHeight
)
474 this._constraints
= new Constraints(new Size(width
, height
), new Size(preferredWidth
, preferredHeight
));
475 this.invalidateConstraints();
479 * @param {number} width
480 * @param {number} height
482 setMinimumSize: function(width
, height
)
484 this._constraints
= new Constraints(new Size(width
, height
));
485 this.invalidateConstraints();
491 _hasNonZeroConstraints: function()
493 var constraints
= this.constraints();
494 return !!(constraints
.minimum
.width
|| constraints
.minimum
.height
|| constraints
.preferred
.width
|| constraints
.preferred
.height
);
497 invalidateConstraints: function()
499 var cached
= this._cachedConstraints
;
500 delete this._cachedConstraints
;
501 var actual
= this.constraints();
502 if (!actual
.isEqual(cached
) && this._parentWidget
)
503 this._parentWidget
.invalidateConstraints();
508 __proto__
: WebInspector
.Object
.prototype
511 WebInspector
.Widget
._originalAppendChild
= Element
.prototype.appendChild
;
512 WebInspector
.Widget
._originalInsertBefore
= Element
.prototype.insertBefore
;
513 WebInspector
.Widget
._originalRemoveChild
= Element
.prototype.removeChild
;
514 WebInspector
.Widget
._originalRemoveChildren
= Element
.prototype.removeChildren
;
516 WebInspector
.Widget
._incrementWidgetCounter = function(parentElement
, childElement
)
518 var count
= (childElement
.__widgetCounter
|| 0) + (childElement
.__widget
? 1 : 0);
522 while (parentElement
) {
523 parentElement
.__widgetCounter
= (parentElement
.__widgetCounter
|| 0) + count
;
524 parentElement
= parentElement
.parentElementOrShadowHost();
528 WebInspector
.Widget
._decrementWidgetCounter = function(parentElement
, childElement
)
530 var count
= (childElement
.__widgetCounter
|| 0) + (childElement
.__widget
? 1 : 0);
534 while (parentElement
) {
535 parentElement
.__widgetCounter
-= count
;
536 parentElement
= parentElement
.parentElementOrShadowHost();
540 WebInspector
.Widget
.__assert = function(condition
, message
)
544 throw new Error(message
);
550 * @extends {WebInspector.Widget}
551 * @param {boolean=} isWebComponent
553 WebInspector
.VBox = function(isWebComponent
)
555 WebInspector
.Widget
.call(this, isWebComponent
);
556 this.contentElement
.classList
.add("vbox");
559 WebInspector
.VBox
.prototype = {
562 * @return {!Constraints}
564 calculateConstraints: function()
566 var constraints
= new Constraints();
569 * @this {!WebInspector.Widget}
570 * @suppressReceiverCheck
572 function updateForChild()
574 var child
= this.constraints();
575 constraints
= constraints
.widthToMax(child
);
576 constraints
= constraints
.addHeight(child
);
579 this._callOnVisibleChildren(updateForChild
);
583 __proto__
: WebInspector
.Widget
.prototype
588 * @extends {WebInspector.Widget}
589 * @param {boolean=} isWebComponent
591 WebInspector
.HBox = function(isWebComponent
)
593 WebInspector
.Widget
.call(this, isWebComponent
);
594 this.contentElement
.classList
.add("hbox");
597 WebInspector
.HBox
.prototype = {
600 * @return {!Constraints}
602 calculateConstraints: function()
604 var constraints
= new Constraints();
607 * @this {!WebInspector.Widget}
608 * @suppressReceiverCheck
610 function updateForChild()
612 var child
= this.constraints();
613 constraints
= constraints
.addWidth(child
);
614 constraints
= constraints
.heightToMax(child
);
617 this._callOnVisibleChildren(updateForChild
);
621 __proto__
: WebInspector
.Widget
.prototype
626 * @extends {WebInspector.VBox}
627 * @param {function()} resizeCallback
629 WebInspector
.VBoxWithResizeCallback = function(resizeCallback
)
631 WebInspector
.VBox
.call(this);
632 this._resizeCallback
= resizeCallback
;
635 WebInspector
.VBoxWithResizeCallback
.prototype = {
638 this._resizeCallback();
641 __proto__
: WebInspector
.VBox
.prototype
646 * @param {?Node} child
648 * @suppress {duplicate}
650 Element
.prototype.appendChild = function(child
)
652 WebInspector
.Widget
.__assert(!child
.__widget
|| child
.parentElement
=== this, "Attempt to add widget via regular DOM operation.");
653 return WebInspector
.Widget
._originalAppendChild
.call(this, child
);
658 * @param {?Node} child
659 * @param {?Node} anchor
661 * @suppress {duplicate}
663 Element
.prototype.insertBefore = function(child
, anchor
)
665 WebInspector
.Widget
.__assert(!child
.__widget
|| child
.parentElement
=== this, "Attempt to add widget via regular DOM operation.");
666 return WebInspector
.Widget
._originalInsertBefore
.call(this, child
, anchor
);
671 * @param {?Node} child
673 * @suppress {duplicate}
675 Element
.prototype.removeChild = function(child
)
677 WebInspector
.Widget
.__assert(!child
.__widgetCounter
&& !child
.__widget
, "Attempt to remove element containing widget via regular DOM operation");
678 return WebInspector
.Widget
._originalRemoveChild
.call(this, child
);
681 Element
.prototype.removeChildren = function()
683 WebInspector
.Widget
.__assert(!this.__widgetCounter
, "Attempt to remove element containing widget via regular DOM operation");
684 WebInspector
.Widget
._originalRemoveChildren
.call(this);