diff --git a/lib/EvalDevToolModulePlugin.js b/lib/EvalDevToolModulePlugin.js index 803d7869a61..b0a47db88f7 100644 --- a/lib/EvalDevToolModulePlugin.js +++ b/lib/EvalDevToolModulePlugin.js @@ -8,6 +8,7 @@ const { ConcatSource, RawSource } = require("webpack-sources"); const ExternalModule = require("./ExternalModule"); const ModuleFilenameHelpers = require("./ModuleFilenameHelpers"); +const RuntimeGlobals = require("./RuntimeGlobals"); const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin"); /** @typedef {import("webpack-sources").Source} Source */ @@ -77,7 +78,13 @@ class EvalDevToolModulePlugin { .replace(/^\//, "") ); const result = new RawSource( - `eval(${JSON.stringify(content + footer)});` + `eval(${ + compilation.outputOptions.trustedTypes + ? `${RuntimeGlobals.createScript}(${JSON.stringify( + content + footer + )})` + : JSON.stringify(content + footer) + });` ); cache.set(source, result); return result; @@ -95,6 +102,14 @@ class EvalDevToolModulePlugin { hash.update("EvalDevToolModulePlugin"); hash.update("2"); }); + if (compilation.outputOptions.trustedTypes) { + compilation.hooks.additionalModuleRuntimeRequirements.tap( + "EvalDevToolModulePlugin", + (module, set, context) => { + set.add(RuntimeGlobals.createScript); + } + ); + } }); } } diff --git a/lib/EvalSourceMapDevToolPlugin.js b/lib/EvalSourceMapDevToolPlugin.js index 7282ba3f676..63129b9f6ee 100644 --- a/lib/EvalSourceMapDevToolPlugin.js +++ b/lib/EvalSourceMapDevToolPlugin.js @@ -8,6 +8,7 @@ const { ConcatSource, RawSource } = require("webpack-sources"); const ModuleFilenameHelpers = require("./ModuleFilenameHelpers"); const NormalModule = require("./NormalModule"); +const RuntimeGlobals = require("./RuntimeGlobals"); const SourceMapDevToolModuleOptionsPlugin = require("./SourceMapDevToolModuleOptionsPlugin"); const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin"); const ConcatenatedModule = require("./optimize/ConcatenatedModule"); @@ -165,7 +166,15 @@ class EvalSourceMapDevToolPlugin { ) + `\n//# sourceURL=webpack-internal:///${moduleId}\n`; // workaround for chrome bug return result( - new RawSource(`eval(${JSON.stringify(content + footer)});`) + new RawSource( + `eval(${ + compilation.outputOptions.trustedTypes + ? `${RuntimeGlobals.createScript}(${JSON.stringify( + content + footer + )})` + : JSON.stringify(content + footer) + });` + ) ); } ); @@ -181,6 +190,14 @@ class EvalSourceMapDevToolPlugin { hash.update("EvalSourceMapDevToolPlugin"); hash.update("2"); }); + if (compilation.outputOptions.trustedTypes) { + compilation.hooks.additionalModuleRuntimeRequirements.tap( + "EvalSourceMapDevToolPlugin", + (module, set, context) => { + set.add(RuntimeGlobals.createScript); + } + ); + } } ); } diff --git a/lib/RuntimeGlobals.js b/lib/RuntimeGlobals.js index 79d5ad5c633..6a7a6cfcdee 100644 --- a/lib/RuntimeGlobals.js +++ b/lib/RuntimeGlobals.js @@ -168,6 +168,13 @@ exports.scriptNonce = "__webpack_require__.nc"; */ exports.loadScript = "__webpack_require__.l"; +/** + * function to promote a string to a TrustedScript using webpack's Trusted + * Types policy + * Arguments: (script: string) => TrustedScript + */ +exports.createScript = "__webpack_require__.ts"; + /** * function to promote a string to a TrustedScriptURL using webpack's Trusted * Types policy @@ -175,6 +182,12 @@ exports.loadScript = "__webpack_require__.l"; */ exports.createScriptUrl = "__webpack_require__.tu"; +/** + * function to return webpack's Trusted Types policy + * Arguments: () => TrustedTypePolicy + */ +exports.getTrustedTypesPolicy = "__webpack_require__.tt"; + /** * the chunk name of the chunk with the runtime */ diff --git a/lib/RuntimePlugin.js b/lib/RuntimePlugin.js index 5ab0b7d78ef..4f6faddd70f 100644 --- a/lib/RuntimePlugin.js +++ b/lib/RuntimePlugin.js @@ -13,11 +13,13 @@ const AutoPublicPathRuntimeModule = require("./runtime/AutoPublicPathRuntimeModu const CompatGetDefaultExportRuntimeModule = require("./runtime/CompatGetDefaultExportRuntimeModule"); const CompatRuntimeModule = require("./runtime/CompatRuntimeModule"); const CreateFakeNamespaceObjectRuntimeModule = require("./runtime/CreateFakeNamespaceObjectRuntimeModule"); +const CreateScriptRuntimeModule = require("./runtime/CreateScriptRuntimeModule"); const CreateScriptUrlRuntimeModule = require("./runtime/CreateScriptUrlRuntimeModule"); const DefinePropertyGettersRuntimeModule = require("./runtime/DefinePropertyGettersRuntimeModule"); const EnsureChunkRuntimeModule = require("./runtime/EnsureChunkRuntimeModule"); const GetChunkFilenameRuntimeModule = require("./runtime/GetChunkFilenameRuntimeModule"); const GetMainFilenameRuntimeModule = require("./runtime/GetMainFilenameRuntimeModule"); +const GetTrustedTypesPolicyRuntimeModule = require("./runtime/GetTrustedTypesPolicyRuntimeModule"); const GlobalRuntimeModule = require("./runtime/GlobalRuntimeModule"); const HasOwnPropertyRuntimeModule = require("./runtime/HasOwnPropertyRuntimeModule"); const LoadScriptRuntimeModule = require("./runtime/LoadScriptRuntimeModule"); @@ -39,7 +41,9 @@ const GLOBALS_ON_REQUIRE = [ RuntimeGlobals.runtimeId, RuntimeGlobals.compatGetDefaultExport, RuntimeGlobals.createFakeNamespaceObject, + RuntimeGlobals.createScript, RuntimeGlobals.createScriptUrl, + RuntimeGlobals.getTrustedTypesPolicy, RuntimeGlobals.definePropertyGetters, RuntimeGlobals.ensureChunk, RuntimeGlobals.entryModuleId, @@ -331,15 +335,36 @@ class RuntimePlugin { ); return true; }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.createScript) + .tap("RuntimePlugin", (chunk, set) => { + if (compilation.outputOptions.trustedTypes) { + set.add(RuntimeGlobals.getTrustedTypesPolicy); + } + compilation.addRuntimeModule(chunk, new CreateScriptRuntimeModule()); + return true; + }); compilation.hooks.runtimeRequirementInTree .for(RuntimeGlobals.createScriptUrl) .tap("RuntimePlugin", (chunk, set) => { + if (compilation.outputOptions.trustedTypes) { + set.add(RuntimeGlobals.getTrustedTypesPolicy); + } compilation.addRuntimeModule( chunk, new CreateScriptUrlRuntimeModule() ); return true; }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.getTrustedTypesPolicy) + .tap("RuntimePlugin", (chunk, set) => { + compilation.addRuntimeModule( + chunk, + new GetTrustedTypesPolicyRuntimeModule(set) + ); + return true; + }); compilation.hooks.runtimeRequirementInTree .for(RuntimeGlobals.relativeUrl) .tap("RuntimePlugin", (chunk, set) => { diff --git a/lib/runtime/CreateScriptRuntimeModule.js b/lib/runtime/CreateScriptRuntimeModule.js new file mode 100644 index 00000000000..ad174fa4d93 --- /dev/null +++ b/lib/runtime/CreateScriptRuntimeModule.js @@ -0,0 +1,36 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HelperRuntimeModule = require("./HelperRuntimeModule"); + +class CreateScriptRuntimeModule extends HelperRuntimeModule { + constructor() { + super("trusted types script"); + } + + /** + * @returns {string} runtime code + */ + generate() { + const { compilation } = this; + const { runtimeTemplate, outputOptions } = compilation; + const { trustedTypes } = outputOptions; + const fn = RuntimeGlobals.createScript; + + return Template.asString( + `${fn} = ${runtimeTemplate.returningFunction( + trustedTypes + ? `${RuntimeGlobals.getTrustedTypesPolicy}().createScript(script)` + : "script", + "script" + )};` + ); + } +} + +module.exports = CreateScriptRuntimeModule; diff --git a/lib/runtime/CreateScriptUrlRuntimeModule.js b/lib/runtime/CreateScriptUrlRuntimeModule.js index d12b92caac8..63a5b0eada2 100644 --- a/lib/runtime/CreateScriptUrlRuntimeModule.js +++ b/lib/runtime/CreateScriptUrlRuntimeModule.js @@ -10,7 +10,7 @@ const HelperRuntimeModule = require("./HelperRuntimeModule"); class CreateScriptUrlRuntimeModule extends HelperRuntimeModule { constructor() { - super("trusted types"); + super("trusted types script url"); } /** @@ -22,39 +22,14 @@ class CreateScriptUrlRuntimeModule extends HelperRuntimeModule { const { trustedTypes } = outputOptions; const fn = RuntimeGlobals.createScriptUrl; - if (!trustedTypes) { - // Skip Trusted Types logic. - return Template.asString([ - `${fn} = ${runtimeTemplate.returningFunction("url", "url")};` - ]); - } - - return Template.asString([ - "var policy;", - `${fn} = ${runtimeTemplate.basicFunction("url", [ - "// Create Trusted Type policy if Trusted Types are available and the policy doesn't exist yet.", - "if (policy === undefined) {", - Template.indent([ - "policy = {", - Template.indent([ - `createScriptURL: ${runtimeTemplate.returningFunction( - "url", - "url" - )}` - ]), - "};", - 'if (typeof trustedTypes !== "undefined" && trustedTypes.createPolicy) {', - Template.indent([ - `policy = trustedTypes.createPolicy(${JSON.stringify( - trustedTypes.policyName - )}, policy);` - ]), - "}" - ]), - "}", - "return policy.createScriptURL(url);" - ])};` - ]); + return Template.asString( + `${fn} = ${runtimeTemplate.returningFunction( + trustedTypes + ? `${RuntimeGlobals.getTrustedTypesPolicy}().createScriptURL(url)` + : "url", + "url" + )};` + ); } } diff --git a/lib/runtime/GetTrustedTypesPolicyRuntimeModule.js b/lib/runtime/GetTrustedTypesPolicyRuntimeModule.js new file mode 100644 index 00000000000..9f719e3ac1b --- /dev/null +++ b/lib/runtime/GetTrustedTypesPolicyRuntimeModule.js @@ -0,0 +1,76 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HelperRuntimeModule = require("./HelperRuntimeModule"); + +class GetTrustedTypesPolicyRuntimeModule extends HelperRuntimeModule { + /** + * @param {Set} runtimeRequirements runtime requirements + */ + constructor(runtimeRequirements) { + super("trusted types policy"); + this.runtimeRequirements = runtimeRequirements; + } + + /** + * @returns {string} runtime code + */ + generate() { + const { compilation } = this; + const { runtimeTemplate, outputOptions } = compilation; + const { trustedTypes } = outputOptions; + const fn = RuntimeGlobals.getTrustedTypesPolicy; + + return Template.asString([ + "var policy;", + `${fn} = ${runtimeTemplate.basicFunction("", [ + "// Create Trusted Type policy if Trusted Types are available and the policy doesn't exist yet.", + "if (policy === undefined) {", + Template.indent([ + "policy = {", + Template.indent( + [ + ...(this.runtimeRequirements.has(RuntimeGlobals.createScript) + ? [ + `createScript: ${runtimeTemplate.returningFunction( + "script", + "script" + )}` + ] + : []), + ...(this.runtimeRequirements.has(RuntimeGlobals.createScriptUrl) + ? [ + `createScriptURL: ${runtimeTemplate.returningFunction( + "url", + "url" + )}` + ] + : []) + ].join(",\n") + ), + "};", + ...(trustedTypes + ? [ + 'if (typeof trustedTypes !== "undefined" && trustedTypes.createPolicy) {', + Template.indent([ + `policy = trustedTypes.createPolicy(${JSON.stringify( + trustedTypes.policyName + )}, policy);` + ]), + "}" + ] + : []) + ]), + "}", + "return policy;" + ])};` + ]); + } +} + +module.exports = GetTrustedTypesPolicyRuntimeModule; diff --git a/lib/webworker/ImportScriptsChunkLoadingPlugin.js b/lib/webworker/ImportScriptsChunkLoadingPlugin.js index e14f384ffdb..b0dda12cb0c 100644 --- a/lib/webworker/ImportScriptsChunkLoadingPlugin.js +++ b/lib/webworker/ImportScriptsChunkLoadingPlugin.js @@ -6,7 +6,6 @@ "use strict"; const RuntimeGlobals = require("../RuntimeGlobals"); -const CreateScriptUrlRuntimeModule = require("../runtime/CreateScriptUrlRuntimeModule"); const StartupChunkDependenciesPlugin = require("../runtime/StartupChunkDependenciesPlugin"); const ImportScriptsChunkLoadingRuntimeModule = require("./ImportScriptsChunkLoadingRuntimeModule"); @@ -43,7 +42,9 @@ class ImportScriptsChunkLoadingPlugin { const withCreateScriptUrl = !!compilation.outputOptions.trustedTypes; set.add(RuntimeGlobals.moduleFactoriesAddOnly); set.add(RuntimeGlobals.hasOwnProperty); - if (withCreateScriptUrl) set.add(RuntimeGlobals.createScriptUrl); + if (withCreateScriptUrl) { + set.add(RuntimeGlobals.createScriptUrl); + } compilation.addRuntimeModule( chunk, new ImportScriptsChunkLoadingRuntimeModule(set, withCreateScriptUrl) @@ -61,15 +62,6 @@ class ImportScriptsChunkLoadingPlugin { compilation.hooks.runtimeRequirementInTree .for(RuntimeGlobals.baseURI) .tap("ImportScriptsChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.createScriptUrl) - .tap("RuntimePlugin", (chunk, set) => { - compilation.addRuntimeModule( - chunk, - new CreateScriptUrlRuntimeModule() - ); - return true; - }); compilation.hooks.runtimeRequirementInTree .for(RuntimeGlobals.ensureChunkHandlers) diff --git a/test/configCases/trusted-types/devtool-eval/index.js b/test/configCases/trusted-types/devtool-eval/index.js new file mode 100644 index 00000000000..5499320847f --- /dev/null +++ b/test/configCases/trusted-types/devtool-eval/index.js @@ -0,0 +1,45 @@ +it("should pass TrustedScript to eval", function () { + var policy = __webpack_require__.tt(); + policy.createScript = jest.fn(script => { + expect(typeof script).toEqual("string"); + return new TrustedScript(script); + }); + + require("./test.js"); + expect(window.module.exports).toBeInstanceOf(Object); + expect(window.module.exports.foo).toEqual("bar"); + + const testPattern = + "var test = {\\s*foo: 'bar'\\s*};\\s*module.exports = test;"; + expect(policy.createScript).toBeCalledWith( + expect.stringMatching(testPattern) + ); + expect(window.eval).toBeCalledWith( + expect.objectContaining({ + _script: expect.stringMatching(testPattern) + }) + ); +}); + +class TrustedScript { + constructor(script) { + this._script = script; + } +} + +let globalEval; +beforeEach(done => { + globalEval = eval; + window.module = {}; + window.eval = jest.fn(x => { + expect(x).toBeInstanceOf(TrustedScript); + return globalEval(x._script); + }); + done(); +}); + +afterEach(done => { + delete window.module; + window.eval = globalEval; + done(); +}); diff --git a/test/configCases/trusted-types/devtool-eval/test.js b/test/configCases/trusted-types/devtool-eval/test.js new file mode 100644 index 00000000000..0c72e78b18d --- /dev/null +++ b/test/configCases/trusted-types/devtool-eval/test.js @@ -0,0 +1,5 @@ +var test = { + foo: 'bar' +}; + +module.exports = test; diff --git a/test/configCases/trusted-types/devtool-eval/webpack.config.js b/test/configCases/trusted-types/devtool-eval/webpack.config.js new file mode 100644 index 00000000000..1356571fd81 --- /dev/null +++ b/test/configCases/trusted-types/devtool-eval/webpack.config.js @@ -0,0 +1,19 @@ +/** @type {import("../../../../").Configuration[]} */ +module.exports = [ + { + target: "web", + output: { + filename: "bundle0.js", + trustedTypes: true + }, + devtool: "eval-source-map" + }, + { + target: "web", + output: { + filename: "bundle1.js", + trustedTypes: true + }, + devtool: "eval" + } +]; diff --git a/types.d.ts b/types.d.ts index 94156d9cfe4..e24f368c518 100644 --- a/types.d.ts +++ b/types.d.ts @@ -12184,7 +12184,9 @@ declare namespace exports { export let uncaughtErrorHandler: string; export let scriptNonce: string; export let loadScript: string; + export let createScript: string; export let createScriptUrl: string; + export let getTrustedTypesPolicy: string; export let chunkName: string; export let runtimeId: string; export let getChunkScriptFilename: string;