Skip to content

Commit 8132e1d

Browse files
authored
feat: support Trusted Types for client overlay (#4404)
1 parent 79536ab commit 8132e1d

19 files changed

+927
-386
lines changed

README.md

+119-129
Large diffs are not rendered by default.

bin/cli-flags.js

+15
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,21 @@ module.exports = {
121121
simpleType: "boolean",
122122
multiple: false,
123123
},
124+
"client-overlay-trusted-types-policy-name": {
125+
configs: [
126+
{
127+
description:
128+
"The name of a Trusted Types policy for the overlay. Defaults to 'webpack-dev-server#overlay'.",
129+
multiple: false,
130+
path: "client.overlay.trustedTypesPolicyName",
131+
type: "string",
132+
},
133+
],
134+
description:
135+
"The name of a Trusted Types policy for the overlay. Defaults to 'webpack-dev-server#overlay'.",
136+
multiple: false,
137+
simpleType: "string",
138+
},
124139
"client-overlay-warnings": {
125140
configs: [
126141
{

client-src/index.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import createSocketURL from "./utils/createSocketURL.js";
1515
* @property {boolean} hot
1616
* @property {boolean} liveReload
1717
* @property {boolean} progress
18-
* @property {boolean | { warnings?: boolean, errors?: boolean }} overlay
18+
* @property {boolean | { warnings?: boolean, errors?: boolean, trustedTypesPolicyName?: string }} overlay
1919
* @property {string} [logging]
2020
* @property {number} [reconnect]
2121
*/
@@ -230,7 +230,10 @@ const onSocketMessage = {
230230
: options.overlay && options.overlay.warnings;
231231

232232
if (needShowOverlayForWarnings) {
233-
show("warning", warnings);
233+
const trustedTypesPolicyName =
234+
typeof options.overlay === "object" &&
235+
options.overlay.trustedTypesPolicyName;
236+
show("warning", warnings, trustedTypesPolicyName || null);
234237
}
235238

236239
if (params && params.preventReloading) {
@@ -263,7 +266,10 @@ const onSocketMessage = {
263266
: options.overlay && options.overlay.errors;
264267

265268
if (needShowOverlayForErrors) {
266-
show("error", errors);
269+
const trustedTypesPolicyName =
270+
typeof options.overlay === "object" &&
271+
options.overlay.trustedTypesPolicyName;
272+
show("error", errors, trustedTypesPolicyName || null);
267273
}
268274
},
269275
/**

client-src/overlay.js

+25-6
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,25 @@ let iframeContainerElement;
2323
let containerElement;
2424
/** @type {Array<(element: HTMLDivElement) => void>} */
2525
let onLoadQueue = [];
26+
/** @type {TrustedTypePolicy | undefined} */
27+
let overlayTrustedTypesPolicy;
2628

2729
ansiHTML.setColors(colors);
2830

29-
function createContainer() {
31+
/**
32+
* @param {string | null} trustedTypesPolicyName
33+
*/
34+
function createContainer(trustedTypesPolicyName) {
35+
// Enable Trusted Types if they are available in the current browser.
36+
if (window.trustedTypes) {
37+
overlayTrustedTypesPolicy = window.trustedTypes.createPolicy(
38+
trustedTypesPolicyName || "webpack-dev-server#overlay",
39+
{
40+
createHTML: (value) => value,
41+
}
42+
);
43+
}
44+
3045
iframeContainerElement = document.createElement("iframe");
3146
iframeContainerElement.id = "webpack-dev-server-client-overlay";
3247
iframeContainerElement.src = "about:blank";
@@ -109,8 +124,9 @@ function createContainer() {
109124

110125
/**
111126
* @param {(element: HTMLDivElement) => void} callback
127+
* @param {string | null} trustedTypesPolicyName
112128
*/
113-
function ensureOverlayExists(callback) {
129+
function ensureOverlayExists(callback, trustedTypesPolicyName) {
114130
if (containerElement) {
115131
// Everything is ready, call the callback right away.
116132
callback(containerElement);
@@ -124,7 +140,7 @@ function ensureOverlayExists(callback) {
124140
return;
125141
}
126142

127-
createContainer();
143+
createContainer(trustedTypesPolicyName);
128144
}
129145

130146
// Successful compilation.
@@ -178,8 +194,9 @@ function formatProblem(type, item) {
178194
/**
179195
* @param {string} type
180196
* @param {Array<string | { file?: string, moduleName?: string, loc?: string, message?: string }>} messages
197+
* @param {string | null} trustedTypesPolicyName
181198
*/
182-
function show(type, messages) {
199+
function show(type, messages, trustedTypesPolicyName) {
183200
ensureOverlayExists(() => {
184201
messages.forEach((message) => {
185202
const entryElement = document.createElement("div");
@@ -193,7 +210,9 @@ function show(type, messages) {
193210
const text = ansiHTML(encode(body));
194211
const messageTextNode = document.createElement("div");
195212

196-
messageTextNode.innerHTML = text;
213+
messageTextNode.innerHTML = overlayTrustedTypesPolicy
214+
? overlayTrustedTypesPolicy.createHTML(text)
215+
: text;
197216

198217
entryElement.appendChild(typeElement);
199218
entryElement.appendChild(document.createElement("br"));
@@ -205,7 +224,7 @@ function show(type, messages) {
205224
/** @type {HTMLDivElement} */
206225
(containerElement).appendChild(entryElement);
207226
});
208-
});
227+
}, trustedTypesPolicyName);
209228
}
210229

211230
export { formatProblem, show, hide };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# client.overlay.trustedTypesPolicyName option
2+
3+
**webpack.config.js**
4+
5+
```js
6+
module.exports = {
7+
// ...
8+
output: {
9+
trustedTypes: { policyName: "webpack" },
10+
},
11+
devServer: {
12+
client: {
13+
overlay: {
14+
trustedTypesPolicyName: "webpack#dev-overlay",
15+
},
16+
},
17+
},
18+
};
19+
```
20+
21+
Usage via CLI:
22+
23+
```shell
24+
npx webpack serve --open
25+
```
26+
27+
## What Should Happen
28+
29+
1. The script should open `http://localhost:8080/` in your default browser.
30+
2. You should see an overlay in browser for compilation errors.
31+
3. Modify `devServer.client.overlay.trustedTypesPolicyName` in webpack.config.js to `disallowed-policy` and save.
32+
4. Restart the command and you should not see an overlay at all. In the console you should see the following error:
33+
34+
```
35+
Refused to create a TrustedTypePolicy named 'disallowed-policy' because it violates the following Content Security Policy directive: "trusted-types webpack webpack#dev-overlay".
36+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"use strict";
2+
3+
const target = document.querySelector("#target");
4+
5+
target.classList.add("pass");
6+
target.textContent = "Success!";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!-- Originally copied from "../../.assets/layout.html" -->
2+
<!DOCTYPE html>
3+
<html>
4+
<head>
5+
<!-- Enable Trusted Types -->
6+
<meta
7+
http-equiv="Content-Security-Policy"
8+
content="require-trusted-types-for 'script'; trusted-types webpack webpack#dev-overlay;"
9+
/>
10+
11+
<title>WDS ▻ <%= htmlWebpackPlugin.options.title %></title>
12+
<meta charset="utf-8" />
13+
<meta name="viewport" content="width=device-width, initial-scale=1" />
14+
<link rel="shortcut icon" href=".assets/favicon.ico" />
15+
<link
16+
rel="stylesheet"
17+
href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,600|Source+Sans+Pro:400,400i,500,600"
18+
/>
19+
<link rel="stylesheet" href=".assets/style.css" />
20+
</head>
21+
<body>
22+
<main>
23+
<header>
24+
<h1>
25+
<img
26+
src=".assets/icon-square.svg"
27+
style="width: 35px; height: 35px"
28+
/>
29+
webpack-dev-server
30+
</h1>
31+
</header>
32+
<section>
33+
<h2><%= htmlWebpackPlugin.options.title %></h2>
34+
<div id="target"></div>
35+
</section>
36+
</main>
37+
</body>
38+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"use strict";
2+
3+
const path = require("path");
4+
const HtmlWebpackPlugin = require("html-webpack-plugin");
5+
// our setup function adds behind-the-scenes bits to the config that all of our
6+
// examples need
7+
const { setup } = require("../../util");
8+
9+
const config = setup({
10+
context: __dirname,
11+
// create error for overlay
12+
entry: "./invalid.js",
13+
output: {
14+
trustedTypes: { policyName: "webpack" },
15+
},
16+
devServer: {
17+
client: {
18+
overlay: {
19+
trustedTypesPolicyName: "webpack#dev-overlay",
20+
},
21+
},
22+
},
23+
});
24+
25+
// overwrite the index.html with our own to enable Trusted Types
26+
config.plugins[0] = new HtmlWebpackPlugin({
27+
filename: "index.html",
28+
template: path.join(__dirname, "./layout.html"),
29+
title: "trusted types overlay",
30+
});
31+
32+
module.exports = config;

lib/options.json

+4
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@
110110
"cli": {
111111
"negatedDescription": "Disables the full-screen overlay in the browser when there are compiler warnings."
112112
}
113+
},
114+
"trustedTypesPolicyName": {
115+
"description": "The name of a Trusted Types policy for the overlay. Defaults to 'webpack-dev-server#overlay'.",
116+
"type": "string"
113117
}
114118
}
115119
}

package-lock.json

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"@types/default-gateway": "^3.0.1",
7979
"@types/rimraf": "^3.0.2",
8080
"@types/sockjs-client": "^1.5.1",
81+
"@types/trusted-types": "^2.0.2",
8182
"acorn": "^8.2.4",
8283
"babel-jest": "^27.5.1",
8384
"babel-loader": "^8.2.4",

test/__snapshots__/validate-options.test.js.snap.webpack5

+3-3
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,19 @@ exports[`options validate should throw an error on the "client" option with '{"o
9090
-> Read more at https://webpack.js.org/configuration/dev-server/#devserverclient
9191
Details:
9292
* options.client.overlay should be one of these:
93-
boolean | object { errors?, warnings? }
93+
boolean | object { errors?, warnings?, trustedTypesPolicyName? }
9494
Details:
9595
* options.client.overlay should be a boolean.
9696
-> Enables a full-screen overlay in the browser when there are compiler errors or warnings.
9797
-> Read more at https://webpack.js.org/configuration/dev-server/#overlay
9898
* options.client.overlay should be an object:
99-
object { errors?, warnings? }"
99+
object { errors?, warnings?, trustedTypesPolicyName? }"
100100
`;
101101

102102
exports[`options validate should throw an error on the "client" option with '{"overlay":{"arbitrary":""}}' value 1`] = `
103103
"ValidationError: Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
104104
- options.client.overlay has an unknown property 'arbitrary'. These properties are valid:
105-
object { errors?, warnings? }"
105+
object { errors?, warnings?, trustedTypesPolicyName? }"
106106
`;
107107

108108
exports[`options validate should throw an error on the "client" option with '{"overlay":{"errors":""}}' value 1`] = `

0 commit comments

Comments
 (0)