mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-09 13:18:29 +08:00
Bug: chromium:1158760 Change-Id: I09b10e827e46728be3020fe0d0c7e339a80350b0 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2687506 Commit-Queue: Jan Scheffler <janscheffler@chromium.org> Reviewed-by: Tim van der Lippe <tvanderlippe@chromium.org> Auto-Submit: Jan Scheffler <janscheffler@chromium.org>
223 lines
7.8 KiB
TypeScript
223 lines
7.8 KiB
TypeScript
// Copyright 2014 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
/* eslint-disable rulesdir/no_underscored_properties */
|
|
|
|
import * as Common from '../common/common.js';
|
|
import * as i18n from '../i18n/i18n.js';
|
|
import * as SDK from '../sdk/sdk.js'; // eslint-disable-line no-unused-vars
|
|
import * as UI from '../ui/ui.js';
|
|
|
|
import {ObjectPropertiesSection} from './ObjectPropertiesSection.js';
|
|
|
|
export const UIStrings = {
|
|
/**
|
|
*@description A context menu item in the Custom Preview Component
|
|
*/
|
|
showAsJavascriptObject: 'Show as JavaScript object',
|
|
};
|
|
const str_ = i18n.i18n.registerUIStrings('object_ui/CustomPreviewComponent.ts', UIStrings);
|
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
export class CustomPreviewSection {
|
|
_sectionElement: HTMLSpanElement;
|
|
_object: SDK.RemoteObject.RemoteObject;
|
|
_expanded: boolean;
|
|
_cachedContent: Node|null;
|
|
_header: Node|undefined;
|
|
_expandIcon: UI.Icon.Icon|undefined;
|
|
constructor(object: SDK.RemoteObject.RemoteObject) {
|
|
this._sectionElement = document.createElement('span');
|
|
this._sectionElement.classList.add('custom-expandable-section');
|
|
this._object = object;
|
|
this._expanded = false;
|
|
this._cachedContent = null;
|
|
const customPreview = object.customPreview();
|
|
|
|
if (!customPreview) {
|
|
return;
|
|
}
|
|
|
|
let headerJSON;
|
|
try {
|
|
headerJSON = JSON.parse(customPreview.header);
|
|
} catch (e) {
|
|
Common.Console.Console.instance().error('Broken formatter: header is invalid json ' + e);
|
|
return;
|
|
}
|
|
this._header = this._renderJSONMLTag(headerJSON);
|
|
if (this._header.nodeType === Node.TEXT_NODE) {
|
|
Common.Console.Console.instance().error('Broken formatter: header should be an element node.');
|
|
return;
|
|
}
|
|
|
|
if (customPreview.bodyGetterId) {
|
|
if (this._header instanceof Element) {
|
|
this._header.classList.add('custom-expandable-section-header');
|
|
}
|
|
this._header.addEventListener('click', this._onClick.bind(this), false);
|
|
this._expandIcon = UI.Icon.Icon.create('smallicon-triangle-right', 'custom-expand-icon');
|
|
this._header.insertBefore(this._expandIcon, this._header.firstChild);
|
|
}
|
|
|
|
this._sectionElement.appendChild(this._header);
|
|
}
|
|
|
|
element(): Element {
|
|
return this._sectionElement;
|
|
}
|
|
|
|
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
_renderJSONMLTag(jsonML: any): Node {
|
|
if (!Array.isArray(jsonML)) {
|
|
return document.createTextNode(String(jsonML));
|
|
}
|
|
|
|
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const array = (jsonML as any[]);
|
|
return array[0] === 'object' ? this._layoutObjectTag(array) : this._renderElement(array);
|
|
}
|
|
|
|
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
_renderElement(object: any[]): Node {
|
|
const tagName = object.shift();
|
|
if (!CustomPreviewSection._allowedTags.has(tagName)) {
|
|
Common.Console.Console.instance().error('Broken formatter: element ' + tagName + ' is not allowed!');
|
|
return document.createElement('span');
|
|
}
|
|
const element = document.createElement((tagName as string));
|
|
if ((typeof object[0] === 'object') && !Array.isArray(object[0])) {
|
|
const attributes = object.shift();
|
|
for (const key in attributes) {
|
|
const value = attributes[key];
|
|
if ((key !== 'style') || (typeof value !== 'string')) {
|
|
continue;
|
|
}
|
|
|
|
element.setAttribute(key, value);
|
|
}
|
|
}
|
|
|
|
this._appendJsonMLTags(element, object);
|
|
return element;
|
|
}
|
|
|
|
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
_layoutObjectTag(objectTag: any[]): Node {
|
|
objectTag.shift();
|
|
const attributes = objectTag.shift();
|
|
const remoteObject = this._object.runtimeModel().createRemoteObject((attributes as Protocol.Runtime.RemoteObject));
|
|
if (remoteObject.customPreview()) {
|
|
return (new CustomPreviewSection(remoteObject)).element();
|
|
}
|
|
|
|
const sectionElement = ObjectPropertiesSection.defaultObjectPresentation(remoteObject);
|
|
sectionElement.classList.toggle('custom-expandable-section-standard-section', remoteObject.hasChildren);
|
|
return sectionElement;
|
|
}
|
|
|
|
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
_appendJsonMLTags(parentElement: Node, jsonMLTags: any[]): void {
|
|
for (let i = 0; i < jsonMLTags.length; ++i) {
|
|
parentElement.appendChild(this._renderJSONMLTag(jsonMLTags[i]));
|
|
}
|
|
}
|
|
|
|
_onClick(event: Event): void {
|
|
event.consume(true);
|
|
if (this._cachedContent) {
|
|
this._toggleExpand();
|
|
} else {
|
|
this._loadBody();
|
|
}
|
|
}
|
|
|
|
_toggleExpand(): void {
|
|
this._expanded = !this._expanded;
|
|
if (this._header instanceof Element) {
|
|
this._header.classList.toggle('expanded', this._expanded);
|
|
}
|
|
if (this._cachedContent instanceof Element) {
|
|
this._cachedContent.classList.toggle('hidden', !this._expanded);
|
|
}
|
|
if (this._expandIcon) {
|
|
if (this._expanded) {
|
|
this._expandIcon.setIconType('smallicon-triangle-down');
|
|
} else {
|
|
this._expandIcon.setIconType('smallicon-triangle-right');
|
|
}
|
|
}
|
|
}
|
|
|
|
async _loadBody(): Promise<void> {
|
|
const customPreview = this._object.customPreview();
|
|
|
|
if (!customPreview) {
|
|
return;
|
|
}
|
|
|
|
if (customPreview.bodyGetterId) {
|
|
const bodyJsonML = await this._object.callFunctionJSON(
|
|
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
bodyGetter => (bodyGetter as () => any)(), [{objectId: customPreview.bodyGetterId}]);
|
|
if (!bodyJsonML) {
|
|
return;
|
|
}
|
|
|
|
this._cachedContent = this._renderJSONMLTag(bodyJsonML);
|
|
this._sectionElement.appendChild(this._cachedContent);
|
|
this._toggleExpand();
|
|
}
|
|
}
|
|
|
|
static _allowedTags = new Set(['span', 'div', 'ol', 'li', 'table', 'tr', 'td']);
|
|
}
|
|
|
|
export class CustomPreviewComponent {
|
|
_object: SDK.RemoteObject.RemoteObject;
|
|
_customPreviewSection: CustomPreviewSection|null;
|
|
element: HTMLSpanElement;
|
|
constructor(object: SDK.RemoteObject.RemoteObject) {
|
|
this._object = object;
|
|
this._customPreviewSection = new CustomPreviewSection(object);
|
|
this.element = document.createElement('span');
|
|
this.element.classList.add('source-code');
|
|
const shadowRoot = UI.Utils.createShadowRootWithCoreStyles(
|
|
this.element,
|
|
{cssFile: 'object_ui/customPreviewComponent.css', enableLegacyPatching: false, delegatesFocus: undefined});
|
|
this.element.addEventListener('contextmenu', this._contextMenuEventFired.bind(this), false);
|
|
shadowRoot.appendChild(this._customPreviewSection.element());
|
|
}
|
|
|
|
expandIfPossible(): void {
|
|
const customPreview = this._object.customPreview();
|
|
if (customPreview && customPreview.bodyGetterId && this._customPreviewSection) {
|
|
this._customPreviewSection._loadBody();
|
|
}
|
|
}
|
|
|
|
_contextMenuEventFired(event: Event): void {
|
|
const contextMenu = new UI.ContextMenu.ContextMenu(event);
|
|
if (this._customPreviewSection) {
|
|
contextMenu.revealSection().appendItem(
|
|
i18nString(UIStrings.showAsJavascriptObject), this._disassemble.bind(this));
|
|
}
|
|
contextMenu.appendApplicableItems(this._object);
|
|
contextMenu.show();
|
|
}
|
|
|
|
_disassemble(): void {
|
|
if (this.element.shadowRoot) {
|
|
this.element.shadowRoot.textContent = '';
|
|
this._customPreviewSection = null;
|
|
this.element.shadowRoot.appendChild(ObjectPropertiesSection.defaultObjectPresentation(this._object));
|
|
}
|
|
}
|
|
}
|