OLD | NEW |
| (Empty) |
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._isWebComponent = isWebComponent; | |
44 this.element.__view = this; | |
45 this._visible = true; | |
46 this._isRoot = false; | |
47 this._isShowing = false; | |
48 this._children = []; | |
49 this._hideOnDetach = false; | |
50 this._notificationDepth = 0; | |
51 } | |
52 | |
53 /** | |
54 * @param {string} cssFile | |
55 * @return {!Element} | |
56 */ | |
57 WebInspector.View.createStyleElement = function(cssFile) | |
58 { | |
59 var content = Runtime.cachedResources[cssFile] || ""; | |
60 if (!content) | |
61 console.error(cssFile + " not preloaded. Check module.json"); | |
62 var styleElement = createElement("style"); | |
63 styleElement.type = "text/css"; | |
64 styleElement.textContent = content; | |
65 return styleElement; | |
66 } | |
67 | |
68 WebInspector.View.prototype = { | |
69 markAsRoot: function() | |
70 { | |
71 WebInspector.installComponentRootStyles(this.element); | |
72 WebInspector.View.__assert(!this.element.parentElement, "Attempt to mark
as root attached node"); | |
73 this._isRoot = true; | |
74 }, | |
75 | |
76 /** | |
77 * @return {?WebInspector.View} | |
78 */ | |
79 parentView: function() | |
80 { | |
81 return this._parentView; | |
82 }, | |
83 | |
84 /** | |
85 * @return {!Array.<!WebInspector.View>} | |
86 */ | |
87 children: function() | |
88 { | |
89 return this._children; | |
90 }, | |
91 | |
92 /** | |
93 * @param {!WebInspector.View} view | |
94 * @protected | |
95 */ | |
96 childWasDetached: function(view) | |
97 { | |
98 }, | |
99 | |
100 /** | |
101 * @return {boolean} | |
102 */ | |
103 isShowing: function() | |
104 { | |
105 return this._isShowing; | |
106 }, | |
107 | |
108 /** | |
109 * @return {boolean} | |
110 */ | |
111 _shouldHideOnDetach: function() | |
112 { | |
113 if (this._hideOnDetach) | |
114 return true; | |
115 for (var child of this._children) { | |
116 if (child._shouldHideOnDetach()) | |
117 return true; | |
118 } | |
119 return false; | |
120 }, | |
121 | |
122 setHideOnDetach: function() | |
123 { | |
124 this._hideOnDetach = true; | |
125 }, | |
126 | |
127 /** | |
128 * @return {boolean} | |
129 */ | |
130 _inNotification: function() | |
131 { | |
132 return !!this._notificationDepth || (this._parentView && this._parentVie
w._inNotification()); | |
133 }, | |
134 | |
135 _parentIsShowing: function() | |
136 { | |
137 if (this._isRoot) | |
138 return true; | |
139 return this._parentView && this._parentView.isShowing(); | |
140 }, | |
141 | |
142 /** | |
143 * @param {function(this:WebInspector.View)} method | |
144 */ | |
145 _callOnVisibleChildren: function(method) | |
146 { | |
147 var copy = this._children.slice(); | |
148 for (var i = 0; i < copy.length; ++i) { | |
149 if (copy[i]._parentView === this && copy[i]._visible) | |
150 method.call(copy[i]); | |
151 } | |
152 }, | |
153 | |
154 _processWillShow: function() | |
155 { | |
156 this._callOnVisibleChildren(this._processWillShow); | |
157 this._isShowing = true; | |
158 }, | |
159 | |
160 _processWasShown: function() | |
161 { | |
162 if (this._inNotification()) | |
163 return; | |
164 this.restoreScrollPositions(); | |
165 this._notify(this.wasShown); | |
166 this._callOnVisibleChildren(this._processWasShown); | |
167 }, | |
168 | |
169 _processWillHide: function() | |
170 { | |
171 if (this._inNotification()) | |
172 return; | |
173 this.storeScrollPositions(); | |
174 | |
175 this._callOnVisibleChildren(this._processWillHide); | |
176 this._notify(this.willHide); | |
177 this._isShowing = false; | |
178 }, | |
179 | |
180 _processWasHidden: function() | |
181 { | |
182 this._callOnVisibleChildren(this._processWasHidden); | |
183 }, | |
184 | |
185 _processOnResize: function() | |
186 { | |
187 if (this._inNotification()) | |
188 return; | |
189 if (!this.isShowing()) | |
190 return; | |
191 this._notify(this.onResize); | |
192 this._callOnVisibleChildren(this._processOnResize); | |
193 }, | |
194 | |
195 /** | |
196 * @param {function(this:WebInspector.View)} notification | |
197 */ | |
198 _notify: function(notification) | |
199 { | |
200 ++this._notificationDepth; | |
201 try { | |
202 notification.call(this); | |
203 } finally { | |
204 --this._notificationDepth; | |
205 } | |
206 }, | |
207 | |
208 wasShown: function() | |
209 { | |
210 }, | |
211 | |
212 willHide: function() | |
213 { | |
214 }, | |
215 | |
216 onResize: function() | |
217 { | |
218 }, | |
219 | |
220 onLayout: function() | |
221 { | |
222 }, | |
223 | |
224 /** | |
225 * @param {?Element} parentElement | |
226 * @param {?Element=} insertBefore | |
227 */ | |
228 show: function(parentElement, insertBefore) | |
229 { | |
230 WebInspector.View.__assert(parentElement, "Attempt to attach view with n
o parent element"); | |
231 | |
232 // Update view hierarchy | |
233 if (this.element.parentElement !== parentElement) { | |
234 if (this.element.parentElement) | |
235 this.detach(); | |
236 | |
237 var currentParent = parentElement; | |
238 while (currentParent && !currentParent.__view) | |
239 currentParent = currentParent.parentElementOrShadowHost(); | |
240 | |
241 if (currentParent) { | |
242 this._parentView = currentParent.__view; | |
243 this._parentView._children.push(this); | |
244 this._isRoot = false; | |
245 } else | |
246 WebInspector.View.__assert(this._isRoot, "Attempt to attach view
to orphan node"); | |
247 } else if (this._visible) { | |
248 return; | |
249 } | |
250 | |
251 this._visible = true; | |
252 | |
253 if (this._parentIsShowing()) | |
254 this._processWillShow(); | |
255 | |
256 this.element.classList.add("visible"); | |
257 | |
258 // Reparent | |
259 if (this.element.parentElement !== parentElement) { | |
260 WebInspector.View._incrementViewCounter(parentElement, this.element)
; | |
261 if (insertBefore) | |
262 WebInspector.View._originalInsertBefore.call(parentElement, this
.element, insertBefore); | |
263 else | |
264 WebInspector.View._originalAppendChild.call(parentElement, this.
element); | |
265 } | |
266 | |
267 if (this._parentIsShowing()) | |
268 this._processWasShown(); | |
269 | |
270 if (this._parentView && this._hasNonZeroConstraints()) | |
271 this._parentView.invalidateConstraints(); | |
272 else | |
273 this._processOnResize(); | |
274 }, | |
275 | |
276 /** | |
277 * @param {boolean=} overrideHideOnDetach | |
278 */ | |
279 detach: function(overrideHideOnDetach) | |
280 { | |
281 var parentElement = this.element.parentElement; | |
282 if (!parentElement) | |
283 return; | |
284 | |
285 if (this._parentIsShowing()) | |
286 this._processWillHide(); | |
287 | |
288 if (!overrideHideOnDetach && this._shouldHideOnDetach()) { | |
289 this.element.classList.remove("visible"); | |
290 this._visible = false; | |
291 if (this._parentIsShowing()) | |
292 this._processWasHidden(); | |
293 if (this._parentView && this._hasNonZeroConstraints()) | |
294 this._parentView.invalidateConstraints(); | |
295 return; | |
296 } | |
297 | |
298 // Force legal removal | |
299 WebInspector.View._decrementViewCounter(parentElement, this.element); | |
300 WebInspector.View._originalRemoveChild.call(parentElement, this.element)
; | |
301 | |
302 this._visible = false; | |
303 if (this._parentIsShowing()) | |
304 this._processWasHidden(); | |
305 | |
306 // Update view hierarchy | |
307 if (this._parentView) { | |
308 var childIndex = this._parentView._children.indexOf(this); | |
309 WebInspector.View.__assert(childIndex >= 0, "Attempt to remove non-c
hild view"); | |
310 this._parentView._children.splice(childIndex, 1); | |
311 this._parentView.childWasDetached(this); | |
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 fro
m 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 ne
eded. | |
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 /** | |
375 * @param {string} cssFile | |
376 */ | |
377 registerRequiredCSS: function(cssFile) | |
378 { | |
379 (this._isWebComponent ? this._shadowRoot : this.element).appendChild(Web
Inspector.View.createStyleElement(cssFile)); | |
380 }, | |
381 | |
382 printViewHierarchy: function() | |
383 { | |
384 var lines = []; | |
385 this._collectViewHierarchy("", lines); | |
386 console.log(lines.join("\n")); | |
387 }, | |
388 | |
389 _collectViewHierarchy: function(prefix, lines) | |
390 { | |
391 lines.push(prefix + "[" + this.element.className + "]" + (this._children
.length ? " {" : "")); | |
392 | |
393 for (var i = 0; i < this._children.length; ++i) | |
394 this._children[i]._collectViewHierarchy(prefix + " ", lines); | |
395 | |
396 if (this._children.length) | |
397 lines.push(prefix + "}"); | |
398 }, | |
399 | |
400 /** | |
401 * @return {!Element} | |
402 */ | |
403 defaultFocusedElement: function() | |
404 { | |
405 return this._defaultFocusedElement || this.element; | |
406 }, | |
407 | |
408 /** | |
409 * @param {!Element} element | |
410 */ | |
411 setDefaultFocusedElement: function(element) | |
412 { | |
413 this._defaultFocusedElement = element; | |
414 }, | |
415 | |
416 focus: function() | |
417 { | |
418 var element = this.defaultFocusedElement(); | |
419 if (!element || element.isAncestor(this.element.ownerDocument.activeElem
ent)) | |
420 return; | |
421 | |
422 WebInspector.setCurrentFocusElement(element); | |
423 }, | |
424 | |
425 /** | |
426 * @return {boolean} | |
427 */ | |
428 hasFocus: function() | |
429 { | |
430 var activeElement = this.element.ownerDocument.activeElement; | |
431 return activeElement && activeElement.isSelfOrDescendant(this.element); | |
432 }, | |
433 | |
434 /** | |
435 * @return {!Size} | |
436 */ | |
437 measurePreferredSize: function() | |
438 { | |
439 var document = this.element.ownerDocument; | |
440 WebInspector.View._originalAppendChild.call(document.body, this.element)
; | |
441 this.element.positionAt(0, 0); | |
442 var result = new Size(this.element.offsetWidth, this.element.offsetHeigh
t); | |
443 this.element.positionAt(undefined, undefined); | |
444 WebInspector.View._originalRemoveChild.call(document.body, this.element)
; | |
445 return result; | |
446 }, | |
447 | |
448 /** | |
449 * @return {!Constraints} | |
450 */ | |
451 calculateConstraints: function() | |
452 { | |
453 return new Constraints(); | |
454 }, | |
455 | |
456 /** | |
457 * @return {!Constraints} | |
458 */ | |
459 constraints: function() | |
460 { | |
461 if (typeof this._constraints !== "undefined") | |
462 return this._constraints; | |
463 if (typeof this._cachedConstraints === "undefined") | |
464 this._cachedConstraints = this.calculateConstraints(); | |
465 return this._cachedConstraints; | |
466 }, | |
467 | |
468 /** | |
469 * @param {number} width | |
470 * @param {number} height | |
471 * @param {number} preferredWidth | |
472 * @param {number} preferredHeight | |
473 */ | |
474 setMinimumAndPreferredSizes: function(width, height, preferredWidth, preferr
edHeight) | |
475 { | |
476 this._constraints = new Constraints(new Size(width, height), new Size(pr
eferredWidth, preferredHeight)); | |
477 this.invalidateConstraints(); | |
478 }, | |
479 | |
480 /** | |
481 * @param {number} width | |
482 * @param {number} height | |
483 */ | |
484 setMinimumSize: function(width, height) | |
485 { | |
486 this._constraints = new Constraints(new Size(width, height)); | |
487 this.invalidateConstraints(); | |
488 }, | |
489 | |
490 /** | |
491 * @return {boolean} | |
492 */ | |
493 _hasNonZeroConstraints: function() | |
494 { | |
495 var constraints = this.constraints(); | |
496 return !!(constraints.minimum.width || constraints.minimum.height || con
straints.preferred.width || constraints.preferred.height); | |
497 }, | |
498 | |
499 invalidateConstraints: function() | |
500 { | |
501 var cached = this._cachedConstraints; | |
502 delete this._cachedConstraints; | |
503 var actual = this.constraints(); | |
504 if (!actual.isEqual(cached) && this._parentView) | |
505 this._parentView.invalidateConstraints(); | |
506 else | |
507 this.doLayout(); | |
508 }, | |
509 | |
510 __proto__: WebInspector.Object.prototype | |
511 } | |
512 | |
513 WebInspector.View._originalAppendChild = Element.prototype.appendChild; | |
514 WebInspector.View._originalInsertBefore = Element.prototype.insertBefore; | |
515 WebInspector.View._originalRemoveChild = Element.prototype.removeChild; | |
516 WebInspector.View._originalRemoveChildren = Element.prototype.removeChildren; | |
517 | |
518 WebInspector.View._incrementViewCounter = function(parentElement, childElement) | |
519 { | |
520 var count = (childElement.__viewCounter || 0) + (childElement.__view ? 1 : 0
); | |
521 if (!count) | |
522 return; | |
523 | |
524 while (parentElement) { | |
525 parentElement.__viewCounter = (parentElement.__viewCounter || 0) + count
; | |
526 parentElement = parentElement.parentElementOrShadowHost(); | |
527 } | |
528 } | |
529 | |
530 WebInspector.View._decrementViewCounter = function(parentElement, childElement) | |
531 { | |
532 var count = (childElement.__viewCounter || 0) + (childElement.__view ? 1 : 0
); | |
533 if (!count) | |
534 return; | |
535 | |
536 while (parentElement) { | |
537 parentElement.__viewCounter -= count; | |
538 parentElement = parentElement.parentElementOrShadowHost(); | |
539 } | |
540 } | |
541 | |
542 WebInspector.View.__assert = function(condition, message) | |
543 { | |
544 if (!condition) { | |
545 console.trace(); | |
546 throw new Error(message); | |
547 } | |
548 } | |
549 | |
550 /** | |
551 * @constructor | |
552 * @extends {WebInspector.View} | |
553 * @param {boolean=} isWebComponent | |
554 */ | |
555 WebInspector.VBox = function(isWebComponent) | |
556 { | |
557 WebInspector.View.call(this, isWebComponent); | |
558 this.contentElement.classList.add("vbox"); | |
559 }; | |
560 | |
561 WebInspector.VBox.prototype = { | |
562 /** | |
563 * @override | |
564 * @return {!Constraints} | |
565 */ | |
566 calculateConstraints: function() | |
567 { | |
568 var constraints = new Constraints(); | |
569 | |
570 /** | |
571 * @this {!WebInspector.View} | |
572 * @suppressReceiverCheck | |
573 */ | |
574 function updateForChild() | |
575 { | |
576 var child = this.constraints(); | |
577 constraints = constraints.widthToMax(child); | |
578 constraints = constraints.addHeight(child); | |
579 } | |
580 | |
581 this._callOnVisibleChildren(updateForChild); | |
582 return constraints; | |
583 }, | |
584 | |
585 __proto__: WebInspector.View.prototype | |
586 }; | |
587 | |
588 /** | |
589 * @constructor | |
590 * @extends {WebInspector.View} | |
591 * @param {boolean=} isWebComponent | |
592 */ | |
593 WebInspector.HBox = function(isWebComponent) | |
594 { | |
595 WebInspector.View.call(this, isWebComponent); | |
596 this.contentElement.classList.add("hbox"); | |
597 }; | |
598 | |
599 WebInspector.HBox.prototype = { | |
600 /** | |
601 * @override | |
602 * @return {!Constraints} | |
603 */ | |
604 calculateConstraints: function() | |
605 { | |
606 var constraints = new Constraints(); | |
607 | |
608 /** | |
609 * @this {!WebInspector.View} | |
610 * @suppressReceiverCheck | |
611 */ | |
612 function updateForChild() | |
613 { | |
614 var child = this.constraints(); | |
615 constraints = constraints.addWidth(child); | |
616 constraints = constraints.heightToMax(child); | |
617 } | |
618 | |
619 this._callOnVisibleChildren(updateForChild); | |
620 return constraints; | |
621 }, | |
622 | |
623 __proto__: WebInspector.View.prototype | |
624 }; | |
625 | |
626 /** | |
627 * @constructor | |
628 * @extends {WebInspector.VBox} | |
629 * @param {function()} resizeCallback | |
630 */ | |
631 WebInspector.VBoxWithResizeCallback = function(resizeCallback) | |
632 { | |
633 WebInspector.VBox.call(this); | |
634 this._resizeCallback = resizeCallback; | |
635 } | |
636 | |
637 WebInspector.VBoxWithResizeCallback.prototype = { | |
638 onResize: function() | |
639 { | |
640 this._resizeCallback(); | |
641 }, | |
642 | |
643 __proto__: WebInspector.VBox.prototype | |
644 } | |
645 | |
646 /** | |
647 * @override | |
648 * @param {?Node} child | |
649 * @return {?Node} | |
650 * @suppress {duplicate} | |
651 */ | |
652 Element.prototype.appendChild = function(child) | |
653 { | |
654 WebInspector.View.__assert(!child.__view || child.parentElement === this, "A
ttempt to add view via regular DOM operation."); | |
655 return WebInspector.View._originalAppendChild.call(this, child); | |
656 } | |
657 | |
658 /** | |
659 * @override | |
660 * @param {?Node} child | |
661 * @param {?Node} anchor | |
662 * @return {!Node} | |
663 * @suppress {duplicate} | |
664 */ | |
665 Element.prototype.insertBefore = function(child, anchor) | |
666 { | |
667 WebInspector.View.__assert(!child.__view || child.parentElement === this, "A
ttempt to add view via regular DOM operation."); | |
668 return WebInspector.View._originalInsertBefore.call(this, child, anchor); | |
669 } | |
670 | |
671 /** | |
672 * @override | |
673 * @param {?Node} child | |
674 * @return {!Node} | |
675 * @suppress {duplicate} | |
676 */ | |
677 Element.prototype.removeChild = function(child) | |
678 { | |
679 WebInspector.View.__assert(!child.__viewCounter && !child.__view, "Attempt t
o remove element containing view via regular DOM operation"); | |
680 return WebInspector.View._originalRemoveChild.call(this, child); | |
681 } | |
682 | |
683 Element.prototype.removeChildren = function() | |
684 { | |
685 WebInspector.View.__assert(!this.__viewCounter, "Attempt to remove element c
ontaining view via regular DOM operation"); | |
686 WebInspector.View._originalRemoveChildren.call(this); | |
687 } | |
OLD | NEW |