Skip to content

Commit

Permalink
Merge pull request #16990 from Zlatkovsky/zlatkovsky/trusted-types-op…
Browse files Browse the repository at this point in the history
…tions

Allow specifying "onPolicyCreationFailure" mode for trusted types
  • Loading branch information
TheLarkInn committed May 3, 2023
2 parents ffcb480 + 83d14b1 commit ddb9627
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 4 deletions.
4 changes: 4 additions & 0 deletions declarations/WebpackOptions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2263,6 +2263,10 @@ export interface Environment {
* Use a Trusted Types policy to create urls for chunks.
*/
export interface TrustedTypes {
/**
* If the call to `trustedTypes.createPolicy(...)` fails -- e.g., due to the policy name missing from the CSP `trusted-types` list, or it being a duplicate name, etc. -- controls whether to continue with loading in the hope that `require-trusted-types-for 'script'` isn't enforced yet, versus fail immediately. Default behavior is 'stop'.
*/
onPolicyCreationFailure?: "continue" | "stop";
/**
* The name of the Trusted Types policy created by webpack to serve bundle chunks.
*/
Expand Down
1 change: 1 addition & 0 deletions lib/config/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,7 @@ const applyOutputDefaults = (
() =>
output.uniqueName.replace(/[^a-zA-Z0-9\-#=_/@.%]+/g, "_") || "webpack"
);
D(trustedTypes, "onPolicyCreationFailure", "stop");
}

/**
Expand Down
25 changes: 22 additions & 3 deletions lib/runtime/GetTrustedTypesPolicyRuntimeModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class GetTrustedTypesPolicyRuntimeModule extends HelperRuntimeModule {
const { runtimeTemplate, outputOptions } = compilation;
const { trustedTypes } = outputOptions;
const fn = RuntimeGlobals.getTrustedTypesPolicy;
const wrapPolicyCreationInTryCatch = trustedTypes
? trustedTypes.onPolicyCreationFailure === "continue"
: false;

return Template.asString([
"var policy;",
Expand Down Expand Up @@ -58,9 +61,25 @@ class GetTrustedTypesPolicyRuntimeModule extends HelperRuntimeModule {
? [
'if (typeof trustedTypes !== "undefined" && trustedTypes.createPolicy) {',
Template.indent([
`policy = trustedTypes.createPolicy(${JSON.stringify(
trustedTypes.policyName
)}, policy);`
...(wrapPolicyCreationInTryCatch ? ["try {"] : []),
...[
`policy = trustedTypes.createPolicy(${JSON.stringify(
trustedTypes.policyName
)}, policy);`
].map(line =>
wrapPolicyCreationInTryCatch ? Template.indent(line) : line
),
...(wrapPolicyCreationInTryCatch
? [
"} catch (e) {",
Template.indent([
`console.warn('Could not create trusted-types policy ${JSON.stringify(
trustedTypes.policyName
)}');`
]),
"}"
]
: [])
]),
"}"
]
Expand Down
2 changes: 1 addition & 1 deletion schemas/WebpackOptions.check.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions schemas/WebpackOptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -5039,6 +5039,10 @@
"type": "object",
"additionalProperties": false,
"properties": {
"onPolicyCreationFailure": {
"description": "If the call to `trustedTypes.createPolicy(...)` fails -- e.g., due to the policy name missing from the CSP `trusted-types` list, or it being a duplicate name, etc. -- controls whether to continue with loading in the hope that `require-trusted-types-for 'script'` isn't enforced yet, versus fail immediately. Default behavior is 'stop'.",
"enum": ["continue", "stop"]
},
"policyName": {
"description": "The name of the Trusted Types policy created by webpack to serve bundle chunks.",
"type": "string",
Expand Down
1 change: 1 addition & 0 deletions test/Defaults.unittest.js
Original file line number Diff line number Diff line change
Expand Up @@ -1931,6 +1931,7 @@ describe("snapshots", () => {
- "trustedTypes": undefined,
- "uniqueName": "webpack",
+ "trustedTypes": Object {
+ "onPolicyCreationFailure": "stop",
+ "policyName": "@@@Hello_World_",
+ },
+ "uniqueName": "@@@Hello World!",
Expand Down
17 changes: 17 additions & 0 deletions test/__snapshots__/Cli.basictest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6628,6 +6628,23 @@ Object {
"multiple": false,
"simpleType": "string",
},
"output-trusted-types-on-policy-creation-failure": Object {
"configs": Array [
Object {
"description": "If the call to \`trustedTypes.createPolicy(...)\` fails -- e.g., due to the policy name missing from the CSP \`trusted-types\` list, or it being a duplicate name, etc. -- controls whether to continue with loading in the hope that \`require-trusted-types-for 'script'\` isn't enforced yet, versus fail immediately. Default behavior is 'stop'.",
"multiple": false,
"path": "output.trustedTypes.onPolicyCreationFailure",
"type": "enum",
"values": Array [
"continue",
"stop",
],
},
],
"description": "If the call to \`trustedTypes.createPolicy(...)\` fails -- e.g., due to the policy name missing from the CSP \`trusted-types\` list, or it being a duplicate name, etc. -- controls whether to continue with loading in the hope that \`require-trusted-types-for 'script'\` isn't enforced yet, versus fail immediately. Default behavior is 'stop'.",
"multiple": false,
"simpleType": "string",
},
"output-trusted-types-policy-name": Object {
"configs": Array [
Object {
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
it("can continue on policy creation failure", function () {
// emulate trusted types in a window object
window.trustedTypes = {
createPolicy: () => {
throw new Error("Rejecting createPolicy call");
}
};

const createPolicySpy = jest.spyOn(window.trustedTypes, "createPolicy");
const consoleWarn = jest.spyOn(console, "warn").mockImplementation(() => {});

const promise = import(
"./empty?b" /* webpackChunkName: "continue-on-policy-creation-failure" */
);
var script = document.head._children.pop();
expect(script.src).toBe(
"https://test.cases/path/continue-on-policy-creation-failure.web.js"
);
__non_webpack_require__("./continue-on-policy-creation-failure.web.js");

expect(createPolicySpy).toHaveBeenCalledWith(
"CustomPolicyName",
expect.objectContaining({
createScriptURL: expect.anything()
})
);
expect(createPolicySpy).toThrow();
expect(consoleWarn).toHaveBeenCalledWith(
`Could not create trusted-types policy "CustomPolicyName"`
);

createPolicySpy.mockReset();
consoleWarn.mockReset();

return promise;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = {
target: "web",
output: {
chunkFilename: "[name].web.js",
crossOriginLoading: "anonymous",
trustedTypes: {
policyName: "CustomPolicyName",
onPolicyCreationFailure: "continue"
}
},
performance: {
hints: false
},
optimization: {
minimize: false
}
};
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
it("should stop if policy fails to be created", function () {
// emulate trusted types in a window object
window.trustedTypes = {
createPolicy: () => {
throw new Error("Rejecting createPolicy call");
}
};

const createPolicySpy = jest.spyOn(window.trustedTypes, "createPolicy");
const consoleWarn = jest.spyOn(console, "warn").mockImplementation(() => {});

let promise;
try {
promise = import(
"./empty?test=stop-on-policy-creation-failure" /* webpackChunkName: "stop-on-policy-creation-failure" */
);
} catch (e) {
expect(e.message).toBe("Rejecting createPolicy call");
}

// Unlike in the other test cases, we expect the failure above to prevent any scripts from being added to the document head
expect(document.head._children.length).toBe(0);
expect(createPolicySpy).toHaveBeenCalledWith(
"webpack",
expect.objectContaining({
createScriptURL: expect.anything()
})
);

// Unlike in the "continue-on-policy-creation-failure" case, we expect an outright thrown error,
// rather than a console warning. The console should not have been called:
expect(consoleWarn).toHaveBeenCalledTimes(0);

createPolicySpy.mockReset();
consoleWarn.mockReset();

return promise;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
target: "web",
output: {
chunkFilename: "[name].web.js",
crossOriginLoading: "anonymous",
trustedTypes: true
},
performance: {
hints: false
},
optimization: {
minimize: false
}
};
5 changes: 5 additions & 0 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12116,6 +12116,11 @@ declare class TopLevelSymbol {
* Use a Trusted Types policy to create urls for chunks.
*/
declare interface TrustedTypes {
/**
* If the call to `trustedTypes.createPolicy(...)` fails -- e.g., due to the policy name missing from the CSP `trusted-types` list, or it being a duplicate name, etc. -- controls whether to continue with loading in the hope that `require-trusted-types-for 'script'` isn't enforced yet, versus fail immediately. Default behavior is 'stop'.
*/
onPolicyCreationFailure?: "continue" | "stop";

/**
* The name of the Trusted Types policy created by webpack to serve bundle chunks.
*/
Expand Down

0 comments on commit ddb9627

Please sign in to comment.