diff --git a/front_end/legacy/legacy-defs.d.ts b/front_end/legacy/legacy-defs.d.ts index fdcf112f57..001c0e1148 100644 --- a/front_end/legacy/legacy-defs.d.ts +++ b/front_end/legacy/legacy-defs.d.ts @@ -88,6 +88,10 @@ declare namespace Adb { type NetworkDiscoveryConfig = string[]; } +interface Document { + deepActiveElement(): Element|null; +} + interface HTMLElement { createChild(tagName: string, className?: string, content?: string): HTMLElement; createSVGChild(childType: string, className?: string): HTMLElement; @@ -96,6 +100,7 @@ interface HTMLElement { interface Element { createChild(tagName: string, className?: string, content?: string): Element; createTextChild(text: string): Text; + hasFocus(): boolean; removeChildren(): void; } @@ -106,6 +111,8 @@ interface Event { interface Node { getComponentSelection(): Selection|null; hasSameShadowRoot(other: Node): boolean; + parentElementOrShadowHost(): Element|null; + traverseNextNode(stayWithin?: Node): Node|null; } declare function isEnterKey(event: Event): boolean; diff --git a/front_end/ui/Widget.js b/front_end/ui/Widget.js index 1caf66670c..d521c948be 100644 --- a/front_end/ui/Widget.js +++ b/front_end/ui/Widget.js @@ -24,9 +24,6 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -// @ts-nocheck -// TODO(crbug.com/1011811): Enable TypeScript compiler checks - import * as Common from '../common/common.js'; import * as DOMExtension from '../dom_extension/dom_extension.js'; import {Constraints, Size} from './Geometry.js'; @@ -34,6 +31,17 @@ import {appendStyle} from './utils/append-style.js'; import {createShadowRootWithCoreStyles} from './utils/create-shadow-root-with-core-styles.js'; import {XWidget} from './XWidget.js'; +export class WidgetElement extends HTMLDivElement { // eslint-disable-line no-unused-vars + constructor() { + super(); + /** @type {?Widget} */ + this.__widget; + + /** @type {?number} */ + this.__widgetCounter; + } +} + /** * @unrestricted */ @@ -44,53 +52,76 @@ export class Widget extends Common.ObjectWrapper.ObjectWrapper { */ constructor(isWebComponent, delegatesFocus) { super(); + /** @type {!WidgetElement} */ + this.element; this.contentElement = document.createElement('div'); this.contentElement.classList.add('widget'); if (isWebComponent) { - this.element = document.createElement('div'); + this.element = /** @type {!WidgetElement} */ (document.createElement('div')); this.element.classList.add('vbox'); this.element.classList.add('flex-auto'); this._shadowRoot = createShadowRootWithCoreStyles(this.element, undefined, delegatesFocus); this._shadowRoot.appendChild(this.contentElement); } else { - this.element = this.contentElement; + this.element = /** @type {!WidgetElement} */ (this.contentElement); } this._isWebComponent = isWebComponent; this.element.__widget = this; this._visible = false; this._isRoot = false; this._isShowing = false; + /** @type {!Array} */ this._children = []; this._hideOnDetach = false; this._notificationDepth = 0; this._invalidationsSuspended = 0; this._defaultFocusedChild = null; + /** @type {?Widget} */ + this._parentWidget = null; } + /** + * @param {!WidgetElement} parentElement + * @param {!WidgetElement} childElement + */ static _incrementWidgetCounter(parentElement, childElement) { const count = (childElement.__widgetCounter || 0) + (childElement.__widget ? 1 : 0); if (!count) { return; } - while (parentElement) { - parentElement.__widgetCounter = (parentElement.__widgetCounter || 0) + count; - parentElement = parentElement.parentElementOrShadowHost(); + /** @type {?WidgetElement} */ + let currentElement = parentElement; + while (currentElement) { + currentElement.__widgetCounter = (currentElement.__widgetCounter || 0) + count; + currentElement = parentWidgetElementOrShadowHost(currentElement); } } + /** + * @param {!WidgetElement} parentElement + * @param {!WidgetElement} childElement + */ static _decrementWidgetCounter(parentElement, childElement) { const count = (childElement.__widgetCounter || 0) + (childElement.__widget ? 1 : 0); if (!count) { return; } - while (parentElement) { - parentElement.__widgetCounter -= count; - parentElement = parentElement.parentElementOrShadowHost(); + /** @type {?WidgetElement} */ + let currentElement = parentElement; + while (currentElement) { + if (currentElement.__widgetCounter) { + currentElement.__widgetCounter -= count; + } + currentElement = parentWidgetElementOrShadowHost(currentElement); } } + /** + * @param {*} condition + * @param {string} message + */ static __assert(condition, message) { if (!condition) { throw new Error(message); @@ -156,7 +187,7 @@ export class Widget extends Common.ObjectWrapper.ObjectWrapper { * @return {boolean} */ _inNotification() { - return !!this._notificationDepth || (this._parentWidget && this._parentWidget._inNotification()); + return !!this._notificationDepth || !!(this._parentWidget && this._parentWidget._inNotification()); } _parentIsShowing() { @@ -254,15 +285,18 @@ export class Widget extends Common.ObjectWrapper.ObjectWrapper { if (!this._isRoot) { // Update widget hierarchy. - let currentParent = parentElement; + /** @type {?WidgetElement} */ + let currentParent = /** @type {?WidgetElement} */ (parentElement); while (currentParent && !currentParent.__widget) { - currentParent = currentParent.parentElementOrShadowHost(); + currentParent = parentWidgetElementOrShadowHost(currentParent); + } + if (!currentParent || !currentParent.__widget) { + throw new Error('Attempt to attach widget to orphan node'); } - Widget.__assert(currentParent, 'Attempt to attach widget to orphan node'); this._attach(currentParent.__widget); } - this._showWidget(parentElement, insertBefore); + this._showWidget(/** @type {!WidgetElement} */ (parentElement), insertBefore); } /** @@ -275,6 +309,7 @@ export class Widget extends Common.ObjectWrapper.ObjectWrapper { if (this._parentWidget) { this.detach(); } + /** @type {?Widget} */ this._parentWidget = parentWidget; this._parentWidget._children.push(this); this._isRoot = false; @@ -284,18 +319,21 @@ export class Widget extends Common.ObjectWrapper.ObjectWrapper { if (this._visible) { return; } - Widget.__assert(this.element.parentElement, 'Attempt to show widget that is not hidden using hideWidget().'); - this._showWidget(/** @type {!Element} */ (this.element.parentElement), this.element.nextSibling); + if (!this.element.parentElement) { + throw new Error('Attempt to show widget that is not hidden using hideWidget().'); + } + this._showWidget(/** @type {!WidgetElement} */ (this.element.parentElement), this.element.nextSibling); } /** - * @param {!Element} parentElement + * @param {!WidgetElement} parentElement * @param {?Node=} insertBefore */ _showWidget(parentElement, insertBefore) { + /** @type {?WidgetElement} */ let currentParent = parentElement; while (currentParent && !currentParent.__widget) { - currentParent = currentParent.parentElementOrShadowHost(); + currentParent = parentWidgetElementOrShadowHost(currentParent); } if (this._isRoot) { @@ -354,7 +392,7 @@ export class Widget extends Common.ObjectWrapper.ObjectWrapper { */ _hideWidget(removeFromDOM) { this._visible = false; - const parentElement = this.element.parentElement; + const parentElement = /** @type {!WidgetElement} */ (this.element.parentElement); if (this._parentIsShowing()) { this._processWillHide(); @@ -393,7 +431,7 @@ export class Widget extends Common.ObjectWrapper.ObjectWrapper { if (this._visible) { this._hideWidget(removeFromDOM); } else if (removeFromDOM && this.element.parentElement) { - const parentElement = this.element.parentElement; + const parentElement = /** @type {!WidgetElement} */ (this.element.parentElement); // Force kick out from DOM. Widget._decrementWidgetCounter(parentElement, this.element); DOMExtension.DOMExtension.originalRemoveChild.call(parentElement, this.element); @@ -468,15 +506,24 @@ export class Widget extends Common.ObjectWrapper.ObjectWrapper { * @param {string} cssFile */ registerRequiredCSS(cssFile) { - appendStyle(this._isWebComponent ? this._shadowRoot : this.element, cssFile); + if (this._isWebComponent) { + appendStyle(/** @type {!DocumentFragment} */ (this._shadowRoot), cssFile); + } else { + appendStyle(this.element, cssFile); + } } printWidgetHierarchy() { + /** @type {!Array} */ const lines = []; this._collectWidgetHierarchy('', lines); console.log(lines.join('\n')); // eslint-disable-line no-console } + /** + * @param {string} prefix + * @param {!Array} lines + */ _collectWidgetHierarchy(prefix, lines) { lines.push(prefix + '[' + this.element.className + ']' + (this._children.length ? ' {' : '')); @@ -509,7 +556,7 @@ export class Widget extends Common.ObjectWrapper.ObjectWrapper { return; } - const element = this._defaultFocusedElement; + const element = /** @type {?HTMLElement} */ (this._defaultFocusedElement); if (element) { if (!element.hasFocus()) { element.focus(); @@ -614,7 +661,7 @@ export class Widget extends Common.ObjectWrapper.ObjectWrapper { const cached = this._cachedConstraints; delete this._cachedConstraints; const actual = this.constraints(); - if (!actual.isEqual(cached) && this._parentWidget) { + if (!actual.isEqual(cached || null) && this._parentWidget) { this._parentWidget.invalidateConstraints(); } else { this.doLayout(); @@ -737,8 +784,10 @@ export class WidgetFocusRestorer { * @param {!Widget} widget */ constructor(widget) { + /** @type {?Widget} */ this._widget = widget; - this._previous = widget.element.ownerDocument.deepActiveElement(); + /** @type {?HTMLElement} */ + this._previous = /** @type {?HTMLElement} */ (widget.element.ownerDocument.deepActiveElement()); widget.focus(); } @@ -753,3 +802,11 @@ export class WidgetFocusRestorer { this._widget = null; } } + +/** + * @param {!WidgetElement} element + * @return {?WidgetElement} + */ +function parentWidgetElementOrShadowHost(element) { + return /** @type {?WidgetElement} */ (element.parentElementOrShadowHost()); +}