Skip to content

Commit

Permalink
feat: support Trusted Types for client overlay (#4404)
Browse files Browse the repository at this point in the history
  • Loading branch information
Siegrift authored May 4, 2022
1 parent 79536ab commit 8132e1d
Show file tree
Hide file tree
Showing 19 changed files with 927 additions and 386 deletions.
248 changes: 119 additions & 129 deletions README.md

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions bin/cli-flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,21 @@ module.exports = {
simpleType: "boolean",
multiple: false,
},
"client-overlay-trusted-types-policy-name": {
configs: [
{
description:
"The name of a Trusted Types policy for the overlay. Defaults to 'webpack-dev-server#overlay'.",
multiple: false,
path: "client.overlay.trustedTypesPolicyName",
type: "string",
},
],
description:
"The name of a Trusted Types policy for the overlay. Defaults to 'webpack-dev-server#overlay'.",
multiple: false,
simpleType: "string",
},
"client-overlay-warnings": {
configs: [
{
Expand Down
12 changes: 9 additions & 3 deletions client-src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import createSocketURL from "./utils/createSocketURL.js";
* @property {boolean} hot
* @property {boolean} liveReload
* @property {boolean} progress
* @property {boolean | { warnings?: boolean, errors?: boolean }} overlay
* @property {boolean | { warnings?: boolean, errors?: boolean, trustedTypesPolicyName?: string }} overlay
* @property {string} [logging]
* @property {number} [reconnect]
*/
Expand Down Expand Up @@ -230,7 +230,10 @@ const onSocketMessage = {
: options.overlay && options.overlay.warnings;

if (needShowOverlayForWarnings) {
show("warning", warnings);
const trustedTypesPolicyName =
typeof options.overlay === "object" &&
options.overlay.trustedTypesPolicyName;
show("warning", warnings, trustedTypesPolicyName || null);
}

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

if (needShowOverlayForErrors) {
show("error", errors);
const trustedTypesPolicyName =
typeof options.overlay === "object" &&
options.overlay.trustedTypesPolicyName;
show("error", errors, trustedTypesPolicyName || null);
}
},
/**
Expand Down
31 changes: 25 additions & 6 deletions client-src/overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,25 @@ let iframeContainerElement;
let containerElement;
/** @type {Array<(element: HTMLDivElement) => void>} */
let onLoadQueue = [];
/** @type {TrustedTypePolicy | undefined} */
let overlayTrustedTypesPolicy;

ansiHTML.setColors(colors);

function createContainer() {
/**
* @param {string | null} trustedTypesPolicyName
*/
function createContainer(trustedTypesPolicyName) {
// Enable Trusted Types if they are available in the current browser.
if (window.trustedTypes) {
overlayTrustedTypesPolicy = window.trustedTypes.createPolicy(
trustedTypesPolicyName || "webpack-dev-server#overlay",
{
createHTML: (value) => value,
}
);
}

iframeContainerElement = document.createElement("iframe");
iframeContainerElement.id = "webpack-dev-server-client-overlay";
iframeContainerElement.src = "about:blank";
Expand Down Expand Up @@ -109,8 +124,9 @@ function createContainer() {

/**
* @param {(element: HTMLDivElement) => void} callback
* @param {string | null} trustedTypesPolicyName
*/
function ensureOverlayExists(callback) {
function ensureOverlayExists(callback, trustedTypesPolicyName) {
if (containerElement) {
// Everything is ready, call the callback right away.
callback(containerElement);
Expand All @@ -124,7 +140,7 @@ function ensureOverlayExists(callback) {
return;
}

createContainer();
createContainer(trustedTypesPolicyName);
}

// Successful compilation.
Expand Down Expand Up @@ -178,8 +194,9 @@ function formatProblem(type, item) {
/**
* @param {string} type
* @param {Array<string | { file?: string, moduleName?: string, loc?: string, message?: string }>} messages
* @param {string | null} trustedTypesPolicyName
*/
function show(type, messages) {
function show(type, messages, trustedTypesPolicyName) {
ensureOverlayExists(() => {
messages.forEach((message) => {
const entryElement = document.createElement("div");
Expand All @@ -193,7 +210,9 @@ function show(type, messages) {
const text = ansiHTML(encode(body));
const messageTextNode = document.createElement("div");

messageTextNode.innerHTML = text;
messageTextNode.innerHTML = overlayTrustedTypesPolicy
? overlayTrustedTypesPolicy.createHTML(text)
: text;

entryElement.appendChild(typeElement);
entryElement.appendChild(document.createElement("br"));
Expand All @@ -205,7 +224,7 @@ function show(type, messages) {
/** @type {HTMLDivElement} */
(containerElement).appendChild(entryElement);
});
});
}, trustedTypesPolicyName);
}

export { formatProblem, show, hide };
36 changes: 36 additions & 0 deletions examples/client/trusted-types-overlay/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# client.overlay.trustedTypesPolicyName option

**webpack.config.js**

```js
module.exports = {
// ...
output: {
trustedTypes: { policyName: "webpack" },
},
devServer: {
client: {
overlay: {
trustedTypesPolicyName: "webpack#dev-overlay",
},
},
},
};
```

Usage via CLI:

```shell
npx webpack serve --open
```

## What Should Happen

1. The script should open `http://localhost:8080/` in your default browser.
2. You should see an overlay in browser for compilation errors.
3. Modify `devServer.client.overlay.trustedTypesPolicyName` in webpack.config.js to `disallowed-policy` and save.
4. Restart the command and you should not see an overlay at all. In the console you should see the following error:

```
Refused to create a TrustedTypePolicy named 'disallowed-policy' because it violates the following Content Security Policy directive: "trusted-types webpack webpack#dev-overlay".
```
6 changes: 6 additions & 0 deletions examples/client/trusted-types-overlay/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"use strict";

const target = document.querySelector("#target");

target.classList.add("pass");
target.textContent = "Success!";
38 changes: 38 additions & 0 deletions examples/client/trusted-types-overlay/layout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!-- Originally copied from "../../.assets/layout.html" -->
<!DOCTYPE html>
<html>
<head>
<!-- Enable Trusted Types -->
<meta
http-equiv="Content-Security-Policy"
content="require-trusted-types-for 'script'; trusted-types webpack webpack#dev-overlay;"
/>

<title>WDS ▻ <%= htmlWebpackPlugin.options.title %></title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="shortcut icon" href=".assets/favicon.ico" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,600|Source+Sans+Pro:400,400i,500,600"
/>
<link rel="stylesheet" href=".assets/style.css" />
</head>
<body>
<main>
<header>
<h1>
<img
src=".assets/icon-square.svg"
style="width: 35px; height: 35px"
/>
webpack-dev-server
</h1>
</header>
<section>
<h2><%= htmlWebpackPlugin.options.title %></h2>
<div id="target"></div>
</section>
</main>
</body>
</html>
32 changes: 32 additions & 0 deletions examples/client/trusted-types-overlay/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use strict";

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
// our setup function adds behind-the-scenes bits to the config that all of our
// examples need
const { setup } = require("../../util");

const config = setup({
context: __dirname,
// create error for overlay
entry: "./invalid.js",
output: {
trustedTypes: { policyName: "webpack" },
},
devServer: {
client: {
overlay: {
trustedTypesPolicyName: "webpack#dev-overlay",
},
},
},
});

// overwrite the index.html with our own to enable Trusted Types
config.plugins[0] = new HtmlWebpackPlugin({
filename: "index.html",
template: path.join(__dirname, "./layout.html"),
title: "trusted types overlay",
});

module.exports = config;
4 changes: 4 additions & 0 deletions lib/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@
"cli": {
"negatedDescription": "Disables the full-screen overlay in the browser when there are compiler warnings."
}
},
"trustedTypesPolicyName": {
"description": "The name of a Trusted Types policy for the overlay. Defaults to 'webpack-dev-server#overlay'.",
"type": "string"
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"@types/default-gateway": "^3.0.1",
"@types/rimraf": "^3.0.2",
"@types/sockjs-client": "^1.5.1",
"@types/trusted-types": "^2.0.2",
"acorn": "^8.2.4",
"babel-jest": "^27.5.1",
"babel-loader": "^8.2.4",
Expand Down
6 changes: 3 additions & 3 deletions test/__snapshots__/validate-options.test.js.snap.webpack5
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,19 @@ exports[`options validate should throw an error on the "client" option with '{"o
-> Read more at https://webpack.js.org/configuration/dev-server/#devserverclient
Details:
* options.client.overlay should be one of these:
boolean | object { errors?, warnings? }
boolean | object { errors?, warnings?, trustedTypesPolicyName? }
Details:
* options.client.overlay should be a boolean.
-> Enables a full-screen overlay in the browser when there are compiler errors or warnings.
-> Read more at https://webpack.js.org/configuration/dev-server/#overlay
* options.client.overlay should be an object:
object { errors?, warnings? }"
object { errors?, warnings?, trustedTypesPolicyName? }"
`;

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

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

0 comments on commit 8132e1d

Please sign in to comment.