blob: fda6be3a71219ba50b6c343cee06863708924263 [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';
Paul Lewis9950e182019-12-16 16:06:0728import {Constraints, Size} from './Geometry.js';
29import {appendStyle} from './utils/append-style.js';
30import {createShadowRootWithCoreStyles} from './utils/create-shadow-root-with-core-styles.js';
31import {XWidget} from './XWidget.js';
32
Blink Reformat4c46d092018-04-07 15:32:3733/**
34 * @unrestricted
35 */
Paul Lewis17e384e2020-01-08 15:46:5136export class Widget extends Common.ObjectWrapper.ObjectWrapper {
Blink Reformat4c46d092018-04-07 15:32:3737 /**
38 * @param {boolean=} isWebComponent
Joel Einbinder7fbe24c2019-01-24 05:19:0139 * @param {boolean=} delegatesFocus
Blink Reformat4c46d092018-04-07 15:32:3740 */
Joel Einbinder7fbe24c2019-01-24 05:19:0141 constructor(isWebComponent, delegatesFocus) {
Blink Reformat4c46d092018-04-07 15:32:3742 super();
43 this.contentElement = createElementWithClass('div', 'widget');
44 if (isWebComponent) {
45 this.element = createElementWithClass('div', 'vbox flex-auto');
Paul Lewis9950e182019-12-16 16:06:0746 this._shadowRoot = createShadowRootWithCoreStyles(this.element, undefined, delegatesFocus);
Blink Reformat4c46d092018-04-07 15:32:3747 this._shadowRoot.appendChild(this.contentElement);
48 } else {
49 this.element = this.contentElement;
50 }
51 this._isWebComponent = isWebComponent;
52 this.element.__widget = this;
53 this._visible = false;
54 this._isRoot = false;
55 this._isShowing = false;
56 this._children = [];
57 this._hideOnDetach = false;
58 this._notificationDepth = 0;
59 this._invalidationsSuspended = 0;
60 this._defaultFocusedChild = null;
61 }
62
63 static _incrementWidgetCounter(parentElement, childElement) {
64 const count = (childElement.__widgetCounter || 0) + (childElement.__widget ? 1 : 0);
Tim van der Lippe1d6e57a2019-09-30 11:55:3465 if (!count) {
Blink Reformat4c46d092018-04-07 15:32:3766 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:3467 }
Blink Reformat4c46d092018-04-07 15:32:3768
69 while (parentElement) {
70 parentElement.__widgetCounter = (parentElement.__widgetCounter || 0) + count;
71 parentElement = parentElement.parentElementOrShadowHost();
72 }
73 }
74
75 static _decrementWidgetCounter(parentElement, childElement) {
76 const count = (childElement.__widgetCounter || 0) + (childElement.__widget ? 1 : 0);
Tim van der Lippe1d6e57a2019-09-30 11:55:3477 if (!count) {
Blink Reformat4c46d092018-04-07 15:32:3778 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:3479 }
Blink Reformat4c46d092018-04-07 15:32:3780
81 while (parentElement) {
82 parentElement.__widgetCounter -= count;
83 parentElement = parentElement.parentElementOrShadowHost();
84 }
85 }
86
87 static __assert(condition, message) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3488 if (!condition) {
Blink Reformat4c46d092018-04-07 15:32:3789 throw new Error(message);
Tim van der Lippe1d6e57a2019-09-30 11:55:3490 }
Blink Reformat4c46d092018-04-07 15:32:3791 }
92
93 /**
94 * @param {?Node} node
95 */
96 static focusWidgetForNode(node) {
97 while (node) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3498 if (node.__widget) {
Blink Reformat4c46d092018-04-07 15:32:3799 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:34100 }
Blink Reformat4c46d092018-04-07 15:32:37101 node = node.parentNodeOrShadowHost();
102 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34103 if (!node) {
Blink Reformat4c46d092018-04-07 15:32:37104 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34105 }
Blink Reformat4c46d092018-04-07 15:32:37106
107 let widget = node.__widget;
108 while (widget._parentWidget) {
109 widget._parentWidget._defaultFocusedChild = widget;
110 widget = widget._parentWidget;
111 }
112 }
113
114 markAsRoot() {
Tim van der Lippe0830b3d2019-10-03 13:20:07115 Widget.__assert(!this.element.parentElement, 'Attempt to mark as root attached node');
Blink Reformat4c46d092018-04-07 15:32:37116 this._isRoot = true;
117 }
118
119 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07120 * @return {?Widget}
Blink Reformat4c46d092018-04-07 15:32:37121 */
122 parentWidget() {
123 return this._parentWidget;
124 }
125
126 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07127 * @return {!Array.<!Widget>}
Blink Reformat4c46d092018-04-07 15:32:37128 */
129 children() {
130 return this._children;
131 }
132
133 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07134 * @param {!Widget} widget
Blink Reformat4c46d092018-04-07 15:32:37135 * @protected
136 */
137 childWasDetached(widget) {
138 }
139
140 /**
141 * @return {boolean}
142 */
143 isShowing() {
144 return this._isShowing;
145 }
146
147 /**
148 * @return {boolean}
149 */
150 shouldHideOnDetach() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34151 if (!this.element.parentElement) {
Blink Reformat4c46d092018-04-07 15:32:37152 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34153 }
154 if (this._hideOnDetach) {
Blink Reformat4c46d092018-04-07 15:32:37155 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34156 }
Blink Reformat4c46d092018-04-07 15:32:37157 for (const child of this._children) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34158 if (child.shouldHideOnDetach()) {
Blink Reformat4c46d092018-04-07 15:32:37159 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34160 }
Blink Reformat4c46d092018-04-07 15:32:37161 }
162 return false;
163 }
164
165 setHideOnDetach() {
166 this._hideOnDetach = true;
167 }
168
169 /**
170 * @return {boolean}
171 */
172 _inNotification() {
173 return !!this._notificationDepth || (this._parentWidget && this._parentWidget._inNotification());
174 }
175
176 _parentIsShowing() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34177 if (this._isRoot) {
Blink Reformat4c46d092018-04-07 15:32:37178 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34179 }
Blink Reformat4c46d092018-04-07 15:32:37180 return !!this._parentWidget && this._parentWidget.isShowing();
181 }
182
183 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07184 * @param {function(this:Widget)} method
Blink Reformat4c46d092018-04-07 15:32:37185 */
186 _callOnVisibleChildren(method) {
187 const copy = this._children.slice();
188 for (let i = 0; i < copy.length; ++i) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34189 if (copy[i]._parentWidget === this && copy[i]._visible) {
Blink Reformat4c46d092018-04-07 15:32:37190 method.call(copy[i]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34191 }
Blink Reformat4c46d092018-04-07 15:32:37192 }
193 }
194
195 _processWillShow() {
196 this._callOnVisibleChildren(this._processWillShow);
197 this._isShowing = true;
198 }
199
200 _processWasShown() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34201 if (this._inNotification()) {
Blink Reformat4c46d092018-04-07 15:32:37202 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34203 }
Blink Reformat4c46d092018-04-07 15:32:37204 this.restoreScrollPositions();
205 this._notify(this.wasShown);
206 this._callOnVisibleChildren(this._processWasShown);
207 }
208
209 _processWillHide() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34210 if (this._inNotification()) {
Blink Reformat4c46d092018-04-07 15:32:37211 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34212 }
Blink Reformat4c46d092018-04-07 15:32:37213 this.storeScrollPositions();
214
215 this._callOnVisibleChildren(this._processWillHide);
216 this._notify(this.willHide);
217 this._isShowing = false;
218 }
219
220 _processWasHidden() {
221 this._callOnVisibleChildren(this._processWasHidden);
222 }
223
224 _processOnResize() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34225 if (this._inNotification()) {
Blink Reformat4c46d092018-04-07 15:32:37226 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34227 }
228 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37229 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34230 }
Blink Reformat4c46d092018-04-07 15:32:37231 this._notify(this.onResize);
232 this._callOnVisibleChildren(this._processOnResize);
233 }
234
235 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07236 * @param {function(this:Widget)} notification
Blink Reformat4c46d092018-04-07 15:32:37237 */
238 _notify(notification) {
239 ++this._notificationDepth;
240 try {
241 notification.call(this);
242 } finally {
243 --this._notificationDepth;
244 }
245 }
246
247 wasShown() {
248 }
249
250 willHide() {
251 }
252
253 onResize() {
254 }
255
256 onLayout() {
257 }
258
259 ownerViewDisposed() {
260 }
261
262 /**
263 * @param {!Element} parentElement
264 * @param {?Node=} insertBefore
265 */
266 show(parentElement, insertBefore) {
Tim van der Lippe0830b3d2019-10-03 13:20:07267 Widget.__assert(parentElement, 'Attempt to attach widget with no parent element');
Blink Reformat4c46d092018-04-07 15:32:37268
269 if (!this._isRoot) {
270 // Update widget hierarchy.
271 let currentParent = parentElement;
Tim van der Lippe1d6e57a2019-09-30 11:55:34272 while (currentParent && !currentParent.__widget) {
Blink Reformat4c46d092018-04-07 15:32:37273 currentParent = currentParent.parentElementOrShadowHost();
Tim van der Lippe1d6e57a2019-09-30 11:55:34274 }
Tim van der Lippe0830b3d2019-10-03 13:20:07275 Widget.__assert(currentParent, 'Attempt to attach widget to orphan node');
Blink Reformat4c46d092018-04-07 15:32:37276 this._attach(currentParent.__widget);
277 }
278
279 this._showWidget(parentElement, insertBefore);
280 }
281
282 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07283 * @param {!Widget} parentWidget
Blink Reformat4c46d092018-04-07 15:32:37284 */
285 _attach(parentWidget) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34286 if (parentWidget === this._parentWidget) {
Blink Reformat4c46d092018-04-07 15:32:37287 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34288 }
289 if (this._parentWidget) {
Blink Reformat4c46d092018-04-07 15:32:37290 this.detach();
Tim van der Lippe1d6e57a2019-09-30 11:55:34291 }
Blink Reformat4c46d092018-04-07 15:32:37292 this._parentWidget = parentWidget;
293 this._parentWidget._children.push(this);
294 this._isRoot = false;
295 }
296
297 showWidget() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34298 if (this._visible) {
Blink Reformat4c46d092018-04-07 15:32:37299 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34300 }
Tim van der Lippe0830b3d2019-10-03 13:20:07301 Widget.__assert(this.element.parentElement, 'Attempt to show widget that is not hidden using hideWidget().');
Blink Reformat4c46d092018-04-07 15:32:37302 this._showWidget(/** @type {!Element} */ (this.element.parentElement), this.element.nextSibling);
303 }
304
305 /**
306 * @param {!Element} parentElement
307 * @param {?Node=} insertBefore
308 */
309 _showWidget(parentElement, insertBefore) {
310 let currentParent = parentElement;
Tim van der Lippe1d6e57a2019-09-30 11:55:34311 while (currentParent && !currentParent.__widget) {
Blink Reformat4c46d092018-04-07 15:32:37312 currentParent = currentParent.parentElementOrShadowHost();
Tim van der Lippe1d6e57a2019-09-30 11:55:34313 }
Blink Reformat4c46d092018-04-07 15:32:37314
315 if (this._isRoot) {
Tim van der Lippe0830b3d2019-10-03 13:20:07316 Widget.__assert(!currentParent, 'Attempt to show root widget under another widget');
Blink Reformat4c46d092018-04-07 15:32:37317 } else {
Tim van der Lippe0830b3d2019-10-03 13:20:07318 Widget.__assert(
Blink Reformat4c46d092018-04-07 15:32:37319 currentParent && currentParent.__widget === this._parentWidget,
320 'Attempt to show under node belonging to alien widget');
321 }
322
323 const wasVisible = this._visible;
Tim van der Lippe1d6e57a2019-09-30 11:55:34324 if (wasVisible && this.element.parentElement === parentElement) {
Blink Reformat4c46d092018-04-07 15:32:37325 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34326 }
Blink Reformat4c46d092018-04-07 15:32:37327
328 this._visible = true;
329
Tim van der Lippe1d6e57a2019-09-30 11:55:34330 if (!wasVisible && this._parentIsShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37331 this._processWillShow();
Tim van der Lippe1d6e57a2019-09-30 11:55:34332 }
Blink Reformat4c46d092018-04-07 15:32:37333
334 this.element.classList.remove('hidden');
335
336 // Reparent
337 if (this.element.parentElement !== parentElement) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34338 if (!this._externallyManaged) {
Tim van der Lippe0830b3d2019-10-03 13:20:07339 Widget._incrementWidgetCounter(parentElement, this.element);
Tim van der Lippe1d6e57a2019-09-30 11:55:34340 }
341 if (insertBefore) {
Tim van der Lippe0830b3d2019-10-03 13:20:07342 Widget._originalInsertBefore.call(parentElement, this.element, insertBefore);
Tim van der Lippe1d6e57a2019-09-30 11:55:34343 } else {
Tim van der Lippe0830b3d2019-10-03 13:20:07344 Widget._originalAppendChild.call(parentElement, this.element);
Tim van der Lippe1d6e57a2019-09-30 11:55:34345 }
Blink Reformat4c46d092018-04-07 15:32:37346 }
347
Tim van der Lippe1d6e57a2019-09-30 11:55:34348 if (!wasVisible && this._parentIsShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37349 this._processWasShown();
Tim van der Lippe1d6e57a2019-09-30 11:55:34350 }
Blink Reformat4c46d092018-04-07 15:32:37351
Tim van der Lippe1d6e57a2019-09-30 11:55:34352 if (this._parentWidget && this._hasNonZeroConstraints()) {
Blink Reformat4c46d092018-04-07 15:32:37353 this._parentWidget.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34354 } else {
Blink Reformat4c46d092018-04-07 15:32:37355 this._processOnResize();
Tim van der Lippe1d6e57a2019-09-30 11:55:34356 }
Blink Reformat4c46d092018-04-07 15:32:37357 }
358
359 hideWidget() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34360 if (!this._visible) {
Blink Reformat4c46d092018-04-07 15:32:37361 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34362 }
Blink Reformat4c46d092018-04-07 15:32:37363 this._hideWidget(false);
364 }
365
366 /**
367 * @param {boolean} removeFromDOM
368 */
369 _hideWidget(removeFromDOM) {
370 this._visible = false;
371 const parentElement = this.element.parentElement;
372
Tim van der Lippe1d6e57a2019-09-30 11:55:34373 if (this._parentIsShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37374 this._processWillHide();
Tim van der Lippe1d6e57a2019-09-30 11:55:34375 }
Blink Reformat4c46d092018-04-07 15:32:37376
377 if (removeFromDOM) {
378 // Force legal removal
Tim van der Lippe0830b3d2019-10-03 13:20:07379 Widget._decrementWidgetCounter(parentElement, this.element);
380 Widget._originalRemoveChild.call(parentElement, this.element);
Blink Reformat4c46d092018-04-07 15:32:37381 } else {
382 this.element.classList.add('hidden');
383 }
384
Tim van der Lippe1d6e57a2019-09-30 11:55:34385 if (this._parentIsShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37386 this._processWasHidden();
Tim van der Lippe1d6e57a2019-09-30 11:55:34387 }
388 if (this._parentWidget && this._hasNonZeroConstraints()) {
Blink Reformat4c46d092018-04-07 15:32:37389 this._parentWidget.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34390 }
Blink Reformat4c46d092018-04-07 15:32:37391 }
392
393 /**
394 * @param {boolean=} overrideHideOnDetach
395 */
396 detach(overrideHideOnDetach) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34397 if (!this._parentWidget && !this._isRoot) {
Blink Reformat4c46d092018-04-07 15:32:37398 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34399 }
Blink Reformat4c46d092018-04-07 15:32:37400
401 // hideOnDetach means that we should never remove element from dom - content
402 // has iframes and detaching it will hurt.
403 //
404 // overrideHideOnDetach will override hideOnDetach and the client takes
405 // responsibility for the consequences.
406 const removeFromDOM = overrideHideOnDetach || !this.shouldHideOnDetach();
407 if (this._visible) {
408 this._hideWidget(removeFromDOM);
409 } else if (removeFromDOM && this.element.parentElement) {
410 const parentElement = this.element.parentElement;
411 // Force kick out from DOM.
Tim van der Lippe0830b3d2019-10-03 13:20:07412 Widget._decrementWidgetCounter(parentElement, this.element);
413 Widget._originalRemoveChild.call(parentElement, this.element);
Blink Reformat4c46d092018-04-07 15:32:37414 }
415
416 // Update widget hierarchy.
417 if (this._parentWidget) {
418 const childIndex = this._parentWidget._children.indexOf(this);
Tim van der Lippe0830b3d2019-10-03 13:20:07419 Widget.__assert(childIndex >= 0, 'Attempt to remove non-child widget');
Blink Reformat4c46d092018-04-07 15:32:37420 this._parentWidget._children.splice(childIndex, 1);
Tim van der Lippe1d6e57a2019-09-30 11:55:34421 if (this._parentWidget._defaultFocusedChild === this) {
Blink Reformat4c46d092018-04-07 15:32:37422 this._parentWidget._defaultFocusedChild = null;
Tim van der Lippe1d6e57a2019-09-30 11:55:34423 }
Blink Reformat4c46d092018-04-07 15:32:37424 this._parentWidget.childWasDetached(this);
425 this._parentWidget = null;
426 } else {
Tim van der Lippe0830b3d2019-10-03 13:20:07427 Widget.__assert(this._isRoot, 'Removing non-root widget from DOM');
Blink Reformat4c46d092018-04-07 15:32:37428 }
429 }
430
431 detachChildWidgets() {
432 const children = this._children.slice();
Tim van der Lippe1d6e57a2019-09-30 11:55:34433 for (let i = 0; i < children.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37434 children[i].detach();
Tim van der Lippe1d6e57a2019-09-30 11:55:34435 }
Blink Reformat4c46d092018-04-07 15:32:37436 }
437
438 /**
439 * @return {!Array.<!Element>}
440 */
441 elementsToRestoreScrollPositionsFor() {
442 return [this.element];
443 }
444
445 storeScrollPositions() {
446 const elements = this.elementsToRestoreScrollPositionsFor();
447 for (let i = 0; i < elements.length; ++i) {
448 const container = elements[i];
449 container._scrollTop = container.scrollTop;
450 container._scrollLeft = container.scrollLeft;
451 }
452 }
453
454 restoreScrollPositions() {
455 const elements = this.elementsToRestoreScrollPositionsFor();
456 for (let i = 0; i < elements.length; ++i) {
457 const container = elements[i];
Tim van der Lippe1d6e57a2019-09-30 11:55:34458 if (container._scrollTop) {
Blink Reformat4c46d092018-04-07 15:32:37459 container.scrollTop = container._scrollTop;
Tim van der Lippe1d6e57a2019-09-30 11:55:34460 }
461 if (container._scrollLeft) {
Blink Reformat4c46d092018-04-07 15:32:37462 container.scrollLeft = container._scrollLeft;
Tim van der Lippe1d6e57a2019-09-30 11:55:34463 }
Blink Reformat4c46d092018-04-07 15:32:37464 }
465 }
466
467 doResize() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34468 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37469 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34470 }
Blink Reformat4c46d092018-04-07 15:32:37471 // No matter what notification we are in, dispatching onResize is not needed.
Tim van der Lippe1d6e57a2019-09-30 11:55:34472 if (!this._inNotification()) {
Blink Reformat4c46d092018-04-07 15:32:37473 this._callOnVisibleChildren(this._processOnResize);
Tim van der Lippe1d6e57a2019-09-30 11:55:34474 }
Blink Reformat4c46d092018-04-07 15:32:37475 }
476
477 doLayout() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34478 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37479 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34480 }
Blink Reformat4c46d092018-04-07 15:32:37481 this._notify(this.onLayout);
482 this.doResize();
483 }
484
485 /**
486 * @param {string} cssFile
487 */
488 registerRequiredCSS(cssFile) {
Paul Lewis9950e182019-12-16 16:06:07489 appendStyle(this._isWebComponent ? this._shadowRoot : this.element, cssFile);
Blink Reformat4c46d092018-04-07 15:32:37490 }
491
492 printWidgetHierarchy() {
493 const lines = [];
494 this._collectWidgetHierarchy('', lines);
495 console.log(lines.join('\n')); // eslint-disable-line no-console
496 }
497
498 _collectWidgetHierarchy(prefix, lines) {
499 lines.push(prefix + '[' + this.element.className + ']' + (this._children.length ? ' {' : ''));
500
Tim van der Lippe1d6e57a2019-09-30 11:55:34501 for (let i = 0; i < this._children.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37502 this._children[i]._collectWidgetHierarchy(prefix + ' ', lines);
Tim van der Lippe1d6e57a2019-09-30 11:55:34503 }
Blink Reformat4c46d092018-04-07 15:32:37504
Tim van der Lippe1d6e57a2019-09-30 11:55:34505 if (this._children.length) {
Blink Reformat4c46d092018-04-07 15:32:37506 lines.push(prefix + '}');
Tim van der Lippe1d6e57a2019-09-30 11:55:34507 }
Blink Reformat4c46d092018-04-07 15:32:37508 }
509
510 /**
511 * @param {?Element} element
512 */
513 setDefaultFocusedElement(element) {
514 this._defaultFocusedElement = element;
515 }
516
517 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07518 * @param {!Widget} child
Blink Reformat4c46d092018-04-07 15:32:37519 */
520 setDefaultFocusedChild(child) {
Tim van der Lippe0830b3d2019-10-03 13:20:07521 Widget.__assert(child._parentWidget === this, 'Attempt to set non-child widget as default focused.');
Blink Reformat4c46d092018-04-07 15:32:37522 this._defaultFocusedChild = child;
523 }
524
525 focus() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34526 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37527 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34528 }
Blink Reformat4c46d092018-04-07 15:32:37529
530 const element = this._defaultFocusedElement;
531 if (element) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34532 if (!element.hasFocus()) {
Blink Reformat4c46d092018-04-07 15:32:37533 element.focus();
Tim van der Lippe1d6e57a2019-09-30 11:55:34534 }
Blink Reformat4c46d092018-04-07 15:32:37535 return;
536 }
537
538 if (this._defaultFocusedChild && this._defaultFocusedChild._visible) {
539 this._defaultFocusedChild.focus();
540 } else {
541 for (const child of this._children) {
542 if (child._visible) {
543 child.focus();
544 return;
545 }
546 }
547 let child = this.contentElement.traverseNextNode(this.contentElement);
548 while (child) {
Paul Lewis9950e182019-12-16 16:06:07549 if (child instanceof XWidget) {
Blink Reformat4c46d092018-04-07 15:32:37550 child.focus();
551 return;
552 }
553 child = child.traverseNextNode(this.contentElement);
554 }
555 }
556 }
557
558 /**
559 * @return {boolean}
560 */
561 hasFocus() {
562 return this.element.hasFocus();
563 }
564
565 /**
Paul Lewis9950e182019-12-16 16:06:07566 * @return {!Constraints}
Blink Reformat4c46d092018-04-07 15:32:37567 */
568 calculateConstraints() {
Paul Lewis9950e182019-12-16 16:06:07569 return new Constraints();
Blink Reformat4c46d092018-04-07 15:32:37570 }
571
572 /**
Paul Lewis9950e182019-12-16 16:06:07573 * @return {!Constraints}
Blink Reformat4c46d092018-04-07 15:32:37574 */
575 constraints() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34576 if (typeof this._constraints !== 'undefined') {
Blink Reformat4c46d092018-04-07 15:32:37577 return this._constraints;
Tim van der Lippe1d6e57a2019-09-30 11:55:34578 }
579 if (typeof this._cachedConstraints === 'undefined') {
Blink Reformat4c46d092018-04-07 15:32:37580 this._cachedConstraints = this.calculateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34581 }
Blink Reformat4c46d092018-04-07 15:32:37582 return this._cachedConstraints;
583 }
584
585 /**
586 * @param {number} width
587 * @param {number} height
588 * @param {number} preferredWidth
589 * @param {number} preferredHeight
590 */
591 setMinimumAndPreferredSizes(width, height, preferredWidth, preferredHeight) {
Paul Lewis9950e182019-12-16 16:06:07592 this._constraints = new Constraints(new Size(width, height), new Size(preferredWidth, preferredHeight));
Blink Reformat4c46d092018-04-07 15:32:37593 this.invalidateConstraints();
594 }
595
596 /**
597 * @param {number} width
598 * @param {number} height
599 */
600 setMinimumSize(width, height) {
Paul Lewis9950e182019-12-16 16:06:07601 this._constraints = new Constraints(new Size(width, height));
Blink Reformat4c46d092018-04-07 15:32:37602 this.invalidateConstraints();
603 }
604
605 /**
606 * @return {boolean}
607 */
608 _hasNonZeroConstraints() {
609 const constraints = this.constraints();
610 return !!(
611 constraints.minimum.width || constraints.minimum.height || constraints.preferred.width ||
612 constraints.preferred.height);
613 }
614
615 suspendInvalidations() {
616 ++this._invalidationsSuspended;
617 }
618
619 resumeInvalidations() {
620 --this._invalidationsSuspended;
Tim van der Lippe1d6e57a2019-09-30 11:55:34621 if (!this._invalidationsSuspended && this._invalidationsRequested) {
Blink Reformat4c46d092018-04-07 15:32:37622 this.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34623 }
Blink Reformat4c46d092018-04-07 15:32:37624 }
625
626 invalidateConstraints() {
627 if (this._invalidationsSuspended) {
628 this._invalidationsRequested = true;
629 return;
630 }
631 this._invalidationsRequested = false;
632 const cached = this._cachedConstraints;
633 delete this._cachedConstraints;
634 const actual = this.constraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34635 if (!actual.isEqual(cached) && this._parentWidget) {
Blink Reformat4c46d092018-04-07 15:32:37636 this._parentWidget.invalidateConstraints();
Tim van der Lippe1d6e57a2019-09-30 11:55:34637 } else {
Blink Reformat4c46d092018-04-07 15:32:37638 this.doLayout();
Tim van der Lippe1d6e57a2019-09-30 11:55:34639 }
Blink Reformat4c46d092018-04-07 15:32:37640 }
Olivia Flynn1d938e42019-09-23 08:13:40641
642 // Excludes the widget from being tracked by its parents/ancestors via
643 // __widgetCounter because the widget is being handled by external code.
644 // Widgets marked as being externally managed are responsible for
645 // finishing out their own lifecycle (i.e. calling detach() before being
646 // removed from the DOM). This is e.g. used for CodeMirror.
647 //
648 // Also note that this must be called before the widget is shown so that
649 // so that its ancestor's __widgetCounter is not incremented.
650 markAsExternallyManaged() {
Tim van der Lippe0830b3d2019-10-03 13:20:07651 Widget.__assert(!this._parentWidget, 'Attempt to mark widget as externally managed after insertion to the DOM');
Olivia Flynn1d938e42019-09-23 08:13:40652 this._externallyManaged = true;
653 }
Tim van der Lippe0830b3d2019-10-03 13:20:07654}
Blink Reformat4c46d092018-04-07 15:32:37655
Tim van der Lippe0830b3d2019-10-03 13:20:07656export const _originalAppendChild = Element.prototype.appendChild;
657export const _originalInsertBefore = Element.prototype.insertBefore;
658export const _originalRemoveChild = Element.prototype.removeChild;
659export const _originalRemoveChildren = Element.prototype.removeChildren;
Blink Reformat4c46d092018-04-07 15:32:37660
661
662/**
663 * @unrestricted
664 */
Tim van der Lippe0830b3d2019-10-03 13:20:07665export class VBox extends Widget {
Blink Reformat4c46d092018-04-07 15:32:37666 /**
667 * @param {boolean=} isWebComponent
Joel Einbinder7fbe24c2019-01-24 05:19:01668 * @param {boolean=} delegatesFocus
Blink Reformat4c46d092018-04-07 15:32:37669 */
Joel Einbinder7fbe24c2019-01-24 05:19:01670 constructor(isWebComponent, delegatesFocus) {
671 super(isWebComponent, delegatesFocus);
Blink Reformat4c46d092018-04-07 15:32:37672 this.contentElement.classList.add('vbox');
673 }
674
675 /**
676 * @override
Paul Lewis9950e182019-12-16 16:06:07677 * @return {!Constraints}
Blink Reformat4c46d092018-04-07 15:32:37678 */
679 calculateConstraints() {
Paul Lewis9950e182019-12-16 16:06:07680 let constraints = new Constraints();
Blink Reformat4c46d092018-04-07 15:32:37681
682 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07683 * @this {!Widget}
Blink Reformat4c46d092018-04-07 15:32:37684 * @suppressReceiverCheck
685 */
686 function updateForChild() {
687 const child = this.constraints();
688 constraints = constraints.widthToMax(child);
689 constraints = constraints.addHeight(child);
690 }
691
692 this._callOnVisibleChildren(updateForChild);
693 return constraints;
694 }
Tim van der Lippe0830b3d2019-10-03 13:20:07695}
Blink Reformat4c46d092018-04-07 15:32:37696
697/**
698 * @unrestricted
699 */
Tim van der Lippe0830b3d2019-10-03 13:20:07700export class HBox extends Widget {
Blink Reformat4c46d092018-04-07 15:32:37701 /**
702 * @param {boolean=} isWebComponent
703 */
704 constructor(isWebComponent) {
705 super(isWebComponent);
706 this.contentElement.classList.add('hbox');
707 }
708
709 /**
710 * @override
Paul Lewis9950e182019-12-16 16:06:07711 * @return {!Constraints}
Blink Reformat4c46d092018-04-07 15:32:37712 */
713 calculateConstraints() {
Paul Lewis9950e182019-12-16 16:06:07714 let constraints = new Constraints();
Blink Reformat4c46d092018-04-07 15:32:37715
716 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07717 * @this {!Widget}
Blink Reformat4c46d092018-04-07 15:32:37718 * @suppressReceiverCheck
719 */
720 function updateForChild() {
721 const child = this.constraints();
722 constraints = constraints.addWidth(child);
723 constraints = constraints.heightToMax(child);
724 }
725
726 this._callOnVisibleChildren(updateForChild);
727 return constraints;
728 }
Tim van der Lippe0830b3d2019-10-03 13:20:07729}
Blink Reformat4c46d092018-04-07 15:32:37730
731/**
732 * @unrestricted
733 */
Tim van der Lippe0830b3d2019-10-03 13:20:07734export class VBoxWithResizeCallback extends VBox {
Blink Reformat4c46d092018-04-07 15:32:37735 /**
736 * @param {function()} resizeCallback
737 */
738 constructor(resizeCallback) {
739 super();
740 this._resizeCallback = resizeCallback;
741 }
742
743 /**
744 * @override
745 */
746 onResize() {
747 this._resizeCallback();
748 }
Tim van der Lippe0830b3d2019-10-03 13:20:07749}
Blink Reformat4c46d092018-04-07 15:32:37750
751/**
752 * @unrestricted
753 */
Tim van der Lippe0830b3d2019-10-03 13:20:07754export class WidgetFocusRestorer {
Blink Reformat4c46d092018-04-07 15:32:37755 /**
Tim van der Lippe0830b3d2019-10-03 13:20:07756 * @param {!Widget} widget
Blink Reformat4c46d092018-04-07 15:32:37757 */
758 constructor(widget) {
759 this._widget = widget;
760 this._previous = widget.element.ownerDocument.deepActiveElement();
761 widget.focus();
762 }
763
764 restore() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34765 if (!this._widget) {
Blink Reformat4c46d092018-04-07 15:32:37766 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34767 }
768 if (this._widget.hasFocus() && this._previous) {
Blink Reformat4c46d092018-04-07 15:32:37769 this._previous.focus();
Tim van der Lippe1d6e57a2019-09-30 11:55:34770 }
Blink Reformat4c46d092018-04-07 15:32:37771 this._previous = null;
772 this._widget = null;
773 }
Tim van der Lippe0830b3d2019-10-03 13:20:07774}
Blink Reformat4c46d092018-04-07 15:32:37775
776/**
777 * @override
778 * @param {?Node} child
779 * @return {!Node}
780 * @suppress {duplicate}
781 */
782Element.prototype.appendChild = function(child) {
Tim van der Lippe0830b3d2019-10-03 13:20:07783 Widget.__assert(!child.__widget || child.parentElement === this, 'Attempt to add widget via regular DOM operation.');
784 return Widget._originalAppendChild.call(this, child);
Blink Reformat4c46d092018-04-07 15:32:37785};
786
787/**
788 * @override
789 * @param {?Node} child
790 * @param {?Node} anchor
791 * @return {!Node}
792 * @suppress {duplicate}
793 */
794Element.prototype.insertBefore = function(child, anchor) {
Tim van der Lippe0830b3d2019-10-03 13:20:07795 Widget.__assert(!child.__widget || child.parentElement === this, 'Attempt to add widget via regular DOM operation.');
796 return Widget._originalInsertBefore.call(this, child, anchor);
Blink Reformat4c46d092018-04-07 15:32:37797};
798
799/**
800 * @override
801 * @param {?Node} child
802 * @return {!Node}
803 * @suppress {duplicate}
804 */
805Element.prototype.removeChild = function(child) {
Tim van der Lippe0830b3d2019-10-03 13:20:07806 Widget.__assert(
Blink Reformat4c46d092018-04-07 15:32:37807 !child.__widgetCounter && !child.__widget,
808 'Attempt to remove element containing widget via regular DOM operation');
Tim van der Lippe0830b3d2019-10-03 13:20:07809 return Widget._originalRemoveChild.call(this, child);
Blink Reformat4c46d092018-04-07 15:32:37810};
811
812Element.prototype.removeChildren = function() {
Tim van der Lippe0830b3d2019-10-03 13:20:07813 Widget.__assert(!this.__widgetCounter, 'Attempt to remove element containing widget via regular DOM operation');
814 Widget._originalRemoveChildren.call(this);
Blink Reformat4c46d092018-04-07 15:32:37815};
Tim van der Lippe0830b3d2019-10-03 13:20:07816
Tim van der Lippe0830b3d2019-10-03 13:20:07817Widget._originalAppendChild = _originalAppendChild;
818Widget._originalInsertBefore = _originalInsertBefore;
819Widget._originalRemoveChild = _originalRemoveChild;
820Widget._originalRemoveChildren = _originalRemoveChildren;