chrome-devtools-frontend
Version:
Chrome DevTools UI
165 lines (138 loc) • 5.25 kB
text/typescript
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {Overlay} from './common.js';
export interface GreenDevAnchorsHighlight {
nodeId: number;
x: number;
y: number;
}
export type GreenDevAnchorsToolMessage = {
highlightType: 'greenDevFloaty',
command: 'setInputValue',
nodeId: number,
value: string,
}|{
highlightType: 'greenDevFloaty',
command: 'debugMessage',
message: string,
}|{
highlightType: 'greenDevFloaty',
command: 'openDevTools',
nodeId: number,
}|{
highlightType: 'greenDevFloaty',
command: 'restoreFloaty',
nodeId: number,
};
function consoleLog(message: string) {
window.InspectorOverlayHost.send({highlightType: 'greenDevFloaty', command: 'debugMessage', message});
}
export interface GreenDevAnchorsDispatchMessage {
nodeId: number;
value: string;
}
export class GreenDevAnchorsOverlay extends Overlay {
#anchorsContainer!: HTMLElement;
#anchorsByNodeId = new Map<number, HTMLElement>();
override install() {
consoleLog('GreenDevAnchorsOverlay.install() called');
this.document.body.classList.add('fill');
const canvas = this.document.createElement('canvas');
canvas.id = 'canvas';
canvas.classList.add('fill');
canvas.style.pointerEvents = 'none';
this.document.body.append(canvas);
const anchorsContainer = this.document.createElement('div');
anchorsContainer.id = 'green-dev-anchors-container';
this.document.body.append(anchorsContainer);
this.#anchorsContainer = anchorsContainer;
this.setCanvas(canvas);
super.install();
}
override uninstall() {
consoleLog('GreenDevAnchorsOverlay.uninstall() called');
this.document.body.classList.remove('fill');
if (this.#anchorsContainer && this.#anchorsContainer.parentElement) {
this.#anchorsContainer.parentElement.removeChild(this.#anchorsContainer);
}
// Clear the map as the elements are removed from DOM.
this.#anchorsByNodeId.clear();
super.uninstall();
}
drawGreenDevAnchors(highlights: GreenDevAnchorsHighlight[]) {
if (this.#anchorsContainer && !this.#anchorsContainer.isConnected) {
// The container was removed from the DOM (likely by PersistentOverlay.uninstall cleaning up),
// but this overlay still thinks it's installed. We need to re-install to restore the DOM.
try {
this.uninstall();
} catch (e) {
console.error('Error during uninstall in drawGreenDevAnchors:', e);
}
this.install();
}
const newAnchorsByNodeId = new Map<number, HTMLElement>();
for (const highlight of highlights) {
const {x, y, nodeId} = highlight;
let anchor = this.#anchorsByNodeId.get(nodeId);
if (anchor) {
anchor.style.left = `${x}px`;
anchor.style.top = `${y}px`;
// Remove from the old map so only anchors that need to be deleted remain.
this.#anchorsByNodeId.delete(nodeId);
} else {
const newAnchor = this.drawGreenDevAnchor(highlight);
if (!newAnchor) {
continue;
}
anchor = newAnchor;
anchor.style.left = `${x}px`;
anchor.style.top = `${y}px`;
this.#anchorsContainer.append(anchor);
}
newAnchorsByNodeId.set(nodeId, anchor);
}
// Remove any anchors that were not present in the new highlights list.
for (const anchor of this.#anchorsByNodeId.values()) {
anchor.remove();
}
this.#anchorsByNodeId = newAnchorsByNodeId;
}
private drawGreenDevAnchor(highlight: GreenDevAnchorsHighlight): HTMLElement {
const {nodeId} = highlight;
const anchor = this.document.createElement('div');
anchor.classList.add('green-dev-anchor-minimal');
const geminiIcon = this.document.createElement('div');
geminiIcon.classList.add('green-dev-anchor-icon-gemini');
anchor.append(geminiIcon);
const openIcon = this.document.createElement('div');
openIcon.classList.add('green-dev-anchor-icon-open');
anchor.append(openIcon);
const onEvent = (event: MouseEvent) => {
event.stopPropagation();
event.preventDefault();
};
openIcon.addEventListener('mousedown', onEvent);
openIcon.addEventListener('mouseup', onEvent);
openIcon.addEventListener('mousemove', onEvent);
openIcon.addEventListener('mouseenter', onEvent);
openIcon.addEventListener('mouseleave', onEvent);
openIcon.addEventListener('click', (event: MouseEvent) => {
onEvent(event);
window.InspectorOverlayHost.send({highlightType: 'greenDevFloaty', command: 'openDevTools', nodeId});
});
anchor.addEventListener('mousedown', onEvent);
anchor.addEventListener('mouseup', onEvent);
anchor.addEventListener('mousemove', onEvent);
anchor.addEventListener('mouseenter', onEvent);
anchor.addEventListener('mouseleave', onEvent);
anchor.addEventListener('click', (event: MouseEvent) => {
if (event.target !== anchor && !anchor.contains(event.target as Node)) {
return;
}
onEvent(event);
window.InspectorOverlayHost.send({highlightType: 'greenDevFloaty', command: 'restoreFloaty', nodeId});
});
return anchor;
}
}