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.View = function(isWebComponent)
34 this.contentElement = createElementWithClass("div", "view");
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);
41 this.element = this.contentElement;
43 this.element.__view = this;
46 this._isShowing = false;
48 this._hideOnDetach = false;
50 this._notificationDepth = 0;
53 WebInspector.View._buildSourceURL = function(cssFile)
55 return "\n/*# sourceURL=" + WebInspector.ParsedURL.completeURL(window.location.href, cssFile) + " */";
59 * @param {string} cssFile
62 WebInspector.View.createStyleElement = function(cssFile)
64 var content = Runtime.cachedResources[cssFile];
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;
71 var styleElement = createElement("style");
72 styleElement.type = "text/css";
73 styleElement.textContent = content;
77 WebInspector.View.prototype = {
78 markAsRoot: function()
80 WebInspector.installComponentRootStyles(this.element);
81 WebInspector.View.__assert(!this.element.parentElement, "Attempt to mark as root attached node");
86 * @return {?WebInspector.View}
88 parentView: function()
90 return this._parentView;
94 * @return {!Array.<!WebInspector.View>}
98 return this._children;
104 isShowing: function()
106 return this._isShowing;
112 _shouldHideOnDetach: function()
114 if (this._hideOnDetach)
116 for (var child of this._children) {
117 if (child._shouldHideOnDetach())
123 setHideOnDetach: function()
125 this._hideOnDetach = true;
131 _inNotification: function()
133 return !!this._notificationDepth || (this._parentView && this._parentView._inNotification());
136 _parentIsShowing: function()
140 return this._parentView && this._parentView.isShowing();
144 * @param {function(this:WebInspector.View)} method
146 _callOnVisibleChildren: function(method)
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]);
155 _processWillShow: function()
157 this._callOnVisibleChildren(this._processWillShow);
158 this._isShowing = true;
161 _processWasShown: function()
163 if (this._inNotification())
165 this.restoreScrollPositions();
166 this._notify(this.wasShown);
167 this._callOnVisibleChildren(this._processWasShown);
170 _processWillHide: function()
172 if (this._inNotification())
174 this.storeScrollPositions();
176 this._callOnVisibleChildren(this._processWillHide);
177 this._notify(this.willHide);
178 this._isShowing = false;
181 _processWasHidden: function()
183 this._callOnVisibleChildren(this._processWasHidden);
186 _processOnResize: function()
188 if (this._inNotification())
190 if (!this.isShowing())
192 this._notify(this.onResize);
193 this._callOnVisibleChildren(this._processOnResize);
197 * @param {function(this:WebInspector.View)} notification
199 _notify: function(notification)
201 ++this._notificationDepth;
203 notification.call(this);
205 --this._notificationDepth;
226 * @param {?Element} parentElement
227 * @param {?Element=} insertBefore
229 show: function(parentElement, insertBefore)
231 WebInspector.View.__assert(parentElement, "Attempt to attach view with no parent element");
233 // Update view hierarchy
234 if (this.element.parentElement !== parentElement) {
235 if (this.element.parentElement)
238 var currentParent = parentElement;
239 while (currentParent && !currentParent.__view)
240 currentParent = currentParent.parentElementOrShadowHost();
243 this._parentView = currentParent.__view;
244 this._parentView._children.push(this);
245 this._isRoot = false;
247 WebInspector.View.__assert(this._isRoot, "Attempt to attach view to orphan node");
248 } else if (this._visible) {
252 this._visible = true;
254 if (this._parentIsShowing())
255 this._processWillShow();
257 this.element.classList.add("visible");
260 if (this.element.parentElement !== parentElement) {
261 WebInspector.View._incrementViewCounter(parentElement, this.element);
263 WebInspector.View._originalInsertBefore.call(parentElement, this.element, insertBefore);
265 WebInspector.View._originalAppendChild.call(parentElement, this.element);
268 if (this._parentIsShowing())
269 this._processWasShown();
271 if (this._parentView && this._hasNonZeroConstraints())
272 this._parentView.invalidateConstraints();
274 this._processOnResize();
278 * @param {boolean=} overrideHideOnDetach
280 detach: function(overrideHideOnDetach)
282 var parentElement = this.element.parentElement;
286 if (this._parentIsShowing())
287 this._processWillHide();
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();
299 // Force legal removal
300 WebInspector.View._decrementViewCounter(parentElement, this.element);
301 WebInspector.View._originalRemoveChild.call(parentElement, this.element);
303 this._visible = false;
304 if (this._parentIsShowing())
305 this._processWasHidden();
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();
317 WebInspector.View.__assert(this._isRoot, "Removing non-root view from DOM");
320 detachChildViews: function()
322 var children = this._children.slice();
323 for (var i = 0; i < children.length; ++i)
324 children[i].detach();
328 * @return {!Array.<!Element>}
330 elementsToRestoreScrollPositionsFor: function()
332 return [this.element];
335 storeScrollPositions: function()
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;
345 restoreScrollPositions: function()
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;
359 if (!this.isShowing())
361 // No matter what notification we are in, dispatching onResize is not needed.
362 if (!this._inNotification())
363 this._callOnVisibleChildren(this._processOnResize);
368 if (!this.isShowing())
370 this._notify(this.onLayout);
374 registerRequiredCSS: function(cssFile)
376 this.element.appendChild(WebInspector.View.createStyleElement(cssFile));
379 printViewHierarchy: function()
382 this._collectViewHierarchy("", lines);
383 console.log(lines.join("\n"));
386 _collectViewHierarchy: function(prefix, lines)
388 lines.push(prefix + "[" + this.element.className + "]" + (this._children.length ? " {" : ""));
390 for (var i = 0; i < this._children.length; ++i)
391 this._children[i]._collectViewHierarchy(prefix + " ", lines);
393 if (this._children.length)
394 lines.push(prefix + "}");
400 defaultFocusedElement: function()
402 return this._defaultFocusedElement || this.element;
406 * @param {!Element} element
408 setDefaultFocusedElement: function(element)
410 this._defaultFocusedElement = element;
415 var element = this.defaultFocusedElement();
416 if (!element || element.isAncestor(this.element.ownerDocument.activeElement))
419 WebInspector.setCurrentFocusElement(element);
427 var activeElement = this.element.ownerDocument.activeElement;
428 return activeElement && activeElement.isSelfOrDescendant(this.element);
434 measurePreferredSize: function()
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);
446 * @return {!Constraints}
448 calculateConstraints: function()
450 return new Constraints(new Size(0, 0));
454 * @return {!Constraints}
456 constraints: function()
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;
466 * @param {number} width
467 * @param {number} height
468 * @param {number} preferredWidth
469 * @param {number} preferredHeight
471 setMinimumAndPreferredSizes: function(width, height, preferredWidth, preferredHeight)
473 this._constraints = new Constraints(new Size(width, height), new Size(preferredWidth, preferredHeight));
474 this.invalidateConstraints();
478 * @param {number} width
479 * @param {number} height
481 setMinimumSize: function(width, height)
483 this._constraints = new Constraints(new Size(width, height));
484 this.invalidateConstraints();
490 _hasNonZeroConstraints: function()
492 var constraints = this.constraints();
493 return !!(constraints.minimum.width || constraints.minimum.height || constraints.preferred.width || constraints.preferred.height);
496 invalidateConstraints: function()
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();
507 __proto__: WebInspector.Object.prototype
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;
515 WebInspector.View._incrementViewCounter = function(parentElement, childElement)
517 var count = (childElement.__viewCounter || 0) + (childElement.__view ? 1 : 0);
521 while (parentElement) {
522 parentElement.__viewCounter = (parentElement.__viewCounter || 0) + count;
523 parentElement = parentElement.parentElementOrShadowHost();
527 WebInspector.View._decrementViewCounter = function(parentElement, childElement)
529 var count = (childElement.__viewCounter || 0) + (childElement.__view ? 1 : 0);
533 while (parentElement) {
534 parentElement.__viewCounter -= count;
535 parentElement = parentElement.parentElementOrShadowHost();
539 WebInspector.View.__assert = function(condition, message)
543 throw new Error(message);
549 * @extends {WebInspector.View}
550 * @param {boolean=} isWebComponent
552 WebInspector.VBox = function(isWebComponent)
554 WebInspector.View.call(this, isWebComponent);
555 this.contentElement.classList.add("vbox");
558 WebInspector.VBox.prototype = {
560 * @return {!Constraints}
562 calculateConstraints: function()
564 var constraints = new Constraints(new Size(0, 0));
567 * @this {!WebInspector.View}
568 * @suppressReceiverCheck
570 function updateForChild()
572 var child = this.constraints();
573 constraints = constraints.widthToMax(child);
574 constraints = constraints.addHeight(child);
577 this._callOnVisibleChildren(updateForChild);
581 __proto__: WebInspector.View.prototype
586 * @extends {WebInspector.View}
587 * @param {boolean=} isWebComponent
589 WebInspector.HBox = function(isWebComponent)
591 WebInspector.View.call(this, isWebComponent);
592 this.contentElement.classList.add("hbox");
595 WebInspector.HBox.prototype = {
597 * @return {!Constraints}
599 calculateConstraints: function()
601 var constraints = new Constraints(new Size(0, 0));
604 * @this {!WebInspector.View}
605 * @suppressReceiverCheck
607 function updateForChild()
609 var child = this.constraints();
610 constraints = constraints.addWidth(child);
611 constraints = constraints.heightToMax(child);
614 this._callOnVisibleChildren(updateForChild);
618 __proto__: WebInspector.View.prototype
623 * @extends {WebInspector.VBox}
624 * @param {function()} resizeCallback
626 WebInspector.VBoxWithResizeCallback = function(resizeCallback)
628 WebInspector.VBox.call(this);
629 this._resizeCallback = resizeCallback;
632 WebInspector.VBoxWithResizeCallback.prototype = {
635 this._resizeCallback();
638 __proto__: WebInspector.VBox.prototype
642 * @param {?Node} child
644 * @suppress {duplicate}
646 Element.prototype.appendChild = function(child)
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);
653 * @param {?Node} child
654 * @param {?Node} anchor
656 * @suppress {duplicate}
658 Element.prototype.insertBefore = function(child, anchor)
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);
665 * @param {?Node} child
667 * @suppress {duplicate}
669 Element.prototype.removeChild = function(child)
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);
675 Element.prototype.removeChildren = function()
677 WebInspector.View.__assert(!this.__viewCounter, "Attempt to remove element containing view via regular DOM operation");
678 WebInspector.View._originalRemoveChildren.call(this);