blob: d521c948beb748aa10f72d42c09bf6af71fd8427 [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
Paul Lewis17e384e2020-01-08 15:46:5127import * as Common from '../common/common.js';
Simon Zünd1b21f292020-08-12 05:38:3628import * as DOMExtension from '../dom_extension/dom_extension.js';
Paul Lewis9950e182019-12-16 16:06:0729import {Constraints, Size} from './Geometry.js';
30import {appendStyle} from './utils/append-style.js';
31import {createShadowRootWithCoreStyles} from './utils/create-shadow-root-with-core-styles.js';
32import {XWidget} from './XWidget.js';
33
Simon Zünd10ebfaf2020-08-12 05:55:3234export class WidgetElement extends HTMLDivElement { // eslint-disable-line no-unused-vars
35 constructor() {
36 super();
37 /** @type {?Widget} */
38 this.__widget;
39
40 /** @type {?number} */
41 this.__widgetCounter;
42 }
43}
44
Blink Reformat4c46d092018-04-07 15:32:3745/**
46 * @unrestricted
47 */
Paul Lewis17e384e2020-01-08 15:46:5148export class Widget extends Common.ObjectWrapper.ObjectWrapper {
Blink Reformat4c46d092018-04-07 15:32:3749 /**
50 * @param {boolean=} isWebComponent
Joel Einbinder7fbe24c2019-01-24 05:19:0151 * @param {boolean=} delegatesFocus
Blink Reformat4c46d092018-04-07 15:32:3752 */
Joel Einbinder7fbe24c2019-01-24 05:19:0153 constructor(isWebComponent, delegatesFocus) {
Blink Reformat4c46d092018-04-07 15:32:3754 super();
Simon Zünd10ebfaf2020-08-12 05:55:3255 /** @type {!WidgetElement} */
56 this.element;
Tim van der Lippef49e2322020-05-01 15:03:0957 this.contentElement = document.createElement('div');
58 this.contentElement.classList.add('widget');
Blink Reformat4c46d092018-04-07 15:32:3759 if (isWebComponent) {
Simon Zünd10ebfaf2020-08-12 05:55:3260 this.element = /** @type {!WidgetElement} */ (document.createElement('div'));
Tim van der Lippee7f27052020-05-01 15:15:2861 this.element.classList.add('vbox');
62 this.element.classList.add('flex-auto');
Paul Lewis9950e182019-12-16 16:06:0763 this._shadowRoot = createShadowRootWithCoreStyles(this.element, undefined, delegatesFocus);
Blink Reformat4c46d092018-04-07 15:32:3764 this._shadowRoot.appendChild(this.contentElement);
65 } else {
Simon Zünd10ebfaf2020-08-12 05:55:3266 this.element = /** @type {!WidgetElement} */ (this.contentElement);
Blink Reformat4c46d092018-04-07 15:32:3767 }
68 this._isWebComponent = isWebComponent;
69 this.element.__widget = this;
70 this._visible = false;
71 this._isRoot = false;
72 this._isShowing = false;
Simon Zünd10ebfaf2020-08-12 05:55:3273 /** @type {!Array<!Widget>} */
Blink Reformat4c46d092018-04-07 15:32:3774 this._children = [];
75 this._hideOnDetach = false;
76 this._notificationDepth = 0;
77 this._invalidationsSuspended = 0;
78 this._defaultFocusedChild = null;
Simon Zünd10ebfaf2020-08-12 05:55:3279 /** @type {?Widget} */
80 this._parentWidget = null;
Blink Reformat4c46d092018-04-07 15:32:3781 }
82
Simon Zünd10ebfaf2020-08-12 05:55:3283 /**
84 * @param {!WidgetElement} parentElement
85 * @param {!WidgetElement} childElement
86 */
Blink Reformat4c46d092018-04-07 15:32:3787 static _incrementWidgetCounter(parentElement, childElement) {
88 const count = (childElement.__widgetCounter || 0) + (childElement.__widget ? 1 : 0);
Tim van der Lippe1d6e57a2019-09-30 11:55:3489 if (!count) {
Blink Reformat4c46d092018-04-07 15:32:3790 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:3491 }
Blink Reformat4c46d092018-04-07 15:32:3792
Simon Zünd10ebfaf2020-08-12 05:55:3293 /** @type {?WidgetElement} */
94 let currentElement = parentElement;
95 while (currentElement) {
96 currentElement.__widgetCounter = (currentElement.__widgetCounter || 0) + count;
97 currentElement = parentWidgetElementOrShadowHost(currentElement);
Blink Reformat4c46d092018-04-07 15:32:3798 }
99 }
100
Simon Zünd10ebfaf2020-08-12 05:55:32101 /**
102 * @param {!WidgetElement} parentElement
103 * @param {!WidgetElement} childElement
104 */
Blink Reformat4c46d092018-04-07 15:32:37105 static _decrementWidgetCounter(parentElement, childElement) {
106 const count = (childElement.__widgetCounter || 0) + (childElement.__widget ? 1 : 0);
Tim van der Lippe1d6e57a2019-09-30 11:55:34107 if (!count) {
Blink Reformat4c46d092018-04-07 15:32:37108 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34109 }
Blink Reformat4c46d092018-04-07 15:32:37110
Simon Zünd10ebfaf2020-08-12 05:55:32111 /** @type {?WidgetElement} */
112 let currentElement = parentElement;
113 while (currentElement) {
114 if (currentElement.__widgetCounter) {
115 currentElement.__widgetCounter -= count;
116 }
117 currentElement = parentWidgetElementOrShadowHost(currentElement);
Blink Reformat4c46d092018-04-07 15:32:37118 }
119 }
120
Simon Zünd10ebfaf2020-08-12 05:55:32121 /**
122 * @param {*} condition
123 * @param {string} message
124 */
Blink Reformat4c46d092018-04-07 15:32:37125 static __assert(condition, message) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34126 if (!condition) {
Blink Reformat4c46d092018-04-07 15:32:37127 throw new Error(message);
Tim van der Lippe1d6e57a2019-09-30 11:55:34128 }
Blink Reformat4c46d092018-04-07 15:32:37129 }
130
Blink Reformat4c46d092018-04-07 15:32:37131 markAsRoot() {
Tim van der Lippe0830b3d2019-10-03 13:20:07132 Widget.__assert(!this.element.parentElement, 'Attempt to mark as root attached node');
Blink Reformat4c46d092018-04-07 15:32:37133 this._isRoot = true;
134 }
135
136 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07137 * @return {?Widget}
Blink Reformat4c46d092018-04-07 15:32:37138 */
139 parentWidget() {
140 return this._parentWidget;
141 }
142
143 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07144 * @return {!Array.<!Widget>}
Blink Reformat4c46d092018-04-07 15:32:37145 */
146 children() {
147 return this._children;
148 }
149
150 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07151 * @param {!Widget} widget
Blink Reformat4c46d092018-04-07 15:32:37152 * @protected
153 */
154 childWasDetached(widget) {
155 }
156
157 /**
158 * @return {boolean}
159 */
160 isShowing() {
161 return this._isShowing;
162 }
163
164 /**
165 * @return {boolean}
166 */
167 shouldHideOnDetach() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34168 if (!this.element.parentElement) {
Blink Reformat4c46d092018-04-07 15:32:37169 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34170 }
171 if (this._hideOnDetach) {
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 for (const child of this._children) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34175 if (child.shouldHideOnDetach()) {
Blink Reformat4c46d092018-04-07 15:32:37176 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34177 }
Blink Reformat4c46d092018-04-07 15:32:37178 }
179 return false;
180 }
181
182 setHideOnDetach() {
183 this._hideOnDetach = true;
184 }
185
186 /**
187 * @return {boolean}
188 */
189 _inNotification() {
Simon Zünd10ebfaf2020-08-12 05:55:32190 return !!this._notificationDepth || !!(this._parentWidget && this._parentWidget._inNotification());
Blink Reformat4c46d092018-04-07 15:32:37191 }
192
193 _parentIsShowing() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34194 if (this._isRoot) {
Blink Reformat4c46d092018-04-07 15:32:37195 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34196 }
Blink Reformat4c46d092018-04-07 15:32:37197 return !!this._parentWidget && this._parentWidget.isShowing();
198 }
199
200 /**
Tim van der Lippe403a88d2020-05-13 11:51:32201 * @param {function(this:Widget):void} method
Blink Reformat4c46d092018-04-07 15:32:37202 */
203 _callOnVisibleChildren(method) {
204 const copy = this._children.slice();
205 for (let i = 0; i < copy.length; ++i) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34206 if (copy[i]._parentWidget === this && copy[i]._visible) {
Blink Reformat4c46d092018-04-07 15:32:37207 method.call(copy[i]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34208 }
Blink Reformat4c46d092018-04-07 15:32:37209 }
210 }
211
212 _processWillShow() {
213 this._callOnVisibleChildren(this._processWillShow);
214 this._isShowing = true;
215 }
216
217 _processWasShown() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34218 if (this._inNotification()) {
Blink Reformat4c46d092018-04-07 15:32:37219 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34220 }
Blink Reformat4c46d092018-04-07 15:32:37221 this.restoreScrollPositions();
222 this._notify(this.wasShown);
223 this._callOnVisibleChildren(this._processWasShown);
224 }
225
226 _processWillHide() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34227 if (this._inNotification()) {
Blink Reformat4c46d092018-04-07 15:32:37228 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34229 }
Blink Reformat4c46d092018-04-07 15:32:37230 this.storeScrollPositions();
231
232 this._callOnVisibleChildren(this._processWillHide);
233 this._notify(this.willHide);
234 this._isShowing = false;
235 }
236
237 _processWasHidden() {
238 this._callOnVisibleChildren(this._processWasHidden);
239 }
240
241 _processOnResize() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34242 if (this._inNotification()) {
Blink Reformat4c46d092018-04-07 15:32:37243 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34244 }
245 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37246 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34247 }
Blink Reformat4c46d092018-04-07 15:32:37248 this._notify(this.onResize);
249 this._callOnVisibleChildren(this._processOnResize);
250 }
251
252 /**
Tim van der Lippe403a88d2020-05-13 11:51:32253 * @param {function(this:Widget):void} notification
Blink Reformat4c46d092018-04-07 15:32:37254 */
255 _notify(notification) {
256 ++this._notificationDepth;
257 try {
258 notification.call(this);
259 } finally {
260 --this._notificationDepth;
261 }
262 }
263
264 wasShown() {
265 }
266
267 willHide() {
268 }
269
270 onResize() {
271 }
272
273 onLayout() {
274 }
275
276 ownerViewDisposed() {
277 }
278
279 /**
280 * @param {!Element} parentElement
281 * @param {?Node=} insertBefore
282 */
283 show(parentElement, insertBefore) {
Tim van der Lippe0830b3d2019-10-03 13:20:07284 Widget.__assert(parentElement, 'Attempt to attach widget with no parent element');
Blink Reformat4c46d092018-04-07 15:32:37285
286 if (!this._isRoot) {
287 // Update widget hierarchy.
Simon Zünd10ebfaf2020-08-12 05:55:32288 /** @type {?WidgetElement} */
289 let currentParent = /** @type {?WidgetElement} */ (parentElement);
Tim van der Lippe1d6e57a2019-09-30 11:55:34290 while (currentParent && !currentParent.__widget) {
Simon Zünd10ebfaf2020-08-12 05:55:32291 currentParent = parentWidgetElementOrShadowHost(currentParent);
Tim van der Lippe1d6e57a2019-09-30 11:55:34292 }
Simon Zünd10ebfaf2020-08-12 05:55:32293 if (!currentParent || !currentParent.__widget) {
294 throw new Error('Attempt to attach widget to orphan node');
295 }
Blink Reformat4c46d092018-04-07 15:32:37296 this._attach(currentParent.__widget);
297 }
298
Simon Zünd10ebfaf2020-08-12 05:55:32299 this._showWidget(/** @type {!WidgetElement} */ (parentElement), insertBefore);
Blink Reformat4c46d092018-04-07 15:32:37300 }
301
302 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07303 * @param {!Widget} parentWidget
Blink Reformat4c46d092018-04-07 15:32:37304 */
305 _attach(parentWidget) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34306 if (parentWidget === this._parentWidget) {
Blink Reformat4c46d092018-04-07 15:32:37307 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34308 }
309 if (this._parentWidget) {
Blink Reformat4c46d092018-04-07 15:32:37310 this.detach();
Tim van der Lippe1d6e57a2019-09-30 11:55:34311 }
Simon Zünd10ebfaf2020-08-12 05:55:32312 /** @type {?Widget} */
Blink Reformat4c46d092018-04-07 15:32:37313 this._parentWidget = parentWidget;
314 this._parentWidget._children.push(this);
315 this._isRoot = false;
316 }
317
318 showWidget() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34319 if (this._visible) {
Blink Reformat4c46d092018-04-07 15:32:37320 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34321 }
Simon Zünd10ebfaf2020-08-12 05:55:32322 if (!this.element.parentElement) {
323 throw new Error('Attempt to show widget that is not hidden using hideWidget().');
324 }
325 this._showWidget(/** @type {!WidgetElement} */ (this.element.parentElement), this.element.nextSibling);
Blink Reformat4c46d092018-04-07 15:32:37326 }
327
328 /**
Simon Zünd10ebfaf2020-08-12 05:55:32329 * @param {!WidgetElement} parentElement
Blink Reformat4c46d092018-04-07 15:32:37330 * @param {?Node=} insertBefore
331 */
332 _showWidget(parentElement, insertBefore) {
Simon Zünd10ebfaf2020-08-12 05:55:32333 /** @type {?WidgetElement} */
Blink Reformat4c46d092018-04-07 15:32:37334 let currentParent = parentElement;
Tim van der Lippe1d6e57a2019-09-30 11:55:34335 while (currentParent && !currentParent.__widget) {
Simon Zünd10ebfaf2020-08-12 05:55:32336 currentParent = parentWidgetElementOrShadowHost(currentParent);
Tim van der Lippe1d6e57a2019-09-30 11:55:34337 }
Blink Reformat4c46d092018-04-07 15:32:37338
339 if (this._isRoot) {
Tim van der Lippe0830b3d2019-10-03 13:20:07340 Widget.__assert(!currentParent, 'Attempt to show root widget under another widget');
Blink Reformat4c46d092018-04-07 15:32:37341 } else {
Tim van der Lippe0830b3d2019-10-03 13:20:07342 Widget.__assert(
Blink Reformat4c46d092018-04-07 15:32:37343 currentParent && currentParent.__widget === this._parentWidget,
344 'Attempt to show under node belonging to alien widget');
345 }
346
347 const wasVisible = this._visible;
Tim van der Lippe1d6e57a2019-09-30 11:55:34348 if (wasVisible && this.element.parentElement === parentElement) {
Blink Reformat4c46d092018-04-07 15:32:37349 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34350 }
Blink Reformat4c46d092018-04-07 15:32:37351
352 this._visible = true;
353
Tim van der Lippe1d6e57a2019-09-30 11:55:34354 if (!wasVisible && this._parentIsShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37355 this._processWillShow();
Tim van der Lippe1d6e57a2019-09-30 11:55:34356 }
Blink Reformat4c46d092018-04-07 15:32:37357
358 this.element.classList.remove('hidden');
359
360 // Reparent
361 if (this.element.parentElement !== parentElement) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34362 if (!this._externallyManaged) {
Tim van der Lippe0830b3d2019-10-03 13:20:07363 Widget._incrementWidgetCounter(parentElement, this.element);
Tim van der Lippe1d6e57a2019-09-30 11:55:34364 }
365 if (insertBefore) {
Simon Zünd1b21f292020-08-12 05:38:36366 DOMExtension.DOMExtension.originalInsertBefore.call(parentElement, this.element, insertBefore);
Tim van der Lippe1d6e57a2019-09-30 11:55:34367 } else {
Simon Zünd1b21f292020-08-12 05:38:36368 DOMExtension.DOMExtension.originalAppendChild.call(parentElement, this.element);
Tim van der Lippe1d6e57a2019-09-30 11:55:34369 }
Blink Reformat4c46d092018-04-07 15:32:37370 }
371
Tim van der Lippe1d6e57a2019-09-30 11:55:34372 if (!wasVisible && this._parentIsShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37373 this._processWasShown();
Tim van der Lippe1d6e57a2019-09-30 11:55:34374 }
Blink Reformat4c46d092018-04-07 15:32:37375
Tim van der Lippe1d6e57a2019-09-30 11:55:34376 if (this._parentWidget && this._hasNonZeroConstraints()) {
Blink Reformat4c46d092018-04-07 15:32:37377 this._parentWidget.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34378 } else {
Blink Reformat4c46d092018-04-07 15:32:37379 this._processOnResize();
Tim van der Lippe1d6e57a2019-09-30 11:55:34380 }
Blink Reformat4c46d092018-04-07 15:32:37381 }
382
383 hideWidget() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34384 if (!this._visible) {
Blink Reformat4c46d092018-04-07 15:32:37385 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34386 }
Blink Reformat4c46d092018-04-07 15:32:37387 this._hideWidget(false);
388 }
389
390 /**
391 * @param {boolean} removeFromDOM
392 */
393 _hideWidget(removeFromDOM) {
394 this._visible = false;
Simon Zünd10ebfaf2020-08-12 05:55:32395 const parentElement = /** @type {!WidgetElement} */ (this.element.parentElement);
Blink Reformat4c46d092018-04-07 15:32:37396
Tim van der Lippe1d6e57a2019-09-30 11:55:34397 if (this._parentIsShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37398 this._processWillHide();
Tim van der Lippe1d6e57a2019-09-30 11:55:34399 }
Blink Reformat4c46d092018-04-07 15:32:37400
401 if (removeFromDOM) {
402 // Force legal removal
Tim van der Lippe0830b3d2019-10-03 13:20:07403 Widget._decrementWidgetCounter(parentElement, this.element);
Simon Zünd1b21f292020-08-12 05:38:36404 DOMExtension.DOMExtension.originalRemoveChild.call(parentElement, this.element);
Blink Reformat4c46d092018-04-07 15:32:37405 } else {
406 this.element.classList.add('hidden');
407 }
408
Tim van der Lippe1d6e57a2019-09-30 11:55:34409 if (this._parentIsShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37410 this._processWasHidden();
Tim van der Lippe1d6e57a2019-09-30 11:55:34411 }
412 if (this._parentWidget && this._hasNonZeroConstraints()) {
Blink Reformat4c46d092018-04-07 15:32:37413 this._parentWidget.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34414 }
Blink Reformat4c46d092018-04-07 15:32:37415 }
416
417 /**
Changhao Han0b38e242020-06-08 14:09:06418 * @param {boolean=} overrideHideOnDetach remove element from DOM instead of hiding
Blink Reformat4c46d092018-04-07 15:32:37419 */
420 detach(overrideHideOnDetach) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34421 if (!this._parentWidget && !this._isRoot) {
Blink Reformat4c46d092018-04-07 15:32:37422 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34423 }
Blink Reformat4c46d092018-04-07 15:32:37424
425 // hideOnDetach means that we should never remove element from dom - content
426 // has iframes and detaching it will hurt.
427 //
428 // overrideHideOnDetach will override hideOnDetach and the client takes
429 // responsibility for the consequences.
430 const removeFromDOM = overrideHideOnDetach || !this.shouldHideOnDetach();
431 if (this._visible) {
432 this._hideWidget(removeFromDOM);
433 } else if (removeFromDOM && this.element.parentElement) {
Simon Zünd10ebfaf2020-08-12 05:55:32434 const parentElement = /** @type {!WidgetElement} */ (this.element.parentElement);
Blink Reformat4c46d092018-04-07 15:32:37435 // Force kick out from DOM.
Tim van der Lippe0830b3d2019-10-03 13:20:07436 Widget._decrementWidgetCounter(parentElement, this.element);
Simon Zünd1b21f292020-08-12 05:38:36437 DOMExtension.DOMExtension.originalRemoveChild.call(parentElement, this.element);
Blink Reformat4c46d092018-04-07 15:32:37438 }
439
440 // Update widget hierarchy.
441 if (this._parentWidget) {
442 const childIndex = this._parentWidget._children.indexOf(this);
Tim van der Lippe0830b3d2019-10-03 13:20:07443 Widget.__assert(childIndex >= 0, 'Attempt to remove non-child widget');
Blink Reformat4c46d092018-04-07 15:32:37444 this._parentWidget._children.splice(childIndex, 1);
Tim van der Lippe1d6e57a2019-09-30 11:55:34445 if (this._parentWidget._defaultFocusedChild === this) {
Blink Reformat4c46d092018-04-07 15:32:37446 this._parentWidget._defaultFocusedChild = null;
Tim van der Lippe1d6e57a2019-09-30 11:55:34447 }
Blink Reformat4c46d092018-04-07 15:32:37448 this._parentWidget.childWasDetached(this);
449 this._parentWidget = null;
450 } else {
Tim van der Lippe0830b3d2019-10-03 13:20:07451 Widget.__assert(this._isRoot, 'Removing non-root widget from DOM');
Blink Reformat4c46d092018-04-07 15:32:37452 }
453 }
454
455 detachChildWidgets() {
456 const children = this._children.slice();
Tim van der Lippe1d6e57a2019-09-30 11:55:34457 for (let i = 0; i < children.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37458 children[i].detach();
Tim van der Lippe1d6e57a2019-09-30 11:55:34459 }
Blink Reformat4c46d092018-04-07 15:32:37460 }
461
462 /**
463 * @return {!Array.<!Element>}
464 */
465 elementsToRestoreScrollPositionsFor() {
466 return [this.element];
467 }
468
469 storeScrollPositions() {
470 const elements = this.elementsToRestoreScrollPositionsFor();
Simon Zündde14eae2020-08-12 05:49:42471 for (const container of elements) {
472 storedScrollPositions.set(container, {scrollLeft: container.scrollLeft, scrollTop: container.scrollTop});
Blink Reformat4c46d092018-04-07 15:32:37473 }
474 }
475
476 restoreScrollPositions() {
477 const elements = this.elementsToRestoreScrollPositionsFor();
Simon Zündde14eae2020-08-12 05:49:42478 for (const container of elements) {
479 const storedPositions = storedScrollPositions.get(container);
480 if (storedPositions) {
481 container.scrollLeft = storedPositions.scrollLeft;
482 container.scrollTop = storedPositions.scrollTop;
Tim van der Lippe1d6e57a2019-09-30 11:55:34483 }
Blink Reformat4c46d092018-04-07 15:32:37484 }
485 }
486
487 doResize() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34488 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37489 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34490 }
Blink Reformat4c46d092018-04-07 15:32:37491 // No matter what notification we are in, dispatching onResize is not needed.
Tim van der Lippe1d6e57a2019-09-30 11:55:34492 if (!this._inNotification()) {
Blink Reformat4c46d092018-04-07 15:32:37493 this._callOnVisibleChildren(this._processOnResize);
Tim van der Lippe1d6e57a2019-09-30 11:55:34494 }
Blink Reformat4c46d092018-04-07 15:32:37495 }
496
497 doLayout() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34498 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37499 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34500 }
Blink Reformat4c46d092018-04-07 15:32:37501 this._notify(this.onLayout);
502 this.doResize();
503 }
504
505 /**
506 * @param {string} cssFile
507 */
508 registerRequiredCSS(cssFile) {
Simon Zünd10ebfaf2020-08-12 05:55:32509 if (this._isWebComponent) {
510 appendStyle(/** @type {!DocumentFragment} */ (this._shadowRoot), cssFile);
511 } else {
512 appendStyle(this.element, cssFile);
513 }
Blink Reformat4c46d092018-04-07 15:32:37514 }
515
516 printWidgetHierarchy() {
Simon Zünd10ebfaf2020-08-12 05:55:32517 /** @type {!Array<string>} */
Blink Reformat4c46d092018-04-07 15:32:37518 const lines = [];
519 this._collectWidgetHierarchy('', lines);
520 console.log(lines.join('\n')); // eslint-disable-line no-console
521 }
522
Simon Zünd10ebfaf2020-08-12 05:55:32523 /**
524 * @param {string} prefix
525 * @param {!Array<string>} lines
526 */
Blink Reformat4c46d092018-04-07 15:32:37527 _collectWidgetHierarchy(prefix, lines) {
528 lines.push(prefix + '[' + this.element.className + ']' + (this._children.length ? ' {' : ''));
529
Tim van der Lippe1d6e57a2019-09-30 11:55:34530 for (let i = 0; i < this._children.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37531 this._children[i]._collectWidgetHierarchy(prefix + ' ', lines);
Tim van der Lippe1d6e57a2019-09-30 11:55:34532 }
Blink Reformat4c46d092018-04-07 15:32:37533
Tim van der Lippe1d6e57a2019-09-30 11:55:34534 if (this._children.length) {
Blink Reformat4c46d092018-04-07 15:32:37535 lines.push(prefix + '}');
Tim van der Lippe1d6e57a2019-09-30 11:55:34536 }
Blink Reformat4c46d092018-04-07 15:32:37537 }
538
539 /**
540 * @param {?Element} element
541 */
542 setDefaultFocusedElement(element) {
543 this._defaultFocusedElement = element;
544 }
545
546 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07547 * @param {!Widget} child
Blink Reformat4c46d092018-04-07 15:32:37548 */
549 setDefaultFocusedChild(child) {
Tim van der Lippe0830b3d2019-10-03 13:20:07550 Widget.__assert(child._parentWidget === this, 'Attempt to set non-child widget as default focused.');
Blink Reformat4c46d092018-04-07 15:32:37551 this._defaultFocusedChild = child;
552 }
553
554 focus() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34555 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37556 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34557 }
Blink Reformat4c46d092018-04-07 15:32:37558
Simon Zünd10ebfaf2020-08-12 05:55:32559 const element = /** @type {?HTMLElement} */ (this._defaultFocusedElement);
Blink Reformat4c46d092018-04-07 15:32:37560 if (element) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34561 if (!element.hasFocus()) {
Blink Reformat4c46d092018-04-07 15:32:37562 element.focus();
Tim van der Lippe1d6e57a2019-09-30 11:55:34563 }
Blink Reformat4c46d092018-04-07 15:32:37564 return;
565 }
566
567 if (this._defaultFocusedChild && this._defaultFocusedChild._visible) {
568 this._defaultFocusedChild.focus();
569 } else {
570 for (const child of this._children) {
571 if (child._visible) {
572 child.focus();
573 return;
574 }
575 }
576 let child = this.contentElement.traverseNextNode(this.contentElement);
577 while (child) {
Paul Lewis9950e182019-12-16 16:06:07578 if (child instanceof XWidget) {
Blink Reformat4c46d092018-04-07 15:32:37579 child.focus();
580 return;
581 }
582 child = child.traverseNextNode(this.contentElement);
583 }
584 }
585 }
586
587 /**
588 * @return {boolean}
589 */
590 hasFocus() {
591 return this.element.hasFocus();
592 }
593
594 /**
Paul Lewis9950e182019-12-16 16:06:07595 * @return {!Constraints}
Blink Reformat4c46d092018-04-07 15:32:37596 */
597 calculateConstraints() {
Paul Lewis9950e182019-12-16 16:06:07598 return new Constraints();
Blink Reformat4c46d092018-04-07 15:32:37599 }
600
601 /**
Paul Lewis9950e182019-12-16 16:06:07602 * @return {!Constraints}
Blink Reformat4c46d092018-04-07 15:32:37603 */
604 constraints() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34605 if (typeof this._constraints !== 'undefined') {
Blink Reformat4c46d092018-04-07 15:32:37606 return this._constraints;
Tim van der Lippe1d6e57a2019-09-30 11:55:34607 }
608 if (typeof this._cachedConstraints === 'undefined') {
Blink Reformat4c46d092018-04-07 15:32:37609 this._cachedConstraints = this.calculateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34610 }
Blink Reformat4c46d092018-04-07 15:32:37611 return this._cachedConstraints;
612 }
613
614 /**
615 * @param {number} width
616 * @param {number} height
617 * @param {number} preferredWidth
618 * @param {number} preferredHeight
619 */
620 setMinimumAndPreferredSizes(width, height, preferredWidth, preferredHeight) {
Paul Lewis9950e182019-12-16 16:06:07621 this._constraints = new Constraints(new Size(width, height), new Size(preferredWidth, preferredHeight));
Blink Reformat4c46d092018-04-07 15:32:37622 this.invalidateConstraints();
623 }
624
625 /**
626 * @param {number} width
627 * @param {number} height
628 */
629 setMinimumSize(width, height) {
Paul Lewis9950e182019-12-16 16:06:07630 this._constraints = new Constraints(new Size(width, height));
Blink Reformat4c46d092018-04-07 15:32:37631 this.invalidateConstraints();
632 }
633
634 /**
635 * @return {boolean}
636 */
637 _hasNonZeroConstraints() {
638 const constraints = this.constraints();
639 return !!(
640 constraints.minimum.width || constraints.minimum.height || constraints.preferred.width ||
641 constraints.preferred.height);
642 }
643
644 suspendInvalidations() {
645 ++this._invalidationsSuspended;
646 }
647
648 resumeInvalidations() {
649 --this._invalidationsSuspended;
Tim van der Lippe1d6e57a2019-09-30 11:55:34650 if (!this._invalidationsSuspended && this._invalidationsRequested) {
Blink Reformat4c46d092018-04-07 15:32:37651 this.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34652 }
Blink Reformat4c46d092018-04-07 15:32:37653 }
654
655 invalidateConstraints() {
656 if (this._invalidationsSuspended) {
657 this._invalidationsRequested = true;
658 return;
659 }
660 this._invalidationsRequested = false;
661 const cached = this._cachedConstraints;
662 delete this._cachedConstraints;
663 const actual = this.constraints();
Simon Zünd10ebfaf2020-08-12 05:55:32664 if (!actual.isEqual(cached || null) && this._parentWidget) {
Blink Reformat4c46d092018-04-07 15:32:37665 this._parentWidget.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34666 } else {
Blink Reformat4c46d092018-04-07 15:32:37667 this.doLayout();
Tim van der Lippe1d6e57a2019-09-30 11:55:34668 }
Blink Reformat4c46d092018-04-07 15:32:37669 }
Olivia Flynn1d938e42019-09-23 08:13:40670
671 // Excludes the widget from being tracked by its parents/ancestors via
672 // __widgetCounter because the widget is being handled by external code.
673 // Widgets marked as being externally managed are responsible for
674 // finishing out their own lifecycle (i.e. calling detach() before being
675 // removed from the DOM). This is e.g. used for CodeMirror.
676 //
677 // Also note that this must be called before the widget is shown so that
678 // so that its ancestor's __widgetCounter is not incremented.
679 markAsExternallyManaged() {
Tim van der Lippe0830b3d2019-10-03 13:20:07680 Widget.__assert(!this._parentWidget, 'Attempt to mark widget as externally managed after insertion to the DOM');
Olivia Flynn1d938e42019-09-23 08:13:40681 this._externallyManaged = true;
682 }
Tim van der Lippe0830b3d2019-10-03 13:20:07683}
Blink Reformat4c46d092018-04-07 15:32:37684
Blink Reformat4c46d092018-04-07 15:32:37685/**
Simon Zündde14eae2020-08-12 05:49:42686 * @type {!WeakMap<!Element, !{ scrollLeft: number, scrollTop: number }>}
687 */
688const storedScrollPositions = new WeakMap();
689
690/**
Blink Reformat4c46d092018-04-07 15:32:37691 * @unrestricted
692 */
Tim van der Lippe0830b3d2019-10-03 13:20:07693export class VBox extends Widget {
Blink Reformat4c46d092018-04-07 15:32:37694 /**
695 * @param {boolean=} isWebComponent
Joel Einbinder7fbe24c2019-01-24 05:19:01696 * @param {boolean=} delegatesFocus
Blink Reformat4c46d092018-04-07 15:32:37697 */
Joel Einbinder7fbe24c2019-01-24 05:19:01698 constructor(isWebComponent, delegatesFocus) {
699 super(isWebComponent, delegatesFocus);
Blink Reformat4c46d092018-04-07 15:32:37700 this.contentElement.classList.add('vbox');
701 }
702
703 /**
704 * @override
Paul Lewis9950e182019-12-16 16:06:07705 * @return {!Constraints}
Blink Reformat4c46d092018-04-07 15:32:37706 */
707 calculateConstraints() {
Paul Lewis9950e182019-12-16 16:06:07708 let constraints = new Constraints();
Blink Reformat4c46d092018-04-07 15:32:37709
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.widthToMax(child);
717 constraints = constraints.addHeight(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 HBox extends Widget {
Blink Reformat4c46d092018-04-07 15:32:37729 /**
730 * @param {boolean=} isWebComponent
731 */
732 constructor(isWebComponent) {
733 super(isWebComponent);
734 this.contentElement.classList.add('hbox');
735 }
736
737 /**
738 * @override
Paul Lewis9950e182019-12-16 16:06:07739 * @return {!Constraints}
Blink Reformat4c46d092018-04-07 15:32:37740 */
741 calculateConstraints() {
Paul Lewis9950e182019-12-16 16:06:07742 let constraints = new Constraints();
Blink Reformat4c46d092018-04-07 15:32:37743
744 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07745 * @this {!Widget}
Blink Reformat4c46d092018-04-07 15:32:37746 * @suppressReceiverCheck
747 */
748 function updateForChild() {
749 const child = this.constraints();
750 constraints = constraints.addWidth(child);
751 constraints = constraints.heightToMax(child);
752 }
753
754 this._callOnVisibleChildren(updateForChild);
755 return constraints;
756 }
Tim van der Lippe0830b3d2019-10-03 13:20:07757}
Blink Reformat4c46d092018-04-07 15:32:37758
759/**
760 * @unrestricted
761 */
Tim van der Lippe0830b3d2019-10-03 13:20:07762export class VBoxWithResizeCallback extends VBox {
Blink Reformat4c46d092018-04-07 15:32:37763 /**
Tim van der Lippe403a88d2020-05-13 11:51:32764 * @param {function():void} resizeCallback
Blink Reformat4c46d092018-04-07 15:32:37765 */
766 constructor(resizeCallback) {
767 super();
768 this._resizeCallback = resizeCallback;
769 }
770
771 /**
772 * @override
773 */
774 onResize() {
775 this._resizeCallback();
776 }
Tim van der Lippe0830b3d2019-10-03 13:20:07777}
Blink Reformat4c46d092018-04-07 15:32:37778
779/**
780 * @unrestricted
781 */
Tim van der Lippe0830b3d2019-10-03 13:20:07782export class WidgetFocusRestorer {
Blink Reformat4c46d092018-04-07 15:32:37783 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07784 * @param {!Widget} widget
Blink Reformat4c46d092018-04-07 15:32:37785 */
786 constructor(widget) {
Simon Zünd10ebfaf2020-08-12 05:55:32787 /** @type {?Widget} */
Blink Reformat4c46d092018-04-07 15:32:37788 this._widget = widget;
Simon Zünd10ebfaf2020-08-12 05:55:32789 /** @type {?HTMLElement} */
790 this._previous = /** @type {?HTMLElement} */ (widget.element.ownerDocument.deepActiveElement());
Blink Reformat4c46d092018-04-07 15:32:37791 widget.focus();
792 }
793
794 restore() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34795 if (!this._widget) {
Blink Reformat4c46d092018-04-07 15:32:37796 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34797 }
798 if (this._widget.hasFocus() && this._previous) {
Blink Reformat4c46d092018-04-07 15:32:37799 this._previous.focus();
Tim van der Lippe1d6e57a2019-09-30 11:55:34800 }
Blink Reformat4c46d092018-04-07 15:32:37801 this._previous = null;
802 this._widget = null;
803 }
Tim van der Lippe0830b3d2019-10-03 13:20:07804}
Simon Zünd10ebfaf2020-08-12 05:55:32805
806/**
807 * @param {!WidgetElement} element
808 * @return {?WidgetElement}
809 */
810function parentWidgetElementOrShadowHost(element) {
811 return /** @type {?WidgetElement} */ (element.parentElementOrShadowHost());
812}