mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-04 18:29:23 +02:00
feat(demo): add chrome remote debugger (#537)
This commit is contained in:
parent
094b859791
commit
c6bd9e5304
10 changed files with 558 additions and 3 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -17,6 +17,7 @@
|
||||||
"DESERIALIZERS",
|
"DESERIALIZERS",
|
||||||
"ebml",
|
"ebml",
|
||||||
"Embedder",
|
"Embedder",
|
||||||
|
"entrypoints",
|
||||||
"fflate",
|
"fflate",
|
||||||
"fluentui",
|
"fluentui",
|
||||||
"genymobile",
|
"genymobile",
|
||||||
|
@ -49,6 +50,7 @@
|
||||||
"transferables",
|
"transferables",
|
||||||
"tsbuildinfo",
|
"tsbuildinfo",
|
||||||
"typeof",
|
"typeof",
|
||||||
|
"undici",
|
||||||
"webadb",
|
"webadb",
|
||||||
"webcodecs",
|
"webcodecs",
|
||||||
"webm",
|
"webm",
|
||||||
|
|
|
@ -64,7 +64,7 @@ module.exports = withPwa(
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Cross-Origin-Embedder-Policy",
|
key: "Cross-Origin-Embedder-Policy",
|
||||||
value: "require-corp",
|
value: "credentialless",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
"@yume-chan/stream-extra": "workspace:^0.0.19",
|
"@yume-chan/stream-extra": "workspace:^0.0.19",
|
||||||
"@yume-chan/stream-saver": "^2.0.6",
|
"@yume-chan/stream-saver": "^2.0.6",
|
||||||
"@yume-chan/struct": "workspace:^0.0.19",
|
"@yume-chan/struct": "workspace:^0.0.19",
|
||||||
|
"@yume-chan/undici-browser": "5.21.2-mod.9",
|
||||||
"fflate": "^0.7.4",
|
"fflate": "^0.7.4",
|
||||||
"mobx": "^6.7.0",
|
"mobx": "^6.7.0",
|
||||||
"mobx-react-lite": "^3.4.3",
|
"mobx-react-lite": "^3.4.3",
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import { makeStyles, mergeClasses, shorthands } from "@griffel/react";
|
import { makeStyles, mergeClasses, shorthands } from "@griffel/react";
|
||||||
import type { AppProps } from "next/app";
|
import type { AppProps } from "next/app";
|
||||||
|
import getConfig from "next/config";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
@ -16,7 +17,6 @@ import { Connect, ErrorDialogProvider } from "../components";
|
||||||
import "../styles/globals.css";
|
import "../styles/globals.css";
|
||||||
import { Icons } from "../utils";
|
import { Icons } from "../utils";
|
||||||
import { register as registerIcons } from "../utils/icons";
|
import { register as registerIcons } from "../utils/icons";
|
||||||
import getConfig from "next/config";
|
|
||||||
|
|
||||||
registerIcons();
|
registerIcons();
|
||||||
|
|
||||||
|
@ -71,6 +71,11 @@ const ROUTES = [
|
||||||
icon: Icons.Power,
|
icon: Icons.Power,
|
||||||
name: "Power Menu",
|
name: "Power Menu",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
url: "/chrome-devtools",
|
||||||
|
icon: Icons.WindowDevTools,
|
||||||
|
name: "Chrome Remote Debugging",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
url: "/bug-report",
|
url: "/bug-report",
|
||||||
icon: Icons.Bug,
|
icon: Icons.Bug,
|
||||||
|
@ -136,6 +141,10 @@ function App({ Component, pageProps }: AppProps) {
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
if ("noLayout" in Component) {
|
||||||
|
return <Component {...pageProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorDialogProvider>
|
<ErrorDialogProvider>
|
||||||
<Head>
|
<Head>
|
||||||
|
|
98
apps/demo/src/pages/chrome-devtools-frame.tsx
Normal file
98
apps/demo/src/pages/chrome-devtools-frame.tsx
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
function ChromeDevToolsFrame() {
|
||||||
|
useEffect(() => {
|
||||||
|
var WebSocketOriginal = globalThis.WebSocket;
|
||||||
|
globalThis.WebSocket = class WebSocket extends EventTarget {
|
||||||
|
public static readonly CONNECTING: 0 = 0;
|
||||||
|
public static readonly OPEN: 1 = 1;
|
||||||
|
public static readonly CLOSING: 2 = 2;
|
||||||
|
public static readonly CLOSED: 3 = 3;
|
||||||
|
|
||||||
|
public readonly CONNECTING: 0 = 0;
|
||||||
|
public readonly OPEN: 1 = 1;
|
||||||
|
public readonly CLOSING: 2 = 2;
|
||||||
|
public readonly CLOSED: 3 = 3;
|
||||||
|
|
||||||
|
public binaryType: BinaryType = "arraybuffer";
|
||||||
|
public readonly bufferedAmount: number = 0;
|
||||||
|
public readonly extensions: string = "";
|
||||||
|
|
||||||
|
public readonly protocol: string = "";
|
||||||
|
public readonly readyState: number = 1;
|
||||||
|
public readonly url: string;
|
||||||
|
|
||||||
|
private _port: MessagePort;
|
||||||
|
|
||||||
|
public onclose: ((this: WebSocket, ev: CloseEvent) => any) | null =
|
||||||
|
null;
|
||||||
|
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/error_event) */
|
||||||
|
public onerror: ((this: WebSocket, ev: Event) => any) | null = null;
|
||||||
|
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/message_event) */
|
||||||
|
public onmessage:
|
||||||
|
| ((this: WebSocket, ev: MessageEvent) => any)
|
||||||
|
| null = null;
|
||||||
|
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/open_event) */
|
||||||
|
public onopen: ((this: WebSocket, ev: Event) => any) | null = null;
|
||||||
|
|
||||||
|
constructor(url: string) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
console.log("WebSocket constructor", url);
|
||||||
|
this.url = url;
|
||||||
|
|
||||||
|
var channel = new MessageChannel();
|
||||||
|
this._port = channel.port1;
|
||||||
|
|
||||||
|
if (url.includes("/_next/")) {
|
||||||
|
this._port.close();
|
||||||
|
// @ts-ignore
|
||||||
|
return new WebSocketOriginal(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._port.onmessage = (e) => {
|
||||||
|
switch (e.data.type) {
|
||||||
|
case "open":
|
||||||
|
this.onopen?.(new Event("open"));
|
||||||
|
break;
|
||||||
|
case "message":
|
||||||
|
this.onmessage?.(
|
||||||
|
new MessageEvent("message", {
|
||||||
|
data: e.data.message,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "close":
|
||||||
|
this.onclose?.(new CloseEvent("close"));
|
||||||
|
this._port.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.postMessage({ type: "AdbWebSocket", url }, "*", [
|
||||||
|
channel.port2,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data: ArrayBuffer) {
|
||||||
|
this._port.postMessage({ type: "message", message: data });
|
||||||
|
}
|
||||||
|
|
||||||
|
public close() {
|
||||||
|
this._port.postMessage({ type: "close" });
|
||||||
|
this._port.close();
|
||||||
|
}
|
||||||
|
} as typeof WebSocket;
|
||||||
|
console.log("WebSocket hooked");
|
||||||
|
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.type = "module";
|
||||||
|
script.src = new URLSearchParams(location.search).get(
|
||||||
|
"script"
|
||||||
|
) as string;
|
||||||
|
document.body.appendChild(script);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ChromeDevToolsFrame.noLayout = true;
|
||||||
|
export default ChromeDevToolsFrame;
|
406
apps/demo/src/pages/chrome-devtools.tsx
Normal file
406
apps/demo/src/pages/chrome-devtools.tsx
Normal file
|
@ -0,0 +1,406 @@
|
||||||
|
import { Link, Stack } from "@fluentui/react";
|
||||||
|
import { makeStyles } from "@griffel/react";
|
||||||
|
import { AdbSocket } from "@yume-chan/adb";
|
||||||
|
import {
|
||||||
|
Consumable,
|
||||||
|
ReadableStreamDefaultReader,
|
||||||
|
WritableStreamDefaultWriter,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
import {
|
||||||
|
Agent,
|
||||||
|
Client,
|
||||||
|
Duplex,
|
||||||
|
Pool,
|
||||||
|
Symbols,
|
||||||
|
WebSocket,
|
||||||
|
request,
|
||||||
|
setGlobalDispatcher,
|
||||||
|
} from "@yume-chan/undici-browser";
|
||||||
|
import {
|
||||||
|
action,
|
||||||
|
makeAutoObservable,
|
||||||
|
observable,
|
||||||
|
reaction,
|
||||||
|
runInAction,
|
||||||
|
} from "mobx";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { NextPage } from "next";
|
||||||
|
import getConfig from "next/config";
|
||||||
|
import Head from "next/head";
|
||||||
|
import type { Socket } from "node:net";
|
||||||
|
import { useCallback, useEffect } from "react";
|
||||||
|
import { GLOBAL_STATE } from "../state";
|
||||||
|
import { RouteStackProps } from "../utils";
|
||||||
|
|
||||||
|
class AdbUndiciSocket extends Duplex {
|
||||||
|
private _socket: AdbSocket;
|
||||||
|
private _reader: ReadableStreamDefaultReader<Uint8Array>;
|
||||||
|
private _writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;
|
||||||
|
|
||||||
|
constructor(socket: AdbSocket) {
|
||||||
|
super();
|
||||||
|
this._socket = socket;
|
||||||
|
this._reader = this._socket.readable.getReader();
|
||||||
|
this._writer = this._socket.writable.getWriter();
|
||||||
|
this._socket.end.then(() => this.emit("end"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async _read(size: number): Promise<void> {
|
||||||
|
const result = await this._reader.read();
|
||||||
|
if (result.done) {
|
||||||
|
this.emit("end");
|
||||||
|
} else {
|
||||||
|
this.push(result.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _write(
|
||||||
|
chunk: any,
|
||||||
|
encoding: BufferEncoding,
|
||||||
|
callback: (error?: Error | null | undefined) => void
|
||||||
|
): Promise<void> {
|
||||||
|
const consumable = new Consumable(chunk);
|
||||||
|
try {
|
||||||
|
await this._writer.write(consumable);
|
||||||
|
callback();
|
||||||
|
} catch (e) {
|
||||||
|
callback(e as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _final(
|
||||||
|
callback: (error?: Error | null | undefined) => void
|
||||||
|
): Promise<void> {
|
||||||
|
await this._socket.close();
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _destroy(
|
||||||
|
error: Error | null,
|
||||||
|
callback: (error: Error | null) => void
|
||||||
|
): Promise<void> {
|
||||||
|
await this._socket.close();
|
||||||
|
callback(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const agent = new Agent({
|
||||||
|
factory(origin, opts) {
|
||||||
|
const pool = new Pool(origin, {
|
||||||
|
...opts,
|
||||||
|
factory(origin, opts) {
|
||||||
|
const client = new Client(origin, opts);
|
||||||
|
// Remote debugging validates `Host` header to defend against DNS rebinding attacks.
|
||||||
|
// But we can only pass socket name using hostname, so we need to override it.
|
||||||
|
(client as any)[Symbols.kHostHeader] = "Host: localhost\r\n";
|
||||||
|
return client;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return pool;
|
||||||
|
},
|
||||||
|
async connect(options, callback) {
|
||||||
|
const socket = await GLOBAL_STATE.device!.createSocket(
|
||||||
|
"localabstract:" + options.hostname
|
||||||
|
);
|
||||||
|
callback(null, new AdbUndiciSocket(socket) as unknown as Socket);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// WebSocket only uses global dispatcher
|
||||||
|
setGlobalDispatcher(agent);
|
||||||
|
|
||||||
|
interface Page {
|
||||||
|
description: string;
|
||||||
|
devtoolsFrontendUrl: string;
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
webSocketDebuggerUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Version {
|
||||||
|
"Android-Package": string;
|
||||||
|
Browser: string;
|
||||||
|
"Protocol-Version": string;
|
||||||
|
"User-Agent": string;
|
||||||
|
"V8-Version": string;
|
||||||
|
"WebKit-Version": string;
|
||||||
|
webSocketDebuggerUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:chrome/browser/devtools/device/devtools_device_discovery.cc;l=36;drc=4651cec294d1542d6673a89190e192e20de03240
|
||||||
|
|
||||||
|
async function getPages(socket: string) {
|
||||||
|
const response = await request(`http://${socket}/json`);
|
||||||
|
const body = await response.body.json();
|
||||||
|
return body as Page[];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getVersion(socket: string) {
|
||||||
|
const response = await request(`http://${socket}/json/version`);
|
||||||
|
const body = await response.body.json();
|
||||||
|
return body as Version;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function focusPage(socket: string, page: Page) {
|
||||||
|
await request(`http://${socket}/json/activate/${page.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function closePage(socket: string, page: Page) {
|
||||||
|
await request(`http://${socket}/json/close/${page.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
publicRuntimeConfig: { basePath },
|
||||||
|
} = getConfig();
|
||||||
|
|
||||||
|
function getPopupParams(page: Page) {
|
||||||
|
const frontendUrl = page.devtoolsFrontendUrl;
|
||||||
|
const [frontendBase, params] = frontendUrl.split("?");
|
||||||
|
const script = frontendBase.startsWith(
|
||||||
|
"https://aka.ms/docs-landing-page/serve_rev/"
|
||||||
|
)
|
||||||
|
? // Edge
|
||||||
|
frontendBase
|
||||||
|
.replace(
|
||||||
|
"https://aka.ms/docs-landing-page/serve_rev/",
|
||||||
|
"https://devtools.azureedge.net/serve_file/"
|
||||||
|
)
|
||||||
|
.replace("inspector.html", "entrypoints/inspector/inspector.js")
|
||||||
|
: // Chrome
|
||||||
|
frontendBase.replace(
|
||||||
|
"inspector.html",
|
||||||
|
"front_end/entrypoints/inspector/inspector.js"
|
||||||
|
);
|
||||||
|
return { script, params };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Browser {
|
||||||
|
socket: string;
|
||||||
|
version: Version;
|
||||||
|
pages: Page[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATE = makeAutoObservable(
|
||||||
|
{
|
||||||
|
browsers: [] as Browser[],
|
||||||
|
intervalId: null as NodeJS.Timeout | null,
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
browsers: observable.deep,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
async function getBrowsers() {
|
||||||
|
const device = GLOBAL_STATE.device!;
|
||||||
|
const sockets = await device.subprocess.spawnAndWaitLegacy(
|
||||||
|
`cat /proc/net/unix | grep -E "@chrome_devtools_remote|@chrome_devtools_remote_[0-9]+" | awk '{print substr($8, 2)}'`
|
||||||
|
);
|
||||||
|
const browsers: Browser[] = [];
|
||||||
|
for (const socket of sockets.split("\n").filter(Boolean)) {
|
||||||
|
if (browsers.some((browser) => browser.socket == socket)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const version = await getVersion(socket);
|
||||||
|
const pages = await getPages(socket);
|
||||||
|
console.log(socket, version, pages);
|
||||||
|
browsers.push({ socket, version, pages });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(socket, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runInAction(() => {
|
||||||
|
STATE.browsers = browsers;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
() => [GLOBAL_STATE.device, STATE.visible] as const,
|
||||||
|
([device, visible]) => {
|
||||||
|
if (!device || !visible) {
|
||||||
|
STATE.browsers = [];
|
||||||
|
if (STATE.intervalId) {
|
||||||
|
clearInterval(STATE.intervalId);
|
||||||
|
STATE.intervalId = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
STATE.intervalId = setInterval(() => {
|
||||||
|
getBrowsers();
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
getBrowsers();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function getBrowserName(version: Version) {
|
||||||
|
const [name, versionNumber] = version.Browser.split("/");
|
||||||
|
return `${name} (${versionNumber})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useClasses = makeStyles({
|
||||||
|
header: {
|
||||||
|
marginTop: "4px",
|
||||||
|
marginBottom: "4px",
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
marginLeft: "8px",
|
||||||
|
color: "#999",
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
marginRight: "12px",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const ChromeDevToolsPage: NextPage = observer(function ChromeDevTools() {
|
||||||
|
const classes = useClasses();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
runInAction(() => {
|
||||||
|
STATE.visible = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return action(() => {
|
||||||
|
STATE.visible = false;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleInspectClick = useCallback((socket: string, page: Page) => {
|
||||||
|
const { script, params } = getPopupParams(page);
|
||||||
|
const childWindow = window.open(
|
||||||
|
`${basePath}/chrome-devtools-frame?script=${script}&${params}`,
|
||||||
|
"_blank",
|
||||||
|
"popup"
|
||||||
|
)!;
|
||||||
|
childWindow.addEventListener("message", (e) => {
|
||||||
|
if (
|
||||||
|
typeof e.data !== "object" ||
|
||||||
|
!"type in e.data" ||
|
||||||
|
e.data.type !== "AdbWebSocket"
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(e.data.url as string);
|
||||||
|
url.host = socket;
|
||||||
|
|
||||||
|
const port = e.ports[0];
|
||||||
|
|
||||||
|
const ws = new WebSocket(url);
|
||||||
|
ws.binaryType = "arraybuffer";
|
||||||
|
ws.onopen = () => {
|
||||||
|
port.postMessage({ type: "open" });
|
||||||
|
};
|
||||||
|
ws.onclose = () => {
|
||||||
|
port.postMessage({ type: "close" });
|
||||||
|
port.close();
|
||||||
|
};
|
||||||
|
ws.onmessage = (e) => {
|
||||||
|
const { data } = e;
|
||||||
|
port.postMessage({
|
||||||
|
type: "message",
|
||||||
|
message: data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
port.onmessage = (e) => {
|
||||||
|
switch (e.data.type) {
|
||||||
|
case "message":
|
||||||
|
ws.send(e.data.message);
|
||||||
|
break;
|
||||||
|
case "close":
|
||||||
|
ws.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
childWindow.addEventListener("close", () => {
|
||||||
|
ws.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", () => {
|
||||||
|
port.postMessage({ type: "close" });
|
||||||
|
port.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleFocusClick = useCallback((socket: string, page: Page) => {
|
||||||
|
focusPage(socket, page);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCloseClick = useCallback((socket: string, page: Page) => {
|
||||||
|
closePage(socket, page);
|
||||||
|
getBrowsers();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack {...RouteStackProps}>
|
||||||
|
<Head>
|
||||||
|
<title>Chrome Remote Debugging - Tango</title>
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
{STATE.browsers.map((browser) => (
|
||||||
|
<>
|
||||||
|
{browser.version && (
|
||||||
|
<h3 className={classes.header}>
|
||||||
|
{getBrowserName(browser.version)}
|
||||||
|
</h3>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{browser.pages.map((page) => (
|
||||||
|
<div key={page.id}>
|
||||||
|
<div>
|
||||||
|
{page.title ? (
|
||||||
|
<span
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: page.title,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<i>No Title</i>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span className={classes.url}>
|
||||||
|
{page.url || <i>No URL</i>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
className={classes.link}
|
||||||
|
onClick={() =>
|
||||||
|
handleInspectClick(browser.socket, page)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Inspect
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
className={classes.link}
|
||||||
|
onClick={() =>
|
||||||
|
handleFocusClick(browser.socket, page)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Focus
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
className={classes.link}
|
||||||
|
onClick={() =>
|
||||||
|
handleCloseClick(browser.socket, page)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ChromeDevToolsPage;
|
|
@ -1,6 +1,7 @@
|
||||||
import { registerIcons } from "@fluentui/react";
|
import { registerIcons } from "@fluentui/react";
|
||||||
import {
|
import {
|
||||||
AddCircleRegular,
|
AddCircleRegular,
|
||||||
|
WindowDevToolsRegular,
|
||||||
ArrowClockwiseRegular,
|
ArrowClockwiseRegular,
|
||||||
ArrowRotateClockwiseRegular,
|
ArrowRotateClockwiseRegular,
|
||||||
ArrowRotateCounterclockwiseRegular,
|
ArrowRotateCounterclockwiseRegular,
|
||||||
|
@ -108,6 +109,7 @@ export function register() {
|
||||||
Warning: <WarningRegular style={STYLE} />,
|
Warning: <WarningRegular style={STYLE} />,
|
||||||
WifiSettings: <WifiSettingsRegular style={STYLE} />,
|
WifiSettings: <WifiSettingsRegular style={STYLE} />,
|
||||||
WindowConsole: <WindowConsoleRegular style={STYLE} />,
|
WindowConsole: <WindowConsoleRegular style={STYLE} />,
|
||||||
|
WindowDevTools: <WindowDevToolsRegular style={STYLE} />,
|
||||||
|
|
||||||
// Required by @fluentui/react
|
// Required by @fluentui/react
|
||||||
Checkmark: <CheckmarkRegular style={STYLE} />,
|
Checkmark: <CheckmarkRegular style={STYLE} />,
|
||||||
|
@ -179,6 +181,8 @@ const Icons = {
|
||||||
Warning: "Warning",
|
Warning: "Warning",
|
||||||
WifiSettings: "WifiSettings",
|
WifiSettings: "WifiSettings",
|
||||||
WindowConsole: "WindowConsole",
|
WindowConsole: "WindowConsole",
|
||||||
|
WindowDevTools: "WindowDevTools",
|
||||||
|
|
||||||
Document20: "Document20",
|
Document20: "Document20",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
21
common/config/rush/pnpm-lock.yaml
generated
21
common/config/rush/pnpm-lock.yaml
generated
|
@ -35,6 +35,7 @@ importers:
|
||||||
'@yume-chan/stream-extra': workspace:^0.0.19
|
'@yume-chan/stream-extra': workspace:^0.0.19
|
||||||
'@yume-chan/stream-saver': ^2.0.6
|
'@yume-chan/stream-saver': ^2.0.6
|
||||||
'@yume-chan/struct': workspace:^0.0.19
|
'@yume-chan/struct': workspace:^0.0.19
|
||||||
|
'@yume-chan/undici-browser': 5.21.2-mod.9
|
||||||
eslint: ^8.36.0
|
eslint: ^8.36.0
|
||||||
eslint-config-next: 13.2.4
|
eslint-config-next: 13.2.4
|
||||||
fflate: ^0.7.4
|
fflate: ^0.7.4
|
||||||
|
@ -74,6 +75,7 @@ importers:
|
||||||
'@yume-chan/stream-extra': link:../../libraries/stream-extra
|
'@yume-chan/stream-extra': link:../../libraries/stream-extra
|
||||||
'@yume-chan/stream-saver': 2.0.6
|
'@yume-chan/stream-saver': 2.0.6
|
||||||
'@yume-chan/struct': link:../../libraries/struct
|
'@yume-chan/struct': link:../../libraries/struct
|
||||||
|
'@yume-chan/undici-browser': 5.21.2-mod.9
|
||||||
fflate: 0.7.4
|
fflate: 0.7.4
|
||||||
mobx: 6.8.0
|
mobx: 6.8.0
|
||||||
mobx-react-lite: 3.4.3_woojb62cqeyk443mbl7msrwu2e
|
mobx-react-lite: 3.4.3_woojb62cqeyk443mbl7msrwu2e
|
||||||
|
@ -2950,6 +2952,13 @@ packages:
|
||||||
resolution: {integrity: sha512-DzRADjLoHcz18ocgGHvLIanapxygX3o9dlWwE32EUZqhyAsopfdvZ79ttR9+7pqAXIQamP9M4mbDy8hHgFKOIA==}
|
resolution: {integrity: sha512-DzRADjLoHcz18ocgGHvLIanapxygX3o9dlWwE32EUZqhyAsopfdvZ79ttR9+7pqAXIQamP9M4mbDy8hHgFKOIA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@yume-chan/undici-browser/5.21.2-mod.9:
|
||||||
|
resolution: {integrity: sha512-rkpYsC6E9o26D4d38FYCT2I+9NgQPyMzIRCSjo9ZKNpnpP/QL02U9h57LqXGaY1l/nBRgoAlK6ctdMwVClAQjw==}
|
||||||
|
engines: {node: '>=12.18'}
|
||||||
|
dependencies:
|
||||||
|
busboy: 1.6.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/abab/2.0.6:
|
/abab/2.0.6:
|
||||||
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
|
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -3361,6 +3370,13 @@ packages:
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/busboy/1.6.0:
|
||||||
|
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||||
|
engines: {node: '>=10.16.0'}
|
||||||
|
dependencies:
|
||||||
|
streamsearch: 1.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cacheable-request/2.1.4:
|
/cacheable-request/2.1.4:
|
||||||
resolution: {integrity: sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==}
|
resolution: {integrity: sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -7293,6 +7309,11 @@ packages:
|
||||||
internal-slot: 1.0.5
|
internal-slot: 1.0.5
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/streamsearch/1.1.0:
|
||||||
|
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/strict-uri-encode/1.1.0:
|
/strict-uri-encode/1.1.0:
|
||||||
resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==}
|
resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
|
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
|
||||||
{
|
{
|
||||||
"pnpmShrinkwrapHash": "6c01283b613e0e32d56a30dc621fbddac47bd437",
|
"pnpmShrinkwrapHash": "e7166d9b8db90548f97ba0dfa755b9b6a1334125",
|
||||||
"preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
|
"preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,11 @@ export class AdbSocketController
|
||||||
return this._closed;
|
return this._closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _endPromiseResolver = new PromiseResolver<void>();
|
||||||
|
public get end() {
|
||||||
|
return this._endPromiseResolver.promise;
|
||||||
|
}
|
||||||
|
|
||||||
private _socket: AdbSocket;
|
private _socket: AdbSocket;
|
||||||
public get socket() {
|
public get socket() {
|
||||||
return this._socket;
|
return this._socket;
|
||||||
|
@ -99,6 +104,7 @@ export class AdbSocketController
|
||||||
dispose: () => {
|
dispose: () => {
|
||||||
// Error out the pending writes
|
// Error out the pending writes
|
||||||
this._writePromise?.reject(new Error("Socket closed"));
|
this._writePromise?.reject(new Error("Socket closed"));
|
||||||
|
this._endPromiseResolver.resolve();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -196,6 +202,14 @@ export class AdbSocket
|
||||||
return this._controller.writable;
|
return this._controller.writable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get closed(): boolean {
|
||||||
|
return this._controller.closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get end(): Promise<void> {
|
||||||
|
return this._controller.end;
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(controller: AdbSocketController) {
|
public constructor(controller: AdbSocketController) {
|
||||||
this._controller = controller;
|
this._controller = controller;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue