mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-30 09:54:02 +08:00
Add explain experiment
Bug: b/295119680 Change-Id: I046a9516e00f63cd143ded08312abe229b4f8c15 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/4802144 Commit-Queue: Alex Rudenko <alexrudenko@chromium.org> Reviewed-by: Danil Somsikov <dsv@chromium.org>
This commit is contained in:
committed by
Devtools-frontend LUCI CQ
parent
108da102fe
commit
eb44342c2a
@@ -499,6 +499,8 @@ grd_files_release_sources = [
|
||||
"front_end/panels/emulation/emulation-meta.js",
|
||||
"front_end/panels/emulation/emulation.js",
|
||||
"front_end/panels/event_listeners/event_listeners.js",
|
||||
"front_end/panels/explain/explain-meta.js",
|
||||
"front_end/panels/explain/explain.js",
|
||||
"front_end/panels/issues/components/components.js",
|
||||
"front_end/panels/issues/issues-meta.js",
|
||||
"front_end/panels/issues/issues.js",
|
||||
@@ -1241,6 +1243,10 @@ grd_files_debug_sources = [
|
||||
"front_end/panels/event_listeners/EventListenersUtils.js",
|
||||
"front_end/panels/event_listeners/EventListenersView.js",
|
||||
"front_end/panels/event_listeners/eventListenersView.css.js",
|
||||
"front_end/panels/explain/ActionDelegate.js",
|
||||
"front_end/panels/explain/Bindings.js",
|
||||
"front_end/panels/explain/ContextMenuProvider.js",
|
||||
"front_end/panels/explain/ExplainPopover.js",
|
||||
"front_end/panels/issues/AffectedBlockedByResponseView.js",
|
||||
"front_end/panels/issues/AffectedCookiesView.js",
|
||||
"front_end/panels/issues/AffectedDirectivesView.js",
|
||||
|
||||
@@ -449,6 +449,8 @@ export class InspectorFrontendHostStub implements InspectorFrontendHostAPI {
|
||||
async initialTargetId(): Promise<string|null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
explainCode: unknown;
|
||||
}
|
||||
|
||||
// @ts-ignore Global injected by devtools-compatibility.js
|
||||
|
||||
@@ -300,6 +300,9 @@ export interface InspectorFrontendHostAPI {
|
||||
setAddExtensionCallback(callback: (arg0: ExtensionDescriptor) => void): void;
|
||||
|
||||
initialTargetId(): Promise<string|null>;
|
||||
|
||||
// TODO: update the binding name.
|
||||
explainCode?: unknown;
|
||||
}
|
||||
|
||||
export interface ContextMenuDescriptor {
|
||||
|
||||
@@ -21,6 +21,7 @@ devtools_entrypoint("shell") {
|
||||
"../../panels/console:meta",
|
||||
"../../panels/console_counters:meta",
|
||||
"../../panels/coverage:meta",
|
||||
"../../panels/explain:meta",
|
||||
"../../panels/profiler:meta",
|
||||
"../../panels/protocol_monitor:meta",
|
||||
"../../panels/settings:meta",
|
||||
|
||||
@@ -6,6 +6,7 @@ import '../../Images/Images.js';
|
||||
import '../../core/root/root-legacy.js';
|
||||
import '../../core/dom_extension/dom_extension.js';
|
||||
import '../../panels/sources/sources-meta.js';
|
||||
import '../../panels/explain/explain-meta.js';
|
||||
import '../../panels/profiler/profiler-meta.js';
|
||||
import '../../panels/console/console-meta.js';
|
||||
import '../../panels/coverage/coverage-meta.js';
|
||||
|
||||
@@ -42,6 +42,7 @@ devtools_entrypoint("bundle") {
|
||||
"../../panels/changes/*",
|
||||
"../../panels/coverage/*",
|
||||
"../../panels/emulation/*",
|
||||
"../../panels/explain/*",
|
||||
"../../panels/lighthouse/*",
|
||||
"../../panels/network/*",
|
||||
"../../panels/profiler/*",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
import * as UI from '../../ui/legacy/legacy.js';
|
||||
import * as Sources from '../sources/sources.js';
|
||||
|
||||
import { ExplainPopover } from './ExplainPopover.js';
|
||||
|
||||
let actionDelegateInstance: ActionDelegate;
|
||||
|
||||
export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
|
||||
static instance(opts: {
|
||||
forceNew: boolean|null,
|
||||
} = {forceNew: null}): ActionDelegate {
|
||||
const {forceNew} = opts;
|
||||
if (!actionDelegateInstance || forceNew) {
|
||||
actionDelegateInstance = new ActionDelegate();
|
||||
}
|
||||
|
||||
return actionDelegateInstance;
|
||||
}
|
||||
|
||||
handleAction(_context: UI.Context.Context, actionId: string): boolean {
|
||||
switch (actionId) {
|
||||
case 'explain.code': {
|
||||
const frame = UI.Context.Context.instance().flavor(Sources.UISourceCodeFrame.UISourceCodeFrame);
|
||||
if (frame) {
|
||||
new ExplainPopover(frame);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
# Copyright 2023 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.
|
||||
|
||||
import("../../../scripts/build/ninja/devtools_entrypoint.gni")
|
||||
import("../../../scripts/build/ninja/devtools_module.gni")
|
||||
import("../../../scripts/build/ninja/generate_css.gni")
|
||||
import("../visibility.gni")
|
||||
|
||||
devtools_module("explain") {
|
||||
sources = [
|
||||
"ActionDelegate.ts",
|
||||
"Bindings.ts",
|
||||
"ContextMenuProvider.ts",
|
||||
"ExplainPopover.ts",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"../../core/host:bundle",
|
||||
"../../third_party/marked:bundle",
|
||||
"../../ui/components/markdown_view:bundle",
|
||||
"../../ui/legacy:bundle",
|
||||
"../../ui/legacy/components/source_frame:bundle",
|
||||
"../sources:bundle",
|
||||
]
|
||||
}
|
||||
|
||||
devtools_entrypoint("bundle") {
|
||||
entrypoint = "explain.ts"
|
||||
|
||||
deps = [ ":explain" ]
|
||||
|
||||
visibility = [
|
||||
":*",
|
||||
"../../../test/unittests/*",
|
||||
"../../../test/unittests/front_end/panels/emulation/*",
|
||||
"../../entrypoints/*",
|
||||
"../../legacy_test_runner/*",
|
||||
]
|
||||
|
||||
visibility += devtools_panels_visibility
|
||||
}
|
||||
|
||||
devtools_entrypoint("meta") {
|
||||
entrypoint = "explain-meta.ts"
|
||||
|
||||
deps = [
|
||||
":bundle",
|
||||
"../../core/i18n:bundle",
|
||||
"../../models/workspace:bundle",
|
||||
"../../ui/legacy:bundle",
|
||||
"../sources:bundle",
|
||||
]
|
||||
|
||||
visibility = [ "../../entrypoints/*" ]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
import * as Host from '../../core/host/host.js';
|
||||
|
||||
export function isAvailable(): boolean {
|
||||
return Boolean(Host.InspectorFrontendHost.InspectorFrontendHostInstance.explainCode);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
import type * as UI from '../../ui/legacy/legacy.js';
|
||||
|
||||
export class ContextMenuProvider implements UI.ContextMenu.Provider {
|
||||
appendApplicableItems(_event: Event, contextMenu: UI.ContextMenu.ContextMenu, _target: Object): void {
|
||||
contextMenu.debugSection().appendAction('explain.code');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
import * as UI from '../../ui/legacy/legacy.js';
|
||||
import type * as Sources from '../sources/sources.js';
|
||||
import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
|
||||
import * as MarkdownView from '../../ui/components/markdown_view/markdown_view.js';
|
||||
import * as Marked from '../../third_party/marked/marked.js';
|
||||
|
||||
export class ExplainPopover {
|
||||
constructor(frame: Sources.UISourceCodeFrame.UISourceCodeFrame) {
|
||||
const {state: editorState} = frame.textEditor;
|
||||
const selectionRange = editorState.selection.main;
|
||||
const leftCorner = frame.textEditor.editor.coordsAtPos(selectionRange.from);
|
||||
const rightCorner = frame.textEditor.editor.coordsAtPos(selectionRange.to);
|
||||
if (!leftCorner || !rightCorner) {
|
||||
return;
|
||||
}
|
||||
const box = new AnchorBox(leftCorner.left, leftCorner.top - 2, rightCorner.right - leftCorner.left, rightCorner.bottom - leftCorner.top);
|
||||
const popover = UI.PopoverHelper.PopoverHelper.createPopover();
|
||||
popover.contentElement.classList.toggle('has-padding', false);
|
||||
popover.contentElement.addEventListener('mousemove', () => {}, true);
|
||||
popover.contentElement.addEventListener('mouseout', () => {}, true);
|
||||
const div = document.createElement('div');
|
||||
const ouput = 'This code creates a class called `PageHandlerPendingReceiver`. The constructor takes a handle as input and stores it in the `handle` property. The `bindInBrowser` method takes a scope as input and uses it to bind the `handle` to the interface `ntp.history_clusters.mojom.PageHandler`.';
|
||||
const component = new MarkdownView.MarkdownView.MarkdownView();
|
||||
component.data = {tokens: Marked.Marked.lexer(ouput)};
|
||||
div.append(component);
|
||||
div.style.margin = '16px';
|
||||
div.style.maxWidth = ' 400px';
|
||||
popover.contentElement.classList.toggle('has-padding', false);
|
||||
popover.contentElement.appendChild(div);
|
||||
popover.setContentAnchorBox(box);
|
||||
popover.show(document);
|
||||
frame.addEventListener(SourceFrame.SourceFrame.Events.EditorScroll, () => {
|
||||
popover.hide();
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
import * as i18n from '../../core/i18n/i18n.js';
|
||||
import * as UI from '../../ui/legacy/legacy.js';
|
||||
import * as Workspace from '../../models/workspace/workspace.js';
|
||||
|
||||
import type * as Explain from './explain.js';
|
||||
import type * as Sources from '../sources/sources.js';
|
||||
|
||||
import {isAvailable} from './Bindings.js';
|
||||
|
||||
const UIStrings = {
|
||||
/**
|
||||
*@description Title of an action in the debugger tool to explain selection.
|
||||
*/
|
||||
explainCode: '✨ Explain selected code',
|
||||
};
|
||||
const str_ = i18n.i18n.registerUIStrings('panels/explain/explain-meta.ts', UIStrings);
|
||||
const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_);
|
||||
let loadedExplainModule: (typeof Explain|undefined);
|
||||
|
||||
async function loadExplainModule(): Promise<typeof Explain> {
|
||||
if (!loadedExplainModule) {
|
||||
loadedExplainModule = await import('./explain.js');
|
||||
}
|
||||
return loadedExplainModule;
|
||||
}
|
||||
|
||||
let loadedSourcesModule: (typeof Sources|undefined);
|
||||
function maybeRetrieveContextTypes<T = unknown>(getClassCallBack: (sourcesModule: typeof Sources) => T[]): T[] {
|
||||
if (loadedSourcesModule === undefined) {
|
||||
return [];
|
||||
}
|
||||
return getClassCallBack(loadedSourcesModule);
|
||||
}
|
||||
|
||||
if (isAvailable()) {
|
||||
UI.ActionRegistration.registerActionExtension({
|
||||
actionId: 'explain.code',
|
||||
category: UI.ActionRegistration.ActionCategory.EXPLAIN,
|
||||
async loadActionDelegate() {
|
||||
const Explain = await loadExplainModule();
|
||||
return Explain.ActionDelegate.instance();
|
||||
},
|
||||
title: i18nLazyString(UIStrings.explainCode),
|
||||
contextTypes() {
|
||||
return maybeRetrieveContextTypes(Sources => [Sources.UISourceCodeFrame.UISourceCodeFrame]);
|
||||
},
|
||||
});
|
||||
|
||||
UI.ContextMenu.registerProvider({
|
||||
contextTypes() {
|
||||
return [
|
||||
Workspace.UISourceCode.UISourceCode,
|
||||
];
|
||||
},
|
||||
async loadProvider() {
|
||||
const Explain = await loadExplainModule();
|
||||
return new Explain.ContextMenuProvider();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
export * from './ExplainPopover.js';
|
||||
export * from './ActionDelegate.js';
|
||||
export * from './Bindings.js';
|
||||
export * from './ContextMenuProvider.js';
|
||||
@@ -107,6 +107,7 @@ devtools_entrypoint("bundle") {
|
||||
"../../../test/unittests/front_end/entrypoints/missing_entrypoints/*",
|
||||
"../../../test/unittests/front_end/panels/sources/*",
|
||||
"../../entrypoints/*",
|
||||
"../../panels/explain/*",
|
||||
"../../panels/network/*",
|
||||
|
||||
# TODO(crbug.com/1202788): Remove invalid dependents
|
||||
|
||||
+1
@@ -25,6 +25,7 @@ devtools_entrypoint("bundle") {
|
||||
visibility = [
|
||||
"../../../test/unittests/front_end/ui/components/*",
|
||||
"../../models/issues_manager/*",
|
||||
"../../panels/explain/*",
|
||||
"../../ui/components/docs/markdown_view/*",
|
||||
"../../ui/components/markdown_view/*",
|
||||
]
|
||||
|
||||
@@ -299,6 +299,7 @@ export enum ActionCategory {
|
||||
DEBUGGER = 'DEBUGGER',
|
||||
SOURCES = 'SOURCES',
|
||||
RENDERING = 'RENDERING',
|
||||
EXPLAIN = 'EXPLAIN',
|
||||
}
|
||||
|
||||
export function getLocalizedActionCategory(category: ActionCategory): Platform.UIString.LocalizedString {
|
||||
|
||||
@@ -32,6 +32,13 @@ import {GlassPane, MarginBehavior, SizeBehavior} from './GlassPane.js';
|
||||
import popoverStyles from './popover.css.legacy.js';
|
||||
|
||||
export class PopoverHelper {
|
||||
static createPopover = (): GlassPane => {
|
||||
const popover = new GlassPane();
|
||||
popover.registerRequiredCSS(popoverStyles);
|
||||
popover.setSizeBehavior(SizeBehavior.MeasureContent);
|
||||
popover.setMarginBehavior(MarginBehavior.Arrow);
|
||||
return popover;
|
||||
};
|
||||
private disableOnClick: boolean;
|
||||
private hasPadding: boolean;
|
||||
private getRequest: (arg0: MouseEvent) => PopoverRequest | null;
|
||||
@@ -193,10 +200,7 @@ export class PopoverHelper {
|
||||
}
|
||||
|
||||
private showPopover(document: Document): void {
|
||||
const popover = new GlassPane();
|
||||
popover.registerRequiredCSS(popoverStyles);
|
||||
popover.setSizeBehavior(SizeBehavior.MeasureContent);
|
||||
popover.setMarginBehavior(MarginBehavior.Arrow);
|
||||
const popover = PopoverHelper.createPopover();
|
||||
const request = this.scheduledRequest;
|
||||
if (!request) {
|
||||
return;
|
||||
|
||||
@@ -62,6 +62,7 @@ devtools_entrypoint("bundle") {
|
||||
"../../../../../test/unittests/front_end/entrypoints/meta/*",
|
||||
"../../../../panels/application/*",
|
||||
"../../../../panels/coverage/*",
|
||||
"../../../../panels/explain/*",
|
||||
"../../../../panels/network/*",
|
||||
"../../../../panels/protocol_monitor/*",
|
||||
"../../../../panels/sources/*",
|
||||
|
||||
@@ -48,6 +48,7 @@ group("front_end") {
|
||||
"panels/css_overview",
|
||||
"panels/elements",
|
||||
"panels/emulation",
|
||||
"panels/explain",
|
||||
"panels/issues",
|
||||
"panels/layers",
|
||||
"panels/lighthouse",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Copyright 2023 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.
|
||||
|
||||
import("../../../../../third_party/typescript/typescript.gni")
|
||||
|
||||
ts_library("explain") {
|
||||
testonly = true
|
||||
sources = [ "explain_test.ts" ]
|
||||
|
||||
deps = [
|
||||
"../../../../../front_end/panels/explain:bundle",
|
||||
"../../helpers",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
import * as Explain from '../../../../../front_end/panels/explain/explain.js';
|
||||
import * as Host from '../../../../../front_end/core/host/host.js';
|
||||
|
||||
const {assert} = chai;
|
||||
|
||||
describe('Explain', () => {
|
||||
it('should be available if bindings are defined', async () => {
|
||||
const original = Host.InspectorFrontendHost.InspectorFrontendHostInstance.explainCode;
|
||||
try {
|
||||
Host.InspectorFrontendHost.InspectorFrontendHostInstance.explainCode = undefined;
|
||||
assert(!Explain.isAvailable());
|
||||
Host.InspectorFrontendHost.InspectorFrontendHostInstance.explainCode = () => {};
|
||||
assert(Explain.isAvailable());
|
||||
} finally {
|
||||
Host.InspectorFrontendHost.InspectorFrontendHostInstance.explainCode = original;
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user