Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"test:e2e": "pnpm -C e2e run test",
"test": "vitest",
"typecheck": "vue-tsc -b",
"watch": "turbo watch build"
"watch": "turbo watch build",
"zip": "pnpm run zip:webext",
"zip:webext": "turbo run build --filter=@vitejs/devtools-webext && pnpm --filter @vitejs/devtools-webext run zip"
},
"scripts-info": {
"actionspack": "Update pinned GitHub Actions versions",
Expand All @@ -53,7 +55,9 @@
"test:e2e": "Run end-to-end tests",
"test": "Run unit tests with Vitest",
"typecheck": "Type-check all packages with vue-tsc",
"watch": "Rebuild changed packages on file change via turbo watch"
"watch": "Rebuild changed packages on file change via turbo watch",
"zip": "Build and package the browser extension",
"zip:webext": "Build and package the browser extension as a zip file"
},
"devDependencies": {
"@antfu/eslint-config": "catalog:devtools",
Expand Down
81 changes: 0 additions & 81 deletions packages/kit/src/client/remote.test.ts

This file was deleted.

9 changes: 9 additions & 0 deletions packages/webext/app/background/background.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!doctype html>
<html lang="en">
<head>
<title>Vite DevTools Background</title>
</head>
<body>
<script src="../../dist/devtools-bg.js" type="module"></script>
</body>
</html>
79 changes: 79 additions & 0 deletions packages/webext/app/panel/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { getInspectedWindowConnection } from './inspected-window'

const viewerUrl = ref<string | null>(null)
const errorMessage = ref<string | null>(null)
const status = ref<'loading' | 'unavailable'>('loading')
const METADATA_RETRY_COUNT = 50
const METADATA_RETRY_DELAY = 100

function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms))
}

const statusText = computed(() => {
return status.value === 'loading'
? 'Connecting to Vite DevTools...'
: 'Vite DevTools unavailable'
})

async function resolveInspectedWindowConnection() {
for (let i = 0; i < METADATA_RETRY_COUNT; i++) {
const connection = await getInspectedWindowConnection()
if (connection)
return connection

await sleep(METADATA_RETRY_DELAY)
}
}

async function initialize() {
status.value = 'loading'
errorMessage.value = null

try {
const connection = await resolveInspectedWindowConnection()
if (!connection)
throw new Error('Unable to reconnect to the inspected page.')

viewerUrl.value = new URL('./', connection.metaBaseUrl).href
}
catch (err) {
status.value = 'unavailable'
errorMessage.value = err instanceof Error ? err.message : String(err)
}
}

function handleInspectedWindowNavigated() {
location.reload()
}

onMounted(() => {
chrome.devtools.network.onNavigated.addListener(handleInspectedWindowNavigated)
initialize()
})

onUnmounted(() => {
chrome.devtools.network.onNavigated.removeListener(handleInspectedWindowNavigated)
})
</script>

<template>
<iframe
v-if="viewerUrl"
:src="viewerUrl"
title="Vite DevTools"
class="h-screen w-screen border-0"
/>
<div v-else class="h-screen w-screen flex items-center justify-center bg-[#1e1e1e] px-4 py-3 text-center text-[#cccccc]">
<div>
<p class="m0 text-base">
{{ statusText }}
</p>
<p v-if="status === 'unavailable'" class="mb0 mt1 text-sm text-[#999999]">
{{ errorMessage }}
</p>
</div>
</div>
</template>
13 changes: 13 additions & 0 deletions packages/webext/app/panel/devtools-panel.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite DevTools Panel</title>
<meta name="description" content="Vite DevTools browser extension panel" />
</head>
<body class="font-sans">
<div id="app"></div>
<script type="module" src="./main.ts"></script>
</body>
</html>
53 changes: 53 additions & 0 deletions packages/webext/app/panel/inspected-window.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { DevframeConnection } from 'devframe/client'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { connectionEval, getInspectedWindowConnection } from './inspected-window'

function stubInspectedWindow(result: DevframeConnection | null) {
vi.stubGlobal('chrome', {
devtools: {
inspectedWindow: {
eval: vi.fn((_expression, callback) => callback(result)),
},
},
})
}

describe('inspected window metadata', () => {
afterEach(() => {
vi.unstubAllGlobals()
vi.restoreAllMocks()
})

it('reads the standard Devframe connection from the inspected page', async () => {
const connection: DevframeConnection = {
connectionMeta: {
backend: 'websocket',
websocket: { path: '__ws' },
},
metaBaseUrl: 'http://localhost:5173/__devtools/__connection.json',
authToken: 'trusted-token',
}
stubInspectedWindow(connection)

await expect(getInspectedWindowConnection()).resolves.toBe(connection)
expect(connectionEval).toContain('__DEVFRAME_CONNECTION__')
})

it('returns null until the injected client publishes the connection', async () => {
stubInspectedWindow(null)

await expect(getInspectedWindowConnection()).resolves.toBeNull()
})

it('rejects an incomplete connection snapshot', async () => {
stubInspectedWindow({
connectionMeta: {
backend: 'websocket',
websocket: 7812,
},
metaBaseUrl: '',
})

await expect(getInspectedWindowConnection()).resolves.toBeNull()
})
})
21 changes: 21 additions & 0 deletions packages/webext/app/panel/inspected-window.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { DevframeConnection } from 'devframe/client'
import { DEVFRAME_CONNECTION_KEY } from 'devframe/constants'

export const connectionEval
= `window[${JSON.stringify(DEVFRAME_CONNECTION_KEY)}] || undefined`

function inspectWindow(): Promise<DevframeConnection | null> {
return new Promise((resolve) => {
chrome.devtools.inspectedWindow.eval<DevframeConnection>(connectionEval, (connection, exceptionInfo) => {
resolve(exceptionInfo || !connection ? null : connection)
})
})
}

export async function getInspectedWindowConnection(): Promise<DevframeConnection | null> {
const connection = await inspectWindow()
if (!connection?.connectionMeta || !connection.metaBaseUrl)
return null

return connection
}
15 changes: 15 additions & 0 deletions packages/webext/app/panel/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createApp, h, Suspense } from 'vue'
import App from './App.vue'

import '@unocss/reset/tailwind.css'
import './styles/main.css'
import 'uno.css'

const app = createApp({
render: () => h(Suspense, {}, {
default: () => h(App),
fallback: () => h('div', 'Loading...'),
}),
})

app.mount('#app')
13 changes: 13 additions & 0 deletions packages/webext/app/panel/styles/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
html,
body,
#app {
height: 100%;
margin: 0;
padding: 0;
}

@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}
Loading
Loading