-
Notifications
You must be signed in to change notification settings - Fork 400
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(iframe): support load webview by srcdoc (#4071)
- Loading branch information
Showing
19 changed files
with
472 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* eslint-disable no-console */ | ||
import { mkdir, writeFile } from 'fs/promises'; | ||
|
||
import debug from 'debug'; | ||
import * as esbuild from 'esbuild'; | ||
|
||
const log = debug('webview:bundle-webview'); | ||
|
||
const result = await esbuild.build({ | ||
entryPoints: ['src/webview-host/web-preload-builtin.ts'], | ||
sourcemap: false, | ||
write: false, | ||
bundle: true, | ||
minify: true, | ||
}); | ||
|
||
log( | ||
'build result', | ||
result.outputFiles.map((v) => ({ | ||
path: v.path, | ||
length: v.text.length, | ||
})), | ||
); | ||
|
||
const output = result.outputFiles[0].text; | ||
|
||
const htmlWithScript = /* html */ `<!DOCTYPE html> | ||
<html lang="en" style="width: 100%; height: 100%"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<meta http-equiv="X-UA-Compatible" content="ie=edge" /> | ||
<title>Webview Panel Container</title> | ||
</head> | ||
<body style="margin: 0; overflow: hidden; width: 100%; height: 100%"></body> | ||
<script> | ||
window.channelId = {{channelId}}; | ||
</script> | ||
<script> | ||
${output} | ||
</script> | ||
</html> | ||
`; | ||
|
||
const toWrite = /* javascript */ ` | ||
/* This file is generated by scripts/bundle-webview.mjs */ | ||
/* eslint-disable */ | ||
/* prettier-ignore */ | ||
const htmlContent = ${JSON.stringify(htmlWithScript)}; | ||
export const createHTML = (channelId: string) => { | ||
return htmlContent.replace('{{channelId}}', JSON.stringify(channelId)); | ||
}; | ||
`.trimStart(); | ||
|
||
await writeFile('src/browser/iframe/prebuilt.ts', toWrite); | ||
|
||
console.log('Successfully bundled webview, write to src/browser/iframe/prebuilt.ts'); | ||
|
||
if (process.env.DEBUG) { | ||
try { | ||
await mkdir('lib/prebuilt', { recursive: true }); | ||
await writeFile('lib/prebuilt/output.js', output); | ||
await writeFile('lib/prebuilt/webview.html', htmlWithScript); | ||
} catch (error) { | ||
console.log('===== ~ error:', error); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* This file is generated by scripts/bundle-webview.mjs */ | ||
/* eslint-disable */ | ||
/* prettier-ignore */ | ||
const htmlContent = "<!DOCTYPE html>\n<html lang=\"en\" style=\"width: 100%; height: 100%\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n <title>Webview Panel Container</title>\n </head>\n\n <body style=\"margin: 0; overflow: hidden; width: 100%; height: 100%\"></body>\n <script>\n window.channelId = {{channelId}};\n </script>\n <script>\n \"use strict\";(()=>{var u=class{constructor(e){this.getId=e;this.handlers=new Map;this.fakeLoad=!1;this.isInDevelopmentMode=!1;window.addEventListener(\"message\",t=>{if(t.data&&(t.data.command===\"onmessage\"||t.data.command===\"do-update-state\")){this.postMessage(t.data.command,t.data.data);return}let n=t.data.channel,o=this.handlers.get(n);o?o(t,t.data.data):console.log(\"no handler for \",t)}),this.ready=new Promise(t=>{t()}),this.onMessage(\"devtools-opened\",()=>{this.isInDevelopmentMode=!0})}get id(){return this._id||(this._id=this.getId()??\"\"),this._id}get inDev(){return this.isInDevelopmentMode}postMessage(e,t){window.parent!==window&&window.parent.postMessage({target:this.id,channel:e,data:t},\"*\")}onMessage(e,t){this.handlers.set(e,t)}onIframeLoaded(e){}onKeydown(e){e.key===\"s\"&&(e.metaKey||e.ctrlKey)&&e.preventDefault()}};var y=`body {\n background-color: var(--vscode-editor-background);\n color: var(--vscode-editor-foreground);\n font-family: var(--vscode-font-family);\n font-weight: var(--vscode-font-weight);\n font-size: var(--vscode-font-size);\n margin: 0;\n padding: 0 20px;\n}\n\nimg {\n max-width: 100%;\n max-height: 100%;\n}\n\na {\n color: var(--vscode-textLink-foreground);\n}\n\na:hover {\n color: var(--vscode-textLink-activeForeground);\n}\n\na:focus,\ninput:focus,\nselect:focus,\ntextarea:focus {\n outline: 1px solid -webkit-focus-ring-color;\n outline-offset: -1px;\n}\n\ncode {\n color: var(--vscode-textPreformat-foreground);\n}\n\nblockquote {\n background: var(--vscode-textBlockQuote-background);\n border-color: var(--vscode-textBlockQuote-border);\n}\n\n::-webkit-scrollbar {\n width: 10px;\n height: 10px;\n}\n\n::-webkit-scrollbar-thumb {\n background-color: var(--vscode-scrollbarSlider-background);\n}\n::-webkit-scrollbar-thumb:hover {\n background-color: var(--vscode-scrollbarSlider-hoverBackground);\n}\n::-webkit-scrollbar-thumb:active {\n background-color: var(--vscode-scrollbarSlider-activeBackground);\n}\n::-webkit-scrollbar-corner {\n background: transparent;\n}`;function M(l){return(l+\"\").replace(/[\\\\\"']/g,\"\\\\$&\").replace(/\\u0000/g,\"\\\\0\")}function b(l){return`\n const acquireVsCodeApi = (function() {\n const originalPostMessage = window.parent.postMessage.bind(window.parent);\n const targetOrigin = '*';\n let acquired = false;\n\n let state = ${l?`JSON.parse(\"${M(JSON.stringify(l))}\")`:void 0};\n\n return () => {\n if (acquired) {\n throw new Error('An instance of the VS Code API has already been acquired');\n }\n acquired = true;\n return Object.freeze({\n postMessage: function(msg) {\n return originalPostMessage({ command: 'onmessage', data: msg }, targetOrigin);\n },\n setState: function(newState) {\n state = newState;\n originalPostMessage({ command: 'do-update-state', data: JSON.parse(JSON.stringify(newState)) }, targetOrigin);\n return newState;\n },\n getState: function() {\n return state;\n }\n });\n };\n })();\n delete window.parent;\n delete window.top;\n delete window.frameElement;\n window.acquireVsCodeApi = acquireVsCodeApi;\n `}var m=class{constructor(e){this.channel=e;this.activeTheme=\"default\";this.isHandlingScroll=!1;this.updateId=0;this.firstLoad=!0;this.pendingMessages=[];document.addEventListener(\"DOMContentLoaded\",this.init.bind(this))}get ID(){return this.channel.id}init(){document.body&&(this.channel.onMessage(\"styles\",(e,t)=>{this.styles=t.styles,this.activeTheme=t.activeTheme;let n=this.getActiveFrame();n&&n.contentDocument&&this.applyStyles(n.contentDocument,n.contentDocument.body)}),this.channel.onMessage(\"focus\",()=>{let e=this.getActiveFrame();e&&e.contentWindow&&e.contentWindow.focus()}),this.channel.onMessage(\"content\",async(e,t)=>this.setContent(t)),this.channel.onMessage(\"message\",(e,t)=>{if(!this.getPendingFrame()){let o=this.getActiveFrame();if(o){o.contentWindow?.postMessage(t,\"*\");return}}this.pendingMessages.push(t)}),this.trackFocus({onFocus:()=>this.channel.postMessage(\"did-focus\"),onBlur:()=>this.channel.postMessage(\"did-blur\")}),this.channel.postMessage(\"webview-ready\",{}))}async setContent(e){let t=++this.updateId;if(await this.channel.ready,t!==this.updateId)return;let n=e.options,o=this.toContentHtml(e),d=this.getActiveFrame(),c=this.firstLoad,f;if(this.firstLoad)this.firstLoad=!1,f=(s,a)=>{isNaN(this.initialScrollProgress)||a.scrollY===0&&a.scroll(0,s.clientHeight*this.initialScrollProgress)};else{let s=d&&d.contentDocument&&d.contentDocument.body?d.contentWindow?.scrollY:0;f=(a,r)=>{r.scrollY===0&&r.scroll(0,s)}}let g=this.getPendingFrame();g&&(g.setAttribute(\"id\",\"\"),document.body.removeChild(g)),c||(this.pendingMessages=[]);let i=document.createElement(\"iframe\");i.setAttribute(\"id\",\"pending-frame\"),i.setAttribute(\"frameborder\",\"0\"),i.setAttribute(\"allow\",\"autoplay; clipboard-read; clipboard-write;\");let h=new Set([\"allow-same-origin\",\"allow-pointer-lock\"]);n.allowScripts&&(h.add(\"allow-scripts\"),h.add(\"allow-downloads\")),n.allowForms&&h.add(\"allow-forms\"),i.setAttribute(\"sandbox\",Array.from(h).join(\" \")),this.channel.fakeLoad&&(i.src=`./fake.html?id=${this.ID}`),i.style.cssText=\"display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden\",document.body.appendChild(i),this.channel.fakeLoad||i.contentDocument?.open(),i.contentWindow?.addEventListener(\"keydown\",this.handleInnerKeydown.bind(this)),i.contentWindow?.addEventListener(\"DOMContentLoaded\",s=>{this.channel.fakeLoad&&(i.contentDocument?.open(),i.contentDocument?.write(o),i.contentDocument?.close(),v(i));let a=s.target?s.target:void 0;a&&this.applyStyles(a,a.body)});let p=(s,a)=>{s&&s.body&&f(s.body,a);let r=this.getPendingFrame();if(r&&r.contentDocument&&r.contentDocument===s){let w=this.getActiveFrame();w&&document.body.removeChild(w),this.applyStyles(r.contentDocument,r.contentDocument.body),r.setAttribute(\"id\",\"active-frame\"),r.style.visibility=\"visible\",this.channel.focusIframeOnCreate&&r.contentWindow?.focus(),a.addEventListener(\"scroll\",this.handleInnerScroll.bind(this)),this.pendingMessages.forEach(k=>{a.postMessage(k,\"*\")}),this.pendingMessages=[]}},v=s=>{clearTimeout(this.loadTimeout),this.loadTimeout=void 0,this.loadTimeout=setTimeout(()=>{clearTimeout(this.loadTimeout),this.loadTimeout=void 0,p(s.contentDocument,s.contentWindow)},1e3);let a=this;s.contentWindow.addEventListener(\"load\",function(r){a.loadTimeout&&(clearTimeout(a.loadTimeout),a.loadTimeout=void 0,p(r.target,this))}),s.contentWindow.addEventListener(\"click\",this.handleInnerClick.bind(this)),this.channel.onIframeLoaded&&this.channel.onIframeLoaded(s)};this.channel.fakeLoad||v(i),this.channel.fakeLoad||(i.contentDocument?.write(o),i.contentDocument?.close()),this.channel.postMessage(\"did-set-content\",void 0)}trackFocus({onFocus:e,onBlur:t}){let o=document.hasFocus();setInterval(()=>{let d=document.hasFocus();d!==o&&(o=d,d?e():t())},50)}getActiveFrame(){return document.getElementById(\"active-frame\")}getPendingFrame(){return document.getElementById(\"pending-frame\")}get defaultCssRules(){return y}applyStyles(e,t){if(e&&(t&&(t.classList.remove(\"vscode-light\",\"vscode-dark\",\"vscode-high-contrast\"),t.classList.add(this.activeTheme)),this.styles))for(let n of Object.keys(this.styles))e.documentElement.style.setProperty(`--${n}`,this.styles[n])}handleInnerClick(e){if(!e||!e.view||!e.view.document)return;let t=e.view.document.getElementsByTagName(\"base\")[0],n=e.target;for(;n;){if(n.tagName&&n.tagName.toLowerCase()===\"a\"&&n.href){if(n.getAttribute(\"href\")===\"#\")e.view.scrollTo(0,0);else if(n.hash&&(n.getAttribute(\"href\")===n.hash||t&&n.href.indexOf(t.href)>=0)){let o=e.view.document.getElementById(n.hash.substr(1,n.hash.length-1));o&&o.scrollIntoView()}else this.channel.postMessage(\"did-click-link\",n.href.baseVal||n.href);e.preventDefault();break}n=n.parentNode}}handleInnerKeydown(e){this.channel.postMessage(\"did-keydown\",{key:e.key,keyCode:e.keyCode,code:e.code,shiftKey:e.shiftKey,altKey:e.altKey,ctrlKey:e.ctrlKey,metaKey:e.metaKey,repeat:e.repeat}),this.channel.onKeydown&&this.channel.onKeydown(e)}handleInnerScroll(e){if(!e.target||!e.target.body||this.isHandlingScroll)return;let t=e.currentTarget.scrollY/e.target.body.clientHeight;isNaN(t)||(this.isHandlingScroll=!0,window.requestAnimationFrame(()=>{try{this.channel.postMessage(\"did-scroll\",t)}catch{}this.isHandlingScroll=!1}))}toContentHtml(e){let t=e.options,n=e.contents,o=new DOMParser().parseFromString(n,\"text/html\");if(o.querySelectorAll(\"a\").forEach(c=>{c.title||(c.title=c.getAttribute(\"href\"))}),t.allowScripts){let c=o.createElement(\"script\");c.textContent=b(e.state),o.head.prepend(c)}let d=o.createElement(\"style\");return d.id=\"_defaultStyles\",d.innerHTML=this.defaultCssRules,o.head.prepend(d),this.applyStyles(o,o.body),`<!DOCTYPE html>\n`+o.documentElement.outerHTML}};new m(new u(()=>window.channelId));})();\n\n </script>\n</html>\n"; | ||
|
||
export const createHTML = (channelId: string) => { | ||
return htmlContent.replace('{{channelId}}', JSON.stringify(channelId)); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.