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}
31 WebInspector.View = function()
33 this.element = document.createElement("div");
34 this.element.__view = this;
37 this._isShowing = false;
39 this._hideOnDetach = false;
41 this._notificationDepth = 0;
44 WebInspector.View._cssFileToVisibleViewCount = {};
45 WebInspector.View._cssFileToStyleElement = {};
47 WebInspector.View.prototype = {
51 statusBarText: function()
56 markAsRoot: function()
58 WebInspector.View._assert(!this.element.parentElement, "Attempt to mark as root attached node");
64 return this._isShowing;
67 setHideOnDetach: function()
69 this._hideOnDetach = true;
75 _inNotification: function()
77 return !!this._notificationDepth || (this._parentView && this._parentView._inNotification());
80 _parentIsShowing: function()
84 return this._parentView && this._parentView.isShowing();
88 * @param {function(this:WebInspector.View)} method
90 _callOnVisibleChildren: function(method)
92 var copy = this._children.slice();
93 for (var i = 0; i < copy.length; ++i) {
94 if (copy[i]._parentView === this && copy[i]._visible)
99 _processWillShow: function()
101 this._loadCSSIfNeeded();
102 this._callOnVisibleChildren(this._processWillShow);
105 _processWasShown: function()
107 if (this._inNotification())
109 this._isShowing = true;
110 this.restoreScrollPositions();
111 this._notify(this.wasShown);
112 this._notify(this.onResize);
113 this._callOnVisibleChildren(this._processWasShown);
116 _processWillHide: function()
118 if (this._inNotification())
120 this.storeScrollPositions();
122 this._callOnVisibleChildren(this._processWillHide);
123 this._notify(this.willHide);
124 this._isShowing = false;
127 _processWasHidden: function()
129 this._disableCSSIfNeeded();
130 this._callOnVisibleChildren(this._processWasHidden);
133 _processOnResize: function()
135 if (this._inNotification())
137 if (!this.isShowing())
139 this._notify(this.onResize);
140 this._callOnVisibleChildren(this._processOnResize);
144 * @param {function(this:WebInspector.View)} notification
146 _notify: function(notification)
148 ++this._notificationDepth;
150 notification.call(this);
152 --this._notificationDepth;
169 * @param {Element} parentElement
170 * @param {Element=} insertBefore
172 show: function(parentElement, insertBefore)
174 WebInspector.View._assert(parentElement, "Attempt to attach view with no parent element");
176 // Update view hierarchy
177 if (this.element.parentElement !== parentElement) {
178 if (this.element.parentElement)
181 var currentParent = parentElement;
182 while (currentParent && !currentParent.__view)
183 currentParent = currentParent.parentElement;
186 this._parentView = currentParent.__view;
187 this._parentView._children.push(this);
188 this._isRoot = false;
190 WebInspector.View._assert(this._isRoot, "Attempt to attach view to orphan node");
191 } else if (this._visible)
194 this._visible = true;
196 if (this._parentIsShowing())
197 this._processWillShow();
199 this.element.addStyleClass("visible");
202 if (this.element.parentElement !== parentElement) {
203 WebInspector.View._incrementViewCounter(parentElement, this.element);
205 WebInspector.View._originalInsertBefore.call(parentElement, this.element, insertBefore);
207 WebInspector.View._originalAppendChild.call(parentElement, this.element);
210 if (this._parentIsShowing())
211 this._processWasShown();
215 * @param {boolean=} overrideHideOnDetach
217 detach: function(overrideHideOnDetach)
219 var parentElement = this.element.parentElement;
223 if (this._parentIsShowing())
224 this._processWillHide();
226 if (this._hideOnDetach && !overrideHideOnDetach) {
227 this.element.removeStyleClass("visible");
228 this._visible = false;
229 if (this._parentIsShowing())
230 this._processWasHidden();
234 // Force legal removal
235 WebInspector.View._decrementViewCounter(parentElement, this.element);
236 WebInspector.View._originalRemoveChild.call(parentElement, this.element);
238 this._visible = false;
239 if (this._parentIsShowing())
240 this._processWasHidden();
242 // Update view hierarchy
243 if (this._parentView) {
244 var childIndex = this._parentView._children.indexOf(this);
245 WebInspector.View._assert(childIndex >= 0, "Attempt to remove non-child view");
246 this._parentView._children.splice(childIndex, 1);
247 this._parentView = null;
249 WebInspector.View._assert(this._isRoot, "Removing non-root view from DOM");
252 detachChildViews: function()
254 var children = this._children.slice();
255 for (var i = 0; i < children.length; ++i)
256 children[i].detach();
259 elementsToRestoreScrollPositionsFor: function()
261 return [this.element];
264 storeScrollPositions: function()
266 var elements = this.elementsToRestoreScrollPositionsFor();
267 for (var i = 0; i < elements.length; ++i) {
268 var container = elements[i];
269 container._scrollTop = container.scrollTop;
270 container._scrollLeft = container.scrollLeft;
274 restoreScrollPositions: function()
276 var elements = this.elementsToRestoreScrollPositionsFor();
277 for (var i = 0; i < elements.length; ++i) {
278 var container = elements[i];
279 if (container._scrollTop)
280 container.scrollTop = container._scrollTop;
281 if (container._scrollLeft)
282 container.scrollLeft = container._scrollLeft;
286 canHighlightLine: function()
291 highlightLine: function(line)
297 this._processOnResize();
300 registerRequiredCSS: function(cssFile)
302 this._cssFiles.push(cssFile);
305 _loadCSSIfNeeded: function()
307 for (var i = 0; i < this._cssFiles.length; ++i) {
308 var cssFile = this._cssFiles[i];
310 var viewsWithCSSFile = WebInspector.View._cssFileToVisibleViewCount[cssFile];
311 WebInspector.View._cssFileToVisibleViewCount[cssFile] = (viewsWithCSSFile || 0) + 1;
312 if (!viewsWithCSSFile)
313 this._doLoadCSS(cssFile);
317 _doLoadCSS: function(cssFile)
319 var styleElement = WebInspector.View._cssFileToStyleElement[cssFile];
321 styleElement.disabled = false;
325 if (window.debugCSS) { /* debugging support */
326 styleElement = document.createElement("link");
327 styleElement.rel = "stylesheet";
328 styleElement.type = "text/css";
329 styleElement.href = cssFile;
331 var xhr = new XMLHttpRequest();
332 xhr.open("GET", cssFile, false);
335 styleElement = document.createElement("style");
336 styleElement.type = "text/css";
337 styleElement.textContent = xhr.responseText;
339 document.head.insertBefore(styleElement, document.head.firstChild);
341 WebInspector.View._cssFileToStyleElement[cssFile] = styleElement;
344 _disableCSSIfNeeded: function()
346 for (var i = 0; i < this._cssFiles.length; ++i) {
347 var cssFile = this._cssFiles[i];
349 var viewsWithCSSFile = WebInspector.View._cssFileToVisibleViewCount[cssFile];
351 WebInspector.View._cssFileToVisibleViewCount[cssFile] = viewsWithCSSFile;
353 if (!viewsWithCSSFile)
354 this._doUnloadCSS(cssFile);
358 _doUnloadCSS: function(cssFile)
360 var styleElement = WebInspector.View._cssFileToStyleElement[cssFile];
361 styleElement.disabled = true;
364 printViewHierarchy: function()
367 this._collectViewHierarchy("", lines);
368 console.log(lines.join("\n"));
371 _collectViewHierarchy: function(prefix, lines)
373 lines.push(prefix + "[" + this.element.className + "]" + (this._children.length ? " {" : ""));
375 for (var i = 0; i < this._children.length; ++i)
376 this._children[i]._collectViewHierarchy(prefix + " ", lines);
378 if (this._children.length)
379 lines.push(prefix + "}");
385 defaultFocusedElement: function()
387 return this._defaultFocusedElement || this.element;
391 * @param {Element} element
393 setDefaultFocusedElement: function(element)
395 this._defaultFocusedElement = element;
400 var element = this.defaultFocusedElement();
401 if (!element || element.isAncestor(document.activeElement))
404 WebInspector.setCurrentFocusElement(element);
410 measurePreferredSize: function()
412 this._loadCSSIfNeeded();
413 WebInspector.View._originalAppendChild.call(document.body, this.element);
414 this.element.positionAt(0, 0);
415 var result = new Size(this.element.offsetWidth, this.element.offsetHeight);
416 this.element.positionAt(undefined, undefined);
417 WebInspector.View._originalRemoveChild.call(document.body, this.element);
418 this._disableCSSIfNeeded();
422 __proto__: WebInspector.Object.prototype
425 WebInspector.View._originalAppendChild = Element.prototype.appendChild;
426 WebInspector.View._originalInsertBefore = Element.prototype.insertBefore;
427 WebInspector.View._originalRemoveChild = Element.prototype.removeChild;
428 WebInspector.View._originalRemoveChildren = Element.prototype.removeChildren;
430 WebInspector.View._incrementViewCounter = function(parentElement, childElement)
432 var count = (childElement.__viewCounter || 0) + (childElement.__view ? 1 : 0);
436 while (parentElement) {
437 parentElement.__viewCounter = (parentElement.__viewCounter || 0) + count;
438 parentElement = parentElement.parentElement;
442 WebInspector.View._decrementViewCounter = function(parentElement, childElement)
444 var count = (childElement.__viewCounter || 0) + (childElement.__view ? 1 : 0);
448 while (parentElement) {
449 parentElement.__viewCounter -= count;
450 parentElement = parentElement.parentElement;
454 WebInspector.View._assert = function(condition, message)
458 throw new Error(message);
462 Element.prototype.appendChild = function(child)
464 WebInspector.View._assert(!child.__view, "Attempt to add view via regular DOM operation.");
465 return WebInspector.View._originalAppendChild.call(this, child);
468 Element.prototype.insertBefore = function(child, anchor)
470 WebInspector.View._assert(!child.__view, "Attempt to add view via regular DOM operation.");
471 return WebInspector.View._originalInsertBefore.call(this, child, anchor);
475 Element.prototype.removeChild = function(child)
477 WebInspector.View._assert(!child.__viewCounter && !child.__view, "Attempt to remove element containing view via regular DOM operation");
478 return WebInspector.View._originalRemoveChild.call(this, child);
481 Element.prototype.removeChildren = function()
483 WebInspector.View._assert(!this.__viewCounter, "Attempt to remove element containing view via regular DOM operation");
484 WebInspector.View._originalRemoveChildren.call(this);