@@ -15,7 +15,13 @@ request is not cross-origin.
1515There is also an origin check we bypass (this seems to be related to how the
1616webview host is separate by default but we serve on the same host).
1717
18- To test, open a few types of webviews (images, markdown, extension details, etc).
18+ Firefox 149 and later skip service worker interception for cross-origin
19+ stylesheet requests. Load these stylesheets from the controlled outer webview
20+ frame and apply them to the content as constructed stylesheets.
21+
22+ To test, configure a local stylesheet with markdown.styles, then open a Markdown
23+ preview in Firefox and verify the custom stylesheet is applied. Also open a few
24+ other types of webviews (images, extension details, etc).
1925
2026parentOriginHash changes
2127
@@ -53,6 +59,15 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
5359===================================================================
5460--- code-server.orig/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index.html
5561+++ code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index.html
62+ @@ -5,7 +5,7 @@
63+ <meta charset="UTF-8">
64+
65+ <meta http-equiv="Content-Security-Policy"
66+ - content="default-src 'none'; script-src 'sha256-FFQoOVVa2tOE3uqUvirwaMNT20TZmrHcL2aaOjJ8BUo=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
67+ + content="default-src 'none'; script-src 'sha256-FFQoOVVa2tOE3uqUvirwaMNT20TZmrHcL2aaOjJ8BUo=' 'self'; connect-src 'self' https://*.vscode-cdn.net; frame-src 'self'; style-src 'unsafe-inline';">
68+
69+ <!-- Disable pinch zooming -->
70+ <meta name="viewport"
5671@@ -253,7 +253,7 @@
5772 }
5873
@@ -75,6 +90,149 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
7590 if (!crypto.subtle) {
7691 // cannot validate, not running in a secure context
7792 throw new Error(`'crypto.subtle' is not available so webviews will not work. This is likely because the editor is not running in a secure context (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).`);
93+ @@ -841,13 +847,69 @@
94+ }
95+
96+ /**
97+ - * @param {import('../webviewMessages').UpdateContentEvent} data
98+ - * @return {string}
99+ + * Firefox 149+ skips service worker interception for cross-origin stylesheet
100+ + * requests. Fetch these stylesheets from the controlled outer frame instead.
101+ + *
102+ + * @param {Document} contentDocument
103+ + * @returns {Promise<Array<{ href: string, css: string, media: string, disabled: boolean }>>}
104+ */
105+ - function toContentHtml(data) {
106+ + async function loadFirefoxStylesheets(contentDocument) {
107+ + if (!isFirefox) {
108+ + return [];
109+ + }
110+ +
111+ + const resourceBaseAuthority = searchParams.get('vscode-resource-base-authority');
112+ + if (!resourceBaseAuthority) {
113+ + return [];
114+ + }
115+ +
116+ + const stylesheets = [];
117+ + for (const link of contentDocument.querySelectorAll('link')) {
118+ + if (!link.relList.contains('stylesheet')) {
119+ + continue;
120+ + }
121+ +
122+ + let resourceUrl;
123+ + try {
124+ + resourceUrl = new URL(link.href, link.baseURI);
125+ + } catch {
126+ + continue;
127+ + }
128+ +
129+ + if (resourceUrl.protocol !== 'https:' || !resourceUrl.hostname.endsWith('.' + resourceBaseAuthority)) {
130+ + continue;
131+ + }
132+ +
133+ + try {
134+ + const response = await fetch(resourceUrl);
135+ + if (!response.ok) {
136+ + continue;
137+ + }
138+ +
139+ + stylesheets.push({
140+ + href: resourceUrl.toString(),
141+ + css: await response.text(),
142+ + media: link.media,
143+ + disabled: link.disabled,
144+ + });
145+ + link.remove();
146+ + } catch {
147+ + // Leave the link in place so the webview reports the original load failure.
148+ + }
149+ + }
150+ +
151+ + return stylesheets;
152+ + }
153+ +
154+ + /**
155+ + * @param {import('../webviewMessages').UpdateContentEvent} data
156+ + * @returns {Promise<{ html: string, stylesheets: Array<{ href: string, css: string, media: string, disabled: boolean }> }>}
157+ + */
158+ + async function toContentHtml(data) {
159+ const options = data.options;
160+ const text = data.contents;
161+ const newDocument = new DOMParser().parseFromString(text, 'text/html');
162+ + const stylesheets = await loadFirefoxStylesheets(newDocument);
163+
164+ newDocument.querySelectorAll('a').forEach(a => {
165+ if (!a.title) {
166+ @@ -904,7 +966,10 @@
167+
168+ // set DOCTYPE for newDocument explicitly as DOMParser.parseFromString strips it off
169+ // and DOCTYPE is needed in the iframe to ensure that the user agent stylesheet is correctly overridden
170+ - return '<!DOCTYPE html>\n' + newDocument.documentElement.outerHTML;
171+ + return {
172+ + html: '<!DOCTYPE html>\n' + newDocument.documentElement.outerHTML,
173+ + stylesheets,
174+ + };
175+ }
176+
177+ // Also forward events before the contents of the webview have loaded
178+ @@ -979,7 +1044,10 @@
179+ }
180+
181+ const options = data.options;
182+ - const newDocument = toContentHtml(data);
183+ + const { html: newDocument, stylesheets } = await toContentHtml(data);
184+ + if (currentUpdateId !== updateId) {
185+ + return;
186+ + }
187+
188+ const initialStyleVersion = styleVersion;
189+
190+ @@ -1055,15 +1123,44 @@
191+ /**
192+ * @param {Document} contentDocument
193+ */
194+ + async function applyFirefoxStylesheets(contentDocument) {
195+ + if (!stylesheets.length || !contentDocument.defaultView) {
196+ + return;
197+ + }
198+ +
199+ + /** @type {CSSStyleSheet[]} */
200+ + const sheets = [];
201+ + for (const stylesheet of stylesheets) {
202+ + const sheet = new contentDocument.defaultView.CSSStyleSheet({
203+ + baseURL: stylesheet.href,
204+ + media: stylesheet.media,
205+ + disabled: stylesheet.disabled,
206+ + });
207+ + try {
208+ + await sheet.replace(stylesheet.css);
209+ + } catch (error) {
210+ + console.error(`Failed to apply stylesheet '${stylesheet.href}': ${error}`);
211+ + continue;
212+ + }
213+ + sheets.push(sheet);
214+ + }
215+ +
216+ + contentDocument.adoptedStyleSheets = [...contentDocument.adoptedStyleSheets, ...sheets];
217+ + }
218+ +
219+ + /**
220+ + * @param {Document} contentDocument
221+ + */
222+ function onFrameLoaded(contentDocument) {
223+ perfMark('content/innerFrameLoaded')
224+
225+ // Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=978325
226+ - setTimeout(() => {
227+ + setTimeout(async () => {
228+ contentDocument.open();
229+ contentDocument.write(newDocument);
230+ contentDocument.close();
231+ hookupOnLoadHandlers(newFrame);
232+ + await applyFirefoxStylesheets(contentDocument);
233+ perfMark('content/wroteInnerContent')
234+
235+ if (initialStyleVersion !== styleVersion) {
78236Index: code-server/lib/vscode/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
79237===================================================================
80238--- code-server.orig/lib/vscode/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
0 commit comments