mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-07 20:39:31 +08:00
[ts] Type-check ui/Widget.js with TypeScript
This CL introduces a new 'WidgetElement' type. It is never really constructed but it has the __widget and __widgetCounter field. This way, TypeScript is able to understand what is going on. The rest of the CL are mostly added JSDoc annotation, casts and some more explicit null checks that both TS and Closure understand. CL 3 out of 3 to enable TS for ui/Widget.js. Bug: chromium:1011811 Change-Id: I15f3918a25f3387d79ad06a68e79e427de5890e0 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2349174 Commit-Queue: Simon Zünd <szuend@chromium.org> Reviewed-by: Jack Franklin <jacktfranklin@chromium.org> Reviewed-by: Paul Lewis <aerotwist@chromium.org>
This commit is contained in:
Vendored
+7
@@ -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;
|
||||
|
||||
+83
-26
@@ -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<!Widget>} */
|
||||
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<string>} */
|
||||
const lines = [];
|
||||
this._collectWidgetHierarchy('', lines);
|
||||
console.log(lines.join('\n')); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} prefix
|
||||
* @param {!Array<string>} 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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user