blob: 522e90fb129a1fe2dd340b781f77010936c78af1 [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371/*
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 * @unrestricted
29 */
Tim van der Lippe0830b3d2019-10-03 13:20:0730export default class Widget extends Common.Object {
Blink Reformat4c46d092018-04-07 15:32:3731 /**
32 * @param {boolean=} isWebComponent
Joel Einbinder7fbe24c2019-01-24 05:19:0133 * @param {boolean=} delegatesFocus
Blink Reformat4c46d092018-04-07 15:32:3734 */
Joel Einbinder7fbe24c2019-01-24 05:19:0135 constructor(isWebComponent, delegatesFocus) {
Blink Reformat4c46d092018-04-07 15:32:3736 super();
37 this.contentElement = createElementWithClass('div', 'widget');
38 if (isWebComponent) {
39 this.element = createElementWithClass('div', 'vbox flex-auto');
Joel Einbinder7fbe24c2019-01-24 05:19:0140 this._shadowRoot = UI.createShadowRootWithCoreStyles(this.element, undefined, delegatesFocus);
Blink Reformat4c46d092018-04-07 15:32:3741 this._shadowRoot.appendChild(this.contentElement);
42 } else {
43 this.element = this.contentElement;
44 }
45 this._isWebComponent = isWebComponent;
46 this.element.__widget = this;
47 this._visible = false;
48 this._isRoot = false;
49 this._isShowing = false;
50 this._children = [];
51 this._hideOnDetach = false;
52 this._notificationDepth = 0;
53 this._invalidationsSuspended = 0;
54 this._defaultFocusedChild = null;
55 }
56
57 static _incrementWidgetCounter(parentElement, childElement) {
58 const count = (childElement.__widgetCounter || 0) + (childElement.__widget ? 1 : 0);
Tim van der Lippe1d6e57a2019-09-30 11:55:3459 if (!count) {
Blink Reformat4c46d092018-04-07 15:32:3760 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:3461 }
Blink Reformat4c46d092018-04-07 15:32:3762
63 while (parentElement) {
64 parentElement.__widgetCounter = (parentElement.__widgetCounter || 0) + count;
65 parentElement = parentElement.parentElementOrShadowHost();
66 }
67 }
68
69 static _decrementWidgetCounter(parentElement, childElement) {
70 const count = (childElement.__widgetCounter || 0) + (childElement.__widget ? 1 : 0);
Tim van der Lippe1d6e57a2019-09-30 11:55:3471 if (!count) {
Blink Reformat4c46d092018-04-07 15:32:3772 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:3473 }
Blink Reformat4c46d092018-04-07 15:32:3774
75 while (parentElement) {
76 parentElement.__widgetCounter -= count;
77 parentElement = parentElement.parentElementOrShadowHost();
78 }
79 }
80
81 static __assert(condition, message) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3482 if (!condition) {
Blink Reformat4c46d092018-04-07 15:32:3783 throw new Error(message);
Tim van der Lippe1d6e57a2019-09-30 11:55:3484 }
Blink Reformat4c46d092018-04-07 15:32:3785 }
86
87 /**
88 * @param {?Node} node
89 */
90 static focusWidgetForNode(node) {
91 while (node) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3492 if (node.__widget) {
Blink Reformat4c46d092018-04-07 15:32:3793 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:3494 }
Blink Reformat4c46d092018-04-07 15:32:3795 node = node.parentNodeOrShadowHost();
96 }
Tim van der Lippe1d6e57a2019-09-30 11:55:3497 if (!node) {
Blink Reformat4c46d092018-04-07 15:32:3798 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:3499 }
Blink Reformat4c46d092018-04-07 15:32:37100
101 let widget = node.__widget;
102 while (widget._parentWidget) {
103 widget._parentWidget._defaultFocusedChild = widget;
104 widget = widget._parentWidget;
105 }
106 }
107
108 markAsRoot() {
Tim van der Lippe0830b3d2019-10-03 13:20:07109 Widget.__assert(!this.element.parentElement, 'Attempt to mark as root attached node');
Blink Reformat4c46d092018-04-07 15:32:37110 this._isRoot = true;
111 }
112
113 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07114 * @return {?Widget}
Blink Reformat4c46d092018-04-07 15:32:37115 */
116 parentWidget() {
117 return this._parentWidget;
118 }
119
120 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07121 * @return {!Array.<!Widget>}
Blink Reformat4c46d092018-04-07 15:32:37122 */
123 children() {
124 return this._children;
125 }
126
127 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07128 * @param {!Widget} widget
Blink Reformat4c46d092018-04-07 15:32:37129 * @protected
130 */
131 childWasDetached(widget) {
132 }
133
134 /**
135 * @return {boolean}
136 */
137 isShowing() {
138 return this._isShowing;
139 }
140
141 /**
142 * @return {boolean}
143 */
144 shouldHideOnDetach() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34145 if (!this.element.parentElement) {
Blink Reformat4c46d092018-04-07 15:32:37146 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34147 }
148 if (this._hideOnDetach) {
Blink Reformat4c46d092018-04-07 15:32:37149 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34150 }
Blink Reformat4c46d092018-04-07 15:32:37151 for (const child of this._children) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34152 if (child.shouldHideOnDetach()) {
Blink Reformat4c46d092018-04-07 15:32:37153 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34154 }
Blink Reformat4c46d092018-04-07 15:32:37155 }
156 return false;
157 }
158
159 setHideOnDetach() {
160 this._hideOnDetach = true;
161 }
162
163 /**
164 * @return {boolean}
165 */
166 _inNotification() {
167 return !!this._notificationDepth || (this._parentWidget && this._parentWidget._inNotification());
168 }
169
170 _parentIsShowing() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34171 if (this._isRoot) {
Blink Reformat4c46d092018-04-07 15:32:37172 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34173 }
Blink Reformat4c46d092018-04-07 15:32:37174 return !!this._parentWidget && this._parentWidget.isShowing();
175 }
176
177 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07178 * @param {function(this:Widget)} method
Blink Reformat4c46d092018-04-07 15:32:37179 */
180 _callOnVisibleChildren(method) {
181 const copy = this._children.slice();
182 for (let i = 0; i < copy.length; ++i) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34183 if (copy[i]._parentWidget === this && copy[i]._visible) {
Blink Reformat4c46d092018-04-07 15:32:37184 method.call(copy[i]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34185 }
Blink Reformat4c46d092018-04-07 15:32:37186 }
187 }
188
189 _processWillShow() {
190 this._callOnVisibleChildren(this._processWillShow);
191 this._isShowing = true;
192 }
193
194 _processWasShown() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34195 if (this._inNotification()) {
Blink Reformat4c46d092018-04-07 15:32:37196 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34197 }
Blink Reformat4c46d092018-04-07 15:32:37198 this.restoreScrollPositions();
199 this._notify(this.wasShown);
200 this._callOnVisibleChildren(this._processWasShown);
201 }
202
203 _processWillHide() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34204 if (this._inNotification()) {
Blink Reformat4c46d092018-04-07 15:32:37205 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34206 }
Blink Reformat4c46d092018-04-07 15:32:37207 this.storeScrollPositions();
208
209 this._callOnVisibleChildren(this._processWillHide);
210 this._notify(this.willHide);
211 this._isShowing = false;
212 }
213
214 _processWasHidden() {
215 this._callOnVisibleChildren(this._processWasHidden);
216 }
217
218 _processOnResize() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34219 if (this._inNotification()) {
Blink Reformat4c46d092018-04-07 15:32:37220 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34221 }
222 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37223 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34224 }
Blink Reformat4c46d092018-04-07 15:32:37225 this._notify(this.onResize);
226 this._callOnVisibleChildren(this._processOnResize);
227 }
228
229 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07230 * @param {function(this:Widget)} notification
Blink Reformat4c46d092018-04-07 15:32:37231 */
232 _notify(notification) {
233 ++this._notificationDepth;
234 try {
235 notification.call(this);
236 } finally {
237 --this._notificationDepth;
238 }
239 }
240
241 wasShown() {
242 }
243
244 willHide() {
245 }
246
247 onResize() {
248 }
249
250 onLayout() {
251 }
252
253 ownerViewDisposed() {
254 }
255
256 /**
257 * @param {!Element} parentElement
258 * @param {?Node=} insertBefore
259 */
260 show(parentElement, insertBefore) {
Tim van der Lippe0830b3d2019-10-03 13:20:07261 Widget.__assert(parentElement, 'Attempt to attach widget with no parent element');
Blink Reformat4c46d092018-04-07 15:32:37262
263 if (!this._isRoot) {
264 // Update widget hierarchy.
265 let currentParent = parentElement;
Tim van der Lippe1d6e57a2019-09-30 11:55:34266 while (currentParent && !currentParent.__widget) {
Blink Reformat4c46d092018-04-07 15:32:37267 currentParent = currentParent.parentElementOrShadowHost();
Tim van der Lippe1d6e57a2019-09-30 11:55:34268 }
Tim van der Lippe0830b3d2019-10-03 13:20:07269 Widget.__assert(currentParent, 'Attempt to attach widget to orphan node');
Blink Reformat4c46d092018-04-07 15:32:37270 this._attach(currentParent.__widget);
271 }
272
273 this._showWidget(parentElement, insertBefore);
274 }
275
276 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07277 * @param {!Widget} parentWidget
Blink Reformat4c46d092018-04-07 15:32:37278 */
279 _attach(parentWidget) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34280 if (parentWidget === this._parentWidget) {
Blink Reformat4c46d092018-04-07 15:32:37281 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34282 }
283 if (this._parentWidget) {
Blink Reformat4c46d092018-04-07 15:32:37284 this.detach();
Tim van der Lippe1d6e57a2019-09-30 11:55:34285 }
Blink Reformat4c46d092018-04-07 15:32:37286 this._parentWidget = parentWidget;
287 this._parentWidget._children.push(this);
288 this._isRoot = false;
289 }
290
291 showWidget() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34292 if (this._visible) {
Blink Reformat4c46d092018-04-07 15:32:37293 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34294 }
Tim van der Lippe0830b3d2019-10-03 13:20:07295 Widget.__assert(this.element.parentElement, 'Attempt to show widget that is not hidden using hideWidget().');
Blink Reformat4c46d092018-04-07 15:32:37296 this._showWidget(/** @type {!Element} */ (this.element.parentElement), this.element.nextSibling);
297 }
298
299 /**
300 * @param {!Element} parentElement
301 * @param {?Node=} insertBefore
302 */
303 _showWidget(parentElement, insertBefore) {
304 let currentParent = parentElement;
Tim van der Lippe1d6e57a2019-09-30 11:55:34305 while (currentParent && !currentParent.__widget) {
Blink Reformat4c46d092018-04-07 15:32:37306 currentParent = currentParent.parentElementOrShadowHost();
Tim van der Lippe1d6e57a2019-09-30 11:55:34307 }
Blink Reformat4c46d092018-04-07 15:32:37308
309 if (this._isRoot) {
Tim van der Lippe0830b3d2019-10-03 13:20:07310 Widget.__assert(!currentParent, 'Attempt to show root widget under another widget');
Blink Reformat4c46d092018-04-07 15:32:37311 } else {
Tim van der Lippe0830b3d2019-10-03 13:20:07312 Widget.__assert(
Blink Reformat4c46d092018-04-07 15:32:37313 currentParent && currentParent.__widget === this._parentWidget,
314 'Attempt to show under node belonging to alien widget');
315 }
316
317 const wasVisible = this._visible;
Tim van der Lippe1d6e57a2019-09-30 11:55:34318 if (wasVisible && this.element.parentElement === parentElement) {
Blink Reformat4c46d092018-04-07 15:32:37319 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34320 }
Blink Reformat4c46d092018-04-07 15:32:37321
322 this._visible = true;
323
Tim van der Lippe1d6e57a2019-09-30 11:55:34324 if (!wasVisible && this._parentIsShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37325 this._processWillShow();
Tim van der Lippe1d6e57a2019-09-30 11:55:34326 }
Blink Reformat4c46d092018-04-07 15:32:37327
328 this.element.classList.remove('hidden');
329
330 // Reparent
331 if (this.element.parentElement !== parentElement) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34332 if (!this._externallyManaged) {
Tim van der Lippe0830b3d2019-10-03 13:20:07333 Widget._incrementWidgetCounter(parentElement, this.element);
Tim van der Lippe1d6e57a2019-09-30 11:55:34334 }
335 if (insertBefore) {
Tim van der Lippe0830b3d2019-10-03 13:20:07336 Widget._originalInsertBefore.call(parentElement, this.element, insertBefore);
Tim van der Lippe1d6e57a2019-09-30 11:55:34337 } else {
Tim van der Lippe0830b3d2019-10-03 13:20:07338 Widget._originalAppendChild.call(parentElement, this.element);
Tim van der Lippe1d6e57a2019-09-30 11:55:34339 }
Blink Reformat4c46d092018-04-07 15:32:37340 }
341
Tim van der Lippe1d6e57a2019-09-30 11:55:34342 if (!wasVisible && this._parentIsShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37343 this._processWasShown();
Tim van der Lippe1d6e57a2019-09-30 11:55:34344 }
Blink Reformat4c46d092018-04-07 15:32:37345
Tim van der Lippe1d6e57a2019-09-30 11:55:34346 if (this._parentWidget && this._hasNonZeroConstraints()) {
Blink Reformat4c46d092018-04-07 15:32:37347 this._parentWidget.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34348 } else {
Blink Reformat4c46d092018-04-07 15:32:37349 this._processOnResize();
Tim van der Lippe1d6e57a2019-09-30 11:55:34350 }
Blink Reformat4c46d092018-04-07 15:32:37351 }
352
353 hideWidget() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34354 if (!this._visible) {
Blink Reformat4c46d092018-04-07 15:32:37355 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34356 }
Blink Reformat4c46d092018-04-07 15:32:37357 this._hideWidget(false);
358 }
359
360 /**
361 * @param {boolean} removeFromDOM
362 */
363 _hideWidget(removeFromDOM) {
364 this._visible = false;
365 const parentElement = this.element.parentElement;
366
Tim van der Lippe1d6e57a2019-09-30 11:55:34367 if (this._parentIsShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37368 this._processWillHide();
Tim van der Lippe1d6e57a2019-09-30 11:55:34369 }
Blink Reformat4c46d092018-04-07 15:32:37370
371 if (removeFromDOM) {
372 // Force legal removal
Tim van der Lippe0830b3d2019-10-03 13:20:07373 Widget._decrementWidgetCounter(parentElement, this.element);
374 Widget._originalRemoveChild.call(parentElement, this.element);
Blink Reformat4c46d092018-04-07 15:32:37375 } else {
376 this.element.classList.add('hidden');
377 }
378
Tim van der Lippe1d6e57a2019-09-30 11:55:34379 if (this._parentIsShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37380 this._processWasHidden();
Tim van der Lippe1d6e57a2019-09-30 11:55:34381 }
382 if (this._parentWidget && this._hasNonZeroConstraints()) {
Blink Reformat4c46d092018-04-07 15:32:37383 this._parentWidget.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34384 }
Blink Reformat4c46d092018-04-07 15:32:37385 }
386
387 /**
388 * @param {boolean=} overrideHideOnDetach
389 */
390 detach(overrideHideOnDetach) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34391 if (!this._parentWidget && !this._isRoot) {
Blink Reformat4c46d092018-04-07 15:32:37392 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34393 }
Blink Reformat4c46d092018-04-07 15:32:37394
395 // hideOnDetach means that we should never remove element from dom - content
396 // has iframes and detaching it will hurt.
397 //
398 // overrideHideOnDetach will override hideOnDetach and the client takes
399 // responsibility for the consequences.
400 const removeFromDOM = overrideHideOnDetach || !this.shouldHideOnDetach();
401 if (this._visible) {
402 this._hideWidget(removeFromDOM);
403 } else if (removeFromDOM && this.element.parentElement) {
404 const parentElement = this.element.parentElement;
405 // Force kick out from DOM.
Tim van der Lippe0830b3d2019-10-03 13:20:07406 Widget._decrementWidgetCounter(parentElement, this.element);
407 Widget._originalRemoveChild.call(parentElement, this.element);
Blink Reformat4c46d092018-04-07 15:32:37408 }
409
410 // Update widget hierarchy.
411 if (this._parentWidget) {
412 const childIndex = this._parentWidget._children.indexOf(this);
Tim van der Lippe0830b3d2019-10-03 13:20:07413 Widget.__assert(childIndex >= 0, 'Attempt to remove non-child widget');
Blink Reformat4c46d092018-04-07 15:32:37414 this._parentWidget._children.splice(childIndex, 1);
Tim van der Lippe1d6e57a2019-09-30 11:55:34415 if (this._parentWidget._defaultFocusedChild === this) {
Blink Reformat4c46d092018-04-07 15:32:37416 this._parentWidget._defaultFocusedChild = null;
Tim van der Lippe1d6e57a2019-09-30 11:55:34417 }
Blink Reformat4c46d092018-04-07 15:32:37418 this._parentWidget.childWasDetached(this);
419 this._parentWidget = null;
420 } else {
Tim van der Lippe0830b3d2019-10-03 13:20:07421 Widget.__assert(this._isRoot, 'Removing non-root widget from DOM');
Blink Reformat4c46d092018-04-07 15:32:37422 }
423 }
424
425 detachChildWidgets() {
426 const children = this._children.slice();
Tim van der Lippe1d6e57a2019-09-30 11:55:34427 for (let i = 0; i < children.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37428 children[i].detach();
Tim van der Lippe1d6e57a2019-09-30 11:55:34429 }
Blink Reformat4c46d092018-04-07 15:32:37430 }
431
432 /**
433 * @return {!Array.<!Element>}
434 */
435 elementsToRestoreScrollPositionsFor() {
436 return [this.element];
437 }
438
439 storeScrollPositions() {
440 const elements = this.elementsToRestoreScrollPositionsFor();
441 for (let i = 0; i < elements.length; ++i) {
442 const container = elements[i];
443 container._scrollTop = container.scrollTop;
444 container._scrollLeft = container.scrollLeft;
445 }
446 }
447
448 restoreScrollPositions() {
449 const elements = this.elementsToRestoreScrollPositionsFor();
450 for (let i = 0; i < elements.length; ++i) {
451 const container = elements[i];
Tim van der Lippe1d6e57a2019-09-30 11:55:34452 if (container._scrollTop) {
Blink Reformat4c46d092018-04-07 15:32:37453 container.scrollTop = container._scrollTop;
Tim van der Lippe1d6e57a2019-09-30 11:55:34454 }
455 if (container._scrollLeft) {
Blink Reformat4c46d092018-04-07 15:32:37456 container.scrollLeft = container._scrollLeft;
Tim van der Lippe1d6e57a2019-09-30 11:55:34457 }
Blink Reformat4c46d092018-04-07 15:32:37458 }
459 }
460
461 doResize() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34462 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37463 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34464 }
Blink Reformat4c46d092018-04-07 15:32:37465 // No matter what notification we are in, dispatching onResize is not needed.
Tim van der Lippe1d6e57a2019-09-30 11:55:34466 if (!this._inNotification()) {
Blink Reformat4c46d092018-04-07 15:32:37467 this._callOnVisibleChildren(this._processOnResize);
Tim van der Lippe1d6e57a2019-09-30 11:55:34468 }
Blink Reformat4c46d092018-04-07 15:32:37469 }
470
471 doLayout() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34472 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37473 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34474 }
Blink Reformat4c46d092018-04-07 15:32:37475 this._notify(this.onLayout);
476 this.doResize();
477 }
478
479 /**
480 * @param {string} cssFile
481 */
482 registerRequiredCSS(cssFile) {
483 UI.appendStyle(this._isWebComponent ? this._shadowRoot : this.element, cssFile);
484 }
485
486 printWidgetHierarchy() {
487 const lines = [];
488 this._collectWidgetHierarchy('', lines);
489 console.log(lines.join('\n')); // eslint-disable-line no-console
490 }
491
492 _collectWidgetHierarchy(prefix, lines) {
493 lines.push(prefix + '[' + this.element.className + ']' + (this._children.length ? ' {' : ''));
494
Tim van der Lippe1d6e57a2019-09-30 11:55:34495 for (let i = 0; i < this._children.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37496 this._children[i]._collectWidgetHierarchy(prefix + ' ', lines);
Tim van der Lippe1d6e57a2019-09-30 11:55:34497 }
Blink Reformat4c46d092018-04-07 15:32:37498
Tim van der Lippe1d6e57a2019-09-30 11:55:34499 if (this._children.length) {
Blink Reformat4c46d092018-04-07 15:32:37500 lines.push(prefix + '}');
Tim van der Lippe1d6e57a2019-09-30 11:55:34501 }
Blink Reformat4c46d092018-04-07 15:32:37502 }
503
504 /**
505 * @param {?Element} element
506 */
507 setDefaultFocusedElement(element) {
508 this._defaultFocusedElement = element;
509 }
510
511 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07512 * @param {!Widget} child
Blink Reformat4c46d092018-04-07 15:32:37513 */
514 setDefaultFocusedChild(child) {
Tim van der Lippe0830b3d2019-10-03 13:20:07515 Widget.__assert(child._parentWidget === this, 'Attempt to set non-child widget as default focused.');
Blink Reformat4c46d092018-04-07 15:32:37516 this._defaultFocusedChild = child;
517 }
518
519 focus() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34520 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37521 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34522 }
Blink Reformat4c46d092018-04-07 15:32:37523
524 const element = this._defaultFocusedElement;
525 if (element) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34526 if (!element.hasFocus()) {
Blink Reformat4c46d092018-04-07 15:32:37527 element.focus();
Tim van der Lippe1d6e57a2019-09-30 11:55:34528 }
Blink Reformat4c46d092018-04-07 15:32:37529 return;
530 }
531
532 if (this._defaultFocusedChild && this._defaultFocusedChild._visible) {
533 this._defaultFocusedChild.focus();
534 } else {
535 for (const child of this._children) {
536 if (child._visible) {
537 child.focus();
538 return;
539 }
540 }
541 let child = this.contentElement.traverseNextNode(this.contentElement);
542 while (child) {
543 if (child instanceof UI.XWidget) {
544 child.focus();
545 return;
546 }
547 child = child.traverseNextNode(this.contentElement);
548 }
549 }
550 }
551
552 /**
553 * @return {boolean}
554 */
555 hasFocus() {
556 return this.element.hasFocus();
557 }
558
559 /**
560 * @return {!UI.Constraints}
561 */
562 calculateConstraints() {
563 return new UI.Constraints();
564 }
565
566 /**
567 * @return {!UI.Constraints}
568 */
569 constraints() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34570 if (typeof this._constraints !== 'undefined') {
Blink Reformat4c46d092018-04-07 15:32:37571 return this._constraints;
Tim van der Lippe1d6e57a2019-09-30 11:55:34572 }
573 if (typeof this._cachedConstraints === 'undefined') {
Blink Reformat4c46d092018-04-07 15:32:37574 this._cachedConstraints = this.calculateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34575 }
Blink Reformat4c46d092018-04-07 15:32:37576 return this._cachedConstraints;
577 }
578
579 /**
580 * @param {number} width
581 * @param {number} height
582 * @param {number} preferredWidth
583 * @param {number} preferredHeight
584 */
585 setMinimumAndPreferredSizes(width, height, preferredWidth, preferredHeight) {
586 this._constraints = new UI.Constraints(new UI.Size(width, height), new UI.Size(preferredWidth, preferredHeight));
587 this.invalidateConstraints();
588 }
589
590 /**
591 * @param {number} width
592 * @param {number} height
593 */
594 setMinimumSize(width, height) {
595 this._constraints = new UI.Constraints(new UI.Size(width, height));
596 this.invalidateConstraints();
597 }
598
599 /**
600 * @return {boolean}
601 */
602 _hasNonZeroConstraints() {
603 const constraints = this.constraints();
604 return !!(
605 constraints.minimum.width || constraints.minimum.height || constraints.preferred.width ||
606 constraints.preferred.height);
607 }
608
609 suspendInvalidations() {
610 ++this._invalidationsSuspended;
611 }
612
613 resumeInvalidations() {
614 --this._invalidationsSuspended;
Tim van der Lippe1d6e57a2019-09-30 11:55:34615 if (!this._invalidationsSuspended && this._invalidationsRequested) {
Blink Reformat4c46d092018-04-07 15:32:37616 this.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34617 }
Blink Reformat4c46d092018-04-07 15:32:37618 }
619
620 invalidateConstraints() {
621 if (this._invalidationsSuspended) {
622 this._invalidationsRequested = true;
623 return;
624 }
625 this._invalidationsRequested = false;
626 const cached = this._cachedConstraints;
627 delete this._cachedConstraints;
628 const actual = this.constraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34629 if (!actual.isEqual(cached) && this._parentWidget) {
Blink Reformat4c46d092018-04-07 15:32:37630 this._parentWidget.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34631 } else {
Blink Reformat4c46d092018-04-07 15:32:37632 this.doLayout();
Tim van der Lippe1d6e57a2019-09-30 11:55:34633 }
Blink Reformat4c46d092018-04-07 15:32:37634 }
Olivia Flynn1d938e42019-09-23 08:13:40635
636 // Excludes the widget from being tracked by its parents/ancestors via
637 // __widgetCounter because the widget is being handled by external code.
638 // Widgets marked as being externally managed are responsible for
639 // finishing out their own lifecycle (i.e. calling detach() before being
640 // removed from the DOM). This is e.g. used for CodeMirror.
641 //
642 // Also note that this must be called before the widget is shown so that
643 // so that its ancestor's __widgetCounter is not incremented.
644 markAsExternallyManaged() {
Tim van der Lippe0830b3d2019-10-03 13:20:07645 Widget.__assert(!this._parentWidget, 'Attempt to mark widget as externally managed after insertion to the DOM');
Olivia Flynn1d938e42019-09-23 08:13:40646 this._externallyManaged = true;
647 }
Tim van der Lippe0830b3d2019-10-03 13:20:07648}
Blink Reformat4c46d092018-04-07 15:32:37649
Tim van der Lippe0830b3d2019-10-03 13:20:07650export const _originalAppendChild = Element.prototype.appendChild;
651export const _originalInsertBefore = Element.prototype.insertBefore;
652export const _originalRemoveChild = Element.prototype.removeChild;
653export const _originalRemoveChildren = Element.prototype.removeChildren;
Blink Reformat4c46d092018-04-07 15:32:37654
655
656/**
657 * @unrestricted
658 */
Tim van der Lippe0830b3d2019-10-03 13:20:07659export class VBox extends Widget {
Blink Reformat4c46d092018-04-07 15:32:37660 /**
661 * @param {boolean=} isWebComponent
Joel Einbinder7fbe24c2019-01-24 05:19:01662 * @param {boolean=} delegatesFocus
Blink Reformat4c46d092018-04-07 15:32:37663 */
Joel Einbinder7fbe24c2019-01-24 05:19:01664 constructor(isWebComponent, delegatesFocus) {
665 super(isWebComponent, delegatesFocus);
Blink Reformat4c46d092018-04-07 15:32:37666 this.contentElement.classList.add('vbox');
667 }
668
669 /**
670 * @override
671 * @return {!UI.Constraints}
672 */
673 calculateConstraints() {
674 let constraints = new UI.Constraints();
675
676 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07677 * @this {!Widget}
Blink Reformat4c46d092018-04-07 15:32:37678 * @suppressReceiverCheck
679 */
680 function updateForChild() {
681 const child = this.constraints();
682 constraints = constraints.widthToMax(child);
683 constraints = constraints.addHeight(child);
684 }
685
686 this._callOnVisibleChildren(updateForChild);
687 return constraints;
688 }
Tim van der Lippe0830b3d2019-10-03 13:20:07689}
Blink Reformat4c46d092018-04-07 15:32:37690
691/**
692 * @unrestricted
693 */
Tim van der Lippe0830b3d2019-10-03 13:20:07694export class HBox extends Widget {
Blink Reformat4c46d092018-04-07 15:32:37695 /**
696 * @param {boolean=} isWebComponent
697 */
698 constructor(isWebComponent) {
699 super(isWebComponent);
700 this.contentElement.classList.add('hbox');
701 }
702
703 /**
704 * @override
705 * @return {!UI.Constraints}
706 */
707 calculateConstraints() {
708 let constraints = new UI.Constraints();
709
710 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07711 * @this {!Widget}
Blink Reformat4c46d092018-04-07 15:32:37712 * @suppressReceiverCheck
713 */
714 function updateForChild() {
715 const child = this.constraints();
716 constraints = constraints.addWidth(child);
717 constraints = constraints.heightToMax(child);
718 }
719
720 this._callOnVisibleChildren(updateForChild);
721 return constraints;
722 }
Tim van der Lippe0830b3d2019-10-03 13:20:07723}
Blink Reformat4c46d092018-04-07 15:32:37724
725/**
726 * @unrestricted
727 */
Tim van der Lippe0830b3d2019-10-03 13:20:07728export class VBoxWithResizeCallback extends VBox {
Blink Reformat4c46d092018-04-07 15:32:37729 /**
730 * @param {function()} resizeCallback
731 */
732 constructor(resizeCallback) {
733 super();
734 this._resizeCallback = resizeCallback;
735 }
736
737 /**
738 * @override
739 */
740 onResize() {
741 this._resizeCallback();
742 }
Tim van der Lippe0830b3d2019-10-03 13:20:07743}
Blink Reformat4c46d092018-04-07 15:32:37744
745/**
746 * @unrestricted
747 */
Tim van der Lippe0830b3d2019-10-03 13:20:07748export class WidgetFocusRestorer {
Blink Reformat4c46d092018-04-07 15:32:37749 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07750 * @param {!Widget} widget
Blink Reformat4c46d092018-04-07 15:32:37751 */
752 constructor(widget) {
753 this._widget = widget;
754 this._previous = widget.element.ownerDocument.deepActiveElement();
755 widget.focus();
756 }
757
758 restore() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34759 if (!this._widget) {
Blink Reformat4c46d092018-04-07 15:32:37760 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34761 }
762 if (this._widget.hasFocus() && this._previous) {
Blink Reformat4c46d092018-04-07 15:32:37763 this._previous.focus();
Tim van der Lippe1d6e57a2019-09-30 11:55:34764 }
Blink Reformat4c46d092018-04-07 15:32:37765 this._previous = null;
766 this._widget = null;
767 }
Tim van der Lippe0830b3d2019-10-03 13:20:07768}
Blink Reformat4c46d092018-04-07 15:32:37769
770/**
771 * @override
772 * @param {?Node} child
773 * @return {!Node}
774 * @suppress {duplicate}
775 */
776Element.prototype.appendChild = function(child) {
Tim van der Lippe0830b3d2019-10-03 13:20:07777 Widget.__assert(!child.__widget || child.parentElement === this, 'Attempt to add widget via regular DOM operation.');
778 return Widget._originalAppendChild.call(this, child);
Blink Reformat4c46d092018-04-07 15:32:37779};
780
781/**
782 * @override
783 * @param {?Node} child
784 * @param {?Node} anchor
785 * @return {!Node}
786 * @suppress {duplicate}
787 */
788Element.prototype.insertBefore = function(child, anchor) {
Tim van der Lippe0830b3d2019-10-03 13:20:07789 Widget.__assert(!child.__widget || child.parentElement === this, 'Attempt to add widget via regular DOM operation.');
790 return Widget._originalInsertBefore.call(this, child, anchor);
Blink Reformat4c46d092018-04-07 15:32:37791};
792
793/**
794 * @override
795 * @param {?Node} child
796 * @return {!Node}
797 * @suppress {duplicate}
798 */
799Element.prototype.removeChild = function(child) {
Tim van der Lippe0830b3d2019-10-03 13:20:07800 Widget.__assert(
Blink Reformat4c46d092018-04-07 15:32:37801 !child.__widgetCounter && !child.__widget,
802 'Attempt to remove element containing widget via regular DOM operation');
Tim van der Lippe0830b3d2019-10-03 13:20:07803 return Widget._originalRemoveChild.call(this, child);
Blink Reformat4c46d092018-04-07 15:32:37804};
805
806Element.prototype.removeChildren = function() {
Tim van der Lippe0830b3d2019-10-03 13:20:07807 Widget.__assert(!this.__widgetCounter, 'Attempt to remove element containing widget via regular DOM operation');
808 Widget._originalRemoveChildren.call(this);
Blink Reformat4c46d092018-04-07 15:32:37809};
Tim van der Lippe0830b3d2019-10-03 13:20:07810
811/* Legacy exported object*/
812self.UI = self.UI || {};
813
814/* Legacy exported object*/
815UI = UI || {};
816
817/** @constructor */
818UI.Widget = Widget;
819
820Widget._originalAppendChild = _originalAppendChild;
821Widget._originalInsertBefore = _originalInsertBefore;
822Widget._originalRemoveChild = _originalRemoveChild;
823Widget._originalRemoveChildren = _originalRemoveChildren;
824
825/** @constructor */
826UI.HBox = HBox;
827
828/** @constructor */
829UI.VBox = VBox;
830
831/** @constructor */
832UI.WidgetFocusRestorer = WidgetFocusRestorer;
833
834/** @constructor */
835UI.VBoxWithResizeCallback = VBoxWithResizeCallback;