Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

code: Use vscode.env.asExternalUri to generate preview uris #905

Merged
merged 7 commits into from
Oct 7, 2024
Merged
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
1 change: 1 addition & 0 deletions code/changes/896.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The preview window should now work when using Codespaces
116 changes: 85 additions & 31 deletions code/src/node/preview.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as vscode from 'vscode'
import { OutputChannelLogger } from '../common/log'
import { EsbonioClient } from './client'
import { Commands, Events, Notifications } from '../common/constants'
import { Commands, Events } from '../common/constants'
import { ShowDocumentParams, Range } from 'vscode-languageclient'
import { URLSearchParams } from 'url'

interface PreviewFileParams {
uri: string
Expand Down Expand Up @@ -43,7 +44,7 @@ export class PreviewManager {

client.addHandler(
"window/showDocument",
(params: { params: ShowDocumentParams, default: any }) => this.showDocument(params)
async (params: { params: ShowDocumentParams, default: any }) => await this.showDocument(params)
)

client.addHandler(
Expand Down Expand Up @@ -131,7 +132,7 @@ export class PreviewManager {
this.currentUri = editor.document.uri
}

private showDocument(req: { params: ShowDocumentParams, default: any }) {
private async showDocument(req: { params: ShowDocumentParams, default: any }) {
let params = req.params
if (!params.external) {
return this.showInternalDocument(params)
Expand All @@ -141,7 +142,55 @@ export class PreviewManager {
return
}

this.panel.webview.postMessage({ 'show': params.uri })
let panel = this.panel

let previewUri = vscode.Uri.parse(params.uri)
let externalUri = await vscode.env.asExternalUri(previewUri)

// Annoyingly, asExternalUri doesn't preserve attributes like `path` or `query`
previewUri = previewUri.with({ scheme: externalUri.scheme, authority: externalUri.authority })

let origin = `${previewUri.scheme}://${previewUri.authority}`
panel.webview.html = this.getWebViewHTML(origin)

// Don't forget as also need an external uri for the websocket connection
let queryParams = new URLSearchParams(previewUri.query)
let ws = queryParams.get('ws')
if (!ws) {
this.logger.error("Missing websocket uri, features like sync scrolling will not be available.")
this.displayUri(previewUri, queryParams)
return
}

// We need to also pass the websocket uri through `asExternalUri` however, it only works for
// http(s) uris
let wsUri = vscode.Uri.parse(ws)
wsUri = await vscode.env.asExternalUri(wsUri.with({ scheme: 'http' }))
wsUri = wsUri.with({ scheme: wsUri.scheme === 'https' ? 'wss' : 'ws' })
queryParams.set('ws', wsUri.toString())

this.displayUri(previewUri, queryParams)
}

/**
* Display the given uri in the preview pane.
*
* @param uri The base uri to present
* @param queryParams The query parameters to include
* @returns
*/
private displayUri(uri: vscode.Uri, queryParams: URLSearchParams) {

// As far as I can tell, there isn't a way to convince vscode's URI type to encode the
// uri in a way that does not break query parameters (since it converts ?x=y to ?x%3Dy).
// It also appears to be intended behavior see: https://github.com/Microsoft/vscode/issues/8466
//
// So we need to do it ourselves.
let query = queryParams.toString()
let displayUri = `${uri.with({ query: '' })}?${query}`

this.logger.debug(`Displaying uri: ${displayUri}`)
this.panel?.webview.postMessage({ 'show': displayUri })
}

private showInternalDocument(params: ShowDocumentParams) {
Expand Down Expand Up @@ -179,18 +228,46 @@ export class PreviewManager {
{ enableScripts: true, retainContextWhenHidden: true }
)

// The webview will notify us when the page has finished loading.
// Which should also mean the websocket connection is up and running.
// Try and sync up the view to the editor.
this.panel.webview.onDidReceiveMessage(message => {
if (!message.ready) {
return
}

let editor = findEditorFor(this.currentUri)
if (editor) {
this.scrollView(editor)
}
})

this.panel.onDidDispose(() => {
this.panel = undefined
this.currentUri = undefined
})

return this.panel
}

private getWebViewHTML(origin: string): string {

let scriptNonce = getNonce()
let cssNonce = getNonce()

this.panel.webview.html = `
return `
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; style-src 'nonce-${cssNonce}'; script-src 'nonce-${scriptNonce}'; frame-src http://localhost:*/" />
content="default-src 'none';
style-src 'nonce-${cssNonce}';
style-src-attr 'unsafe-inline';
script-src 'nonce-${scriptNonce}';
frame-src ${origin}/" />

<style nonce="${cssNonce}">
* { box-sizing: border-box;}
Expand Down Expand Up @@ -289,8 +366,6 @@ export class PreviewManager {
const noContent = document.getElementById("no-content")
const status = document.getElementById("status")

console.debug(window.location)

// Restore previous page?
const previousState = vscode.getState()
if (previousState && previousState.url) {
Expand All @@ -302,7 +377,7 @@ export class PreviewManager {
let message = event.data

// Control messages coming from the webview hosting this page
if (event.origin.startsWith("vscode-webview://")) {
if (event.origin === window.location.origin) {

if (message.show === "<nothing>") {
status.style.display = "none"
Expand All @@ -322,7 +397,7 @@ export class PreviewManager {
}

// Control messages coming from the webpage being shown.
if (event.origin.startsWith("http://localhost:")) {
if (event.origin.startsWith("${origin}")) {
if (message.ready) {
status.style.display = "none"
noContent.style.display = "none"
Expand All @@ -335,27 +410,6 @@ export class PreviewManager {

</html>
`

// The webview will notify us when the page has finished loading.
// Which should also mean the websocket connection is up and running.
// Try and sync up the view to the editor.
this.panel.webview.onDidReceiveMessage(message => {
if (!message.ready) {
return
}

let editor = findEditorFor(this.currentUri)
if (editor) {
this.scrollView(editor)
}
})

this.panel.onDidDispose(() => {
this.panel = undefined
this.currentUri = undefined
})

return this.panel
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,15 @@ async def show_preview_uri(self) -> Optional[Uri]:
server = await self.preview
webview = await self.webview

query_params: dict[str, Any] = dict(ws=webview.port)
host = "localhost"
query_params: dict[str, Any] = dict(ws=f"ws://{host}:{webview.port}")

if self.config.show_line_markers:
query_params["show-markers"] = True

uri = Uri.create(
scheme="http",
authority=f"localhost:{server.port}",
authority=f"{host}:{server.port}",
path=self.build_path,
query=urlencode(query_params),
)
Expand Down
16 changes: 6 additions & 10 deletions lib/esbonio/esbonio/sphinx_agent/static/webview.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,16 +216,12 @@ function renderLineMarkers() {
document.body.append(markerStyle)
}

const host = window.location.hostname;
const queryString = window.location.search;
const queryParams = new URLSearchParams(queryString);
const ws = parseInt(queryParams.get("ws"));
const queryParams = new URLSearchParams(window.location.search);
const showMarkers = queryParams.has("show-markers")
const wsUrl = queryParams.get("ws");

const wsServer = `ws://${host}:${ws}`
console.debug(`Connecting to '${wsServer}'...`)

const socket = new WebSocket(wsServer);
console.debug(`Connecting to '${wsUrl}'...`)
const socket = new WebSocket(wsUrl);
let connected = false

function sendMessage(data) {
Expand All @@ -242,7 +238,7 @@ const handlers = {
console.debug("Reloading page...")
window.location.reload()
},
"view/scroll": (params) => {scrollViewTo(params.uri, params.line)}
"view/scroll": (params) => { scrollViewTo(params.uri, params.line) }
}

function handle(message) {
Expand Down Expand Up @@ -291,7 +287,7 @@ function main() {
renderLineMarkers()
}

rewriteInternalLinks(ws)
rewriteInternalLinks(wsUrl)

// Are we in an <iframe>?
if (window.parent !== window.top) {
Expand Down