blob: d9bf1b9183651e571c45f6ec76825a969d268c99 [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
Jack Franklin71519f82020-11-03 12:08:59507 * @param {!{enableLegacyPatching:boolean}} options
Blink Reformat4c46d092018-04-07 15:32:37508 */
Jack Franklin71519f82020-11-03 12:08:59509 registerRequiredCSS(cssFile, options) {
Simon Zünd10ebfaf2020-08-12 05:55:32510 if (this._isWebComponent) {
Jack Franklin71519f82020-11-03 12:08:59511 appendStyle(/** @type {!DocumentFragment} */ (this._shadowRoot), cssFile, options);
Simon Zünd10ebfaf2020-08-12 05:55:32512 } else {
Jack Franklin71519f82020-11-03 12:08:59513 appendStyle(this.element, cssFile, options);
Simon Zünd10ebfaf2020-08-12 05:55:32514 }
Blink Reformat4c46d092018-04-07 15:32:37515 }
516
517 printWidgetHierarchy() {
Simon Zünd10ebfaf2020-08-12 05:55:32518 /** @type {!Array<string>} */
Blink Reformat4c46d092018-04-07 15:32:37519 const lines = [];
520 this._collectWidgetHierarchy('', lines);
521 console.log(lines.join('\n')); // eslint-disable-line no-console
522 }
523
Simon Zünd10ebfaf2020-08-12 05:55:32524 /**
525 * @param {string} prefix
526 * @param {!Array<string>} lines
527 */
Blink Reformat4c46d092018-04-07 15:32:37528 _collectWidgetHierarchy(prefix, lines) {
529 lines.push(prefix + '[' + this.element.className + ']' + (this._children.length ? ' {' : ''));
530
Tim van der Lippe1d6e57a2019-09-30 11:55:34531 for (let i = 0; i < this._children.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37532 this._children[i]._collectWidgetHierarchy(prefix + ' ', lines);
Tim van der Lippe1d6e57a2019-09-30 11:55:34533 }
Blink Reformat4c46d092018-04-07 15:32:37534
Tim van der Lippe1d6e57a2019-09-30 11:55:34535 if (this._children.length) {
Blink Reformat4c46d092018-04-07 15:32:37536 lines.push(prefix + '}');
Tim van der Lippe1d6e57a2019-09-30 11:55:34537 }
Blink Reformat4c46d092018-04-07 15:32:37538 }
539
540 /**
541 * @param {?Element} element
542 */
543 setDefaultFocusedElement(element) {
544 this._defaultFocusedElement = element;
545 }
546
547 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07548 * @param {!Widget} child
Blink Reformat4c46d092018-04-07 15:32:37549 */
550 setDefaultFocusedChild(child) {
Tim van der Lippe0830b3d2019-10-03 13:20:07551 Widget.__assert(child._parentWidget === this, 'Attempt to set non-child widget as default focused.');
Blink Reformat4c46d092018-04-07 15:32:37552 this._defaultFocusedChild = child;
553 }
554
555 focus() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34556 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37557 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34558 }
Blink Reformat4c46d092018-04-07 15:32:37559
Simon Zünd10ebfaf2020-08-12 05:55:32560 const element = /** @type {?HTMLElement} */ (this._defaultFocusedElement);
Blink Reformat4c46d092018-04-07 15:32:37561 if (element) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34562 if (!element.hasFocus()) {
Blink Reformat4c46d092018-04-07 15:32:37563 element.focus();
Tim van der Lippe1d6e57a2019-09-30 11:55:34564 }
Blink Reformat4c46d092018-04-07 15:32:37565 return;
566 }
567
568 if (this._defaultFocusedChild && this._defaultFocusedChild._visible) {
569 this._defaultFocusedChild.focus();
570 } else {
571 for (const child of this._children) {
572 if (child._visible) {
573 child.focus();
574 return;
575 }
576 }
577 let child = this.contentElement.traverseNextNode(this.contentElement);
578 while (child) {
Paul Lewis9950e182019-12-16 16:06:07579 if (child instanceof XWidget) {
Blink Reformat4c46d092018-04-07 15:32:37580 child.focus();
581 return;
582 }
583 child = child.traverseNextNode(this.contentElement);
584 }
585 }
586 }
587
588 /**
589 * @return {boolean}
590 */
591 hasFocus() {
592 return this.element.hasFocus();
593 }
594
595 /**
Paul Lewis9950e182019-12-16 16:06:07596 * @return {!Constraints}
Blink Reformat4c46d092018-04-07 15:32:37597 */
598 calculateConstraints() {
Paul Lewis9950e182019-12-16 16:06:07599 return new Constraints();
Blink Reformat4c46d092018-04-07 15:32:37600 }
601
602 /**
Paul Lewis9950e182019-12-16 16:06:07603 * @return {!Constraints}
Blink Reformat4c46d092018-04-07 15:32:37604 */
605 constraints() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34606 if (typeof this._constraints !== 'undefined') {
Blink Reformat4c46d092018-04-07 15:32:37607 return this._constraints;
Tim van der Lippe1d6e57a2019-09-30 11:55:34608 }
609 if (typeof this._cachedConstraints === 'undefined') {
Blink Reformat4c46d092018-04-07 15:32:37610 this._cachedConstraints = this.calculateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34611 }
Blink Reformat4c46d092018-04-07 15:32:37612 return this._cachedConstraints;
613 }
614
615 /**
616 * @param {number} width
617 * @param {number} height
618 * @param {number} preferredWidth
619 * @param {number} preferredHeight
620 */
621 setMinimumAndPreferredSizes(width, height, preferredWidth, preferredHeight) {
Paul Lewis9950e182019-12-16 16:06:07622 this._constraints = new Constraints(new Size(width, height), new Size(preferredWidth, preferredHeight));
Blink Reformat4c46d092018-04-07 15:32:37623 this.invalidateConstraints();
624 }
625
626 /**
627 * @param {number} width
628 * @param {number} height
629 */
630 setMinimumSize(width, height) {
Paul Lewis9950e182019-12-16 16:06:07631 this._constraints = new Constraints(new Size(width, height));
Blink Reformat4c46d092018-04-07 15:32:37632 this.invalidateConstraints();
633 }
634
635 /**
636 * @return {boolean}
637 */
638 _hasNonZeroConstraints() {
639 const constraints = this.constraints();
640 return !!(
641 constraints.minimum.width || constraints.minimum.height || constraints.preferred.width ||
642 constraints.preferred.height);
643 }
644
645 suspendInvalidations() {
646 ++this._invalidationsSuspended;
647 }
648
649 resumeInvalidations() {
650 --this._invalidationsSuspended;
Tim van der Lippe1d6e57a2019-09-30 11:55:34651 if (!this._invalidationsSuspended && this._invalidationsRequested) {
Blink Reformat4c46d092018-04-07 15:32:37652 this.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34653 }
Blink Reformat4c46d092018-04-07 15:32:37654 }
655
656 invalidateConstraints() {
657 if (this._invalidationsSuspended) {
658 this._invalidationsRequested = true;
659 return;
660 }
661 this._invalidationsRequested = false;
662 const cached = this._cachedConstraints;
663 delete this._cachedConstraints;
664 const actual = this.constraints();
Simon Zünd10ebfaf2020-08-12 05:55:32665 if (!actual.isEqual(cached || null) && this._parentWidget) {
Blink Reformat4c46d092018-04-07 15:32:37666 this._parentWidget.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34667 } else {
Blink Reformat4c46d092018-04-07 15:32:37668 this.doLayout();
Tim van der Lippe1d6e57a2019-09-30 11:55:34669 }
Blink Reformat4c46d092018-04-07 15:32:37670 }
Olivia Flynn1d938e42019-09-23 08:13:40671
672 // Excludes the widget from being tracked by its parents/ancestors via
673 // __widgetCounter because the widget is being handled by external code.
674 // Widgets marked as being externally managed are responsible for
675 // finishing out their own lifecycle (i.e. calling detach() before being
676 // removed from the DOM). This is e.g. used for CodeMirror.
677 //
678 // Also note that this must be called before the widget is shown so that
679 // so that its ancestor's __widgetCounter is not incremented.
680 markAsExternallyManaged() {
Tim van der Lippe0830b3d2019-10-03 13:20:07681 Widget.__assert(!this._parentWidget, 'Attempt to mark widget as externally managed after insertion to the DOM');
Olivia Flynn1d938e42019-09-23 08:13:40682 this._externallyManaged = true;
683 }
Tim van der Lippe0830b3d2019-10-03 13:20:07684}
Blink Reformat4c46d092018-04-07 15:32:37685
Blink Reformat4c46d092018-04-07 15:32:37686/**
Simon Zündde14eae2020-08-12 05:49:42687 * @type {!WeakMap<!Element, !{ scrollLeft: number, scrollTop: number }>}
688 */
689const storedScrollPositions = new WeakMap();
690
691/**
Blink Reformat4c46d092018-04-07 15:32:37692 * @unrestricted
693 */
Tim van der Lippe0830b3d2019-10-03 13:20:07694export class VBox extends Widget {
Blink Reformat4c46d092018-04-07 15:32:37695 /**
696 * @param {boolean=} isWebComponent
Joel Einbinder7fbe24c2019-01-24 05:19:01697 * @param {boolean=} delegatesFocus
Blink Reformat4c46d092018-04-07 15:32:37698 */
Joel Einbinder7fbe24c2019-01-24 05:19:01699 constructor(isWebComponent, delegatesFocus) {
700 super(isWebComponent, delegatesFocus);
Blink Reformat4c46d092018-04-07 15:32:37701 this.contentElement.classList.add('vbox');
702 }
703
704 /**
705 * @override
Paul Lewis9950e182019-12-16 16:06:07706 * @return {!Constraints}
Blink Reformat4c46d092018-04-07 15:32:37707 */
708 calculateConstraints() {
Paul Lewis9950e182019-12-16 16:06:07709 let constraints = new Constraints();
Blink Reformat4c46d092018-04-07 15:32:37710
711 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07712 * @this {!Widget}
Blink Reformat4c46d092018-04-07 15:32:37713 * @suppressReceiverCheck
714 */
715 function updateForChild() {
716 const child = this.constraints();
717 constraints = constraints.widthToMax(child);
718 constraints = constraints.addHeight(child);
719 }
720
721 this._callOnVisibleChildren(updateForChild);
722 return constraints;
723 }
Tim van der Lippe0830b3d2019-10-03 13:20:07724}
Blink Reformat4c46d092018-04-07 15:32:37725
726/**
727 * @unrestricted
728 */
Tim van der Lippe0830b3d2019-10-03 13:20:07729export class HBox extends Widget {
Blink Reformat4c46d092018-04-07 15:32:37730 /**
731 * @param {boolean=} isWebComponent
732 */
733 constructor(isWebComponent) {
734 super(isWebComponent);
735 this.contentElement.classList.add('hbox');
736 }
737
738 /**
739 * @override
Paul Lewis9950e182019-12-16 16:06:07740 * @return {!Constraints}
Blink Reformat4c46d092018-04-07 15:32:37741 */
742 calculateConstraints() {
Paul Lewis9950e182019-12-16 16:06:07743 let constraints = new Constraints();
Blink Reformat4c46d092018-04-07 15:32:37744
745 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07746 * @this {!Widget}
Blink Reformat4c46d092018-04-07 15:32:37747 * @suppressReceiverCheck
748 */
749 function updateForChild() {
750 const child = this.constraints();
751 constraints = constraints.addWidth(child);
752 constraints = constraints.heightToMax(child);
753 }
754
755 this._callOnVisibleChildren(updateForChild);
756 return constraints;
757 }
Tim van der Lippe0830b3d2019-10-03 13:20:07758}
Blink Reformat4c46d092018-04-07 15:32:37759
760/**
761 * @unrestricted
762 */
Tim van der Lippe0830b3d2019-10-03 13:20:07763export class VBoxWithResizeCallback extends VBox {
Blink Reformat4c46d092018-04-07 15:32:37764 /**
Tim van der Lippe403a88d2020-05-13 11:51:32765 * @param {function():void} resizeCallback
Blink Reformat4c46d092018-04-07 15:32:37766 */
767 constructor(resizeCallback) {
768 super();
769 this._resizeCallback = resizeCallback;
770 }
771
772 /**
773 * @override
774 */
775 onResize() {
776 this._resizeCallback();
777 }
Tim van der Lippe0830b3d2019-10-03 13:20:07778}
Blink Reformat4c46d092018-04-07 15:32:37779
780/**
781 * @unrestricted
782 */
Tim van der Lippe0830b3d2019-10-03 13:20:07783export class WidgetFocusRestorer {
Blink Reformat4c46d092018-04-07 15:32:37784 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07785 * @param {!Widget} widget
Blink Reformat4c46d092018-04-07 15:32:37786 */
787 constructor(widget) {
Simon Zünd10ebfaf2020-08-12 05:55:32788 /** @type {?Widget} */
Blink Reformat4c46d092018-04-07 15:32:37789 this._widget = widget;
Simon Zünd10ebfaf2020-08-12 05:55:32790 /** @type {?HTMLElement} */
791 this._previous = /** @type {?HTMLElement} */ (widget.element.ownerDocument.deepActiveElement());
Blink Reformat4c46d092018-04-07 15:32:37792 widget.focus();
793 }
794
795 restore() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34796 if (!this._widget) {
Blink Reformat4c46d092018-04-07 15:32:37797 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34798 }
799 if (this._widget.hasFocus() && this._previous) {
Blink Reformat4c46d092018-04-07 15:32:37800 this._previous.focus();
Tim van der Lippe1d6e57a2019-09-30 11:55:34801 }
Blink Reformat4c46d092018-04-07 15:32:37802 this._previous = null;
803 this._widget = null;
804 }
Tim van der Lippe0830b3d2019-10-03 13:20:07805}
Simon Zünd10ebfaf2020-08-12 05:55:32806
807/**
808 * @param {!WidgetElement} element
809 * @return {?WidgetElement}
810 */
811function parentWidgetElementOrShadowHost(element) {
812 return /** @type {?WidgetElement} */ (element.parentElementOrShadowHost());
813}