[web] Use TrustedTypes in flutter.js and other tools (#112969)
This commit is contained in:
parent
782baecc50
commit
883469229e
@ -25,12 +25,18 @@ final String _targetWithBlockedServiceWorkers = path.join('lib', 'service_worker
|
|||||||
final String _targetPath = path.join(_testAppDirectory, _target);
|
final String _targetPath = path.join(_testAppDirectory, _target);
|
||||||
|
|
||||||
enum ServiceWorkerTestType {
|
enum ServiceWorkerTestType {
|
||||||
|
// Mocks how FF disables service workers.
|
||||||
blockedServiceWorkers,
|
blockedServiceWorkers,
|
||||||
|
// Drops the main.dart.js directly on the page.
|
||||||
withoutFlutterJs,
|
withoutFlutterJs,
|
||||||
|
// Uses the standard, promise-based, flutterJS initialization.
|
||||||
withFlutterJs,
|
withFlutterJs,
|
||||||
|
// Uses the shorthand engineInitializer.autoStart();
|
||||||
withFlutterJsShort,
|
withFlutterJsShort,
|
||||||
|
// Uses onEntrypointLoaded callback instead of returned promise.
|
||||||
withFlutterJsEntrypointLoadedEvent,
|
withFlutterJsEntrypointLoadedEvent,
|
||||||
|
// Same as withFlutterJsEntrypointLoadedEvent, but with TrustedTypes enabled.
|
||||||
|
withFlutterJsTrustedTypesOn,
|
||||||
// Entrypoint generated by `flutter create`.
|
// Entrypoint generated by `flutter create`.
|
||||||
generatedEntrypoint,
|
generatedEntrypoint,
|
||||||
}
|
}
|
||||||
@ -44,10 +50,12 @@ Future<void> main() async {
|
|||||||
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
|
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
|
||||||
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
||||||
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent);
|
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent);
|
||||||
|
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn);
|
||||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
|
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
|
||||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
|
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
|
||||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
||||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent);
|
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent);
|
||||||
|
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn);
|
||||||
await runWebServiceWorkerTestWithGeneratedEntrypoint(headless: false);
|
await runWebServiceWorkerTestWithGeneratedEntrypoint(headless: false);
|
||||||
await runWebServiceWorkerTestWithBlockedServiceWorkers(headless: false);
|
await runWebServiceWorkerTestWithBlockedServiceWorkers(headless: false);
|
||||||
|
|
||||||
@ -112,6 +120,9 @@ String _testTypeToIndexFile(ServiceWorkerTestType type) {
|
|||||||
case ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent:
|
case ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent:
|
||||||
indexFile = 'index_with_flutterjs_entrypoint_loaded.html';
|
indexFile = 'index_with_flutterjs_entrypoint_loaded.html';
|
||||||
break;
|
break;
|
||||||
|
case ServiceWorkerTestType.withFlutterJsTrustedTypesOn:
|
||||||
|
indexFile = 'index_with_flutterjs_el_tt_on.html';
|
||||||
|
break;
|
||||||
case ServiceWorkerTestType.generatedEntrypoint:
|
case ServiceWorkerTestType.generatedEntrypoint:
|
||||||
indexFile = 'generated_entrypoint.html';
|
indexFile = 'generated_entrypoint.html';
|
||||||
break;
|
break;
|
||||||
|
@ -1195,10 +1195,12 @@ Future<void> _runWebLongRunningTests() async {
|
|||||||
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
||||||
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
||||||
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent),
|
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent),
|
||||||
|
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn),
|
||||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs),
|
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs),
|
||||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
||||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
||||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent),
|
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent),
|
||||||
|
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn),
|
||||||
() => runWebServiceWorkerTestWithGeneratedEntrypoint(headless: true),
|
() => runWebServiceWorkerTestWithGeneratedEntrypoint(headless: true),
|
||||||
() => runWebServiceWorkerTestWithBlockedServiceWorkers(headless: true),
|
() => runWebServiceWorkerTestWithBlockedServiceWorkers(headless: true),
|
||||||
() => _runWebStackTraceTest('profile', 'lib/stack_trace.dart'),
|
() => _runWebStackTraceTest('profile', 'lib/stack_trace.dart'),
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
|
found in the LICENSE file. -->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
|
|
||||||
|
<title>Integration test. App load with flutter.js and onEntrypointLoaded API. Trusted Types enabled.</title>
|
||||||
|
|
||||||
|
<!-- Enable TrustedTypes for 'script'-->
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'">
|
||||||
|
|
||||||
|
<!-- iOS meta tags & icons -->
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Web Test">
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
<script>
|
||||||
|
// The value below is injected by flutter build, do not touch.
|
||||||
|
var serviceWorkerVersion = null;
|
||||||
|
</script>
|
||||||
|
<!-- This script adds the flutter initialization JS code -->
|
||||||
|
<script src="flutter.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', function(ev) {
|
||||||
|
// Download main.dart.js
|
||||||
|
_flutter.loader.loadEntrypoint({
|
||||||
|
onEntrypointLoaded: onEntrypointLoaded,
|
||||||
|
serviceWorker: {
|
||||||
|
serviceWorkerVersion: serviceWorkerVersion,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Once the entrypoint is ready, do things!
|
||||||
|
async function onEntrypointLoaded(engineInitializer) {
|
||||||
|
const appRunner = await engineInitializer.initializeEngine();
|
||||||
|
appRunner.runApp();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -94,18 +94,45 @@ document.addEventListener('dart-app-ready', function (e) {
|
|||||||
styleSheet.parentNode.removeChild(styleSheet);
|
styleSheet.parentNode.removeChild(styleSheet);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// A map containing the URLs for the bootstrap scripts in debug.
|
||||||
|
let _scriptUrls = {
|
||||||
|
"mapper": "$mapperUrl",
|
||||||
|
"requireJs": "$requireUrl"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a TrustedTypes policy so we can attach Scripts...
|
||||||
|
let _ttPolicy;
|
||||||
|
if (window.trustedTypes) {
|
||||||
|
_ttPolicy = trustedTypes.createPolicy("flutter-tools-bootstrap", {
|
||||||
|
createScriptURL: (url) => {
|
||||||
|
let scriptUrl = _scriptUrls[url];
|
||||||
|
if (!scriptUrl) {
|
||||||
|
console.error("Unknown Flutter Web bootstrap resource!", url);
|
||||||
|
}
|
||||||
|
return scriptUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a TrustedScriptURL for a given `scriptName`.
|
||||||
|
// See `_scriptUrls` and `_ttPolicy` above.
|
||||||
|
function getTTScriptUrl(scriptName) {
|
||||||
|
let defaultUrl = _scriptUrls[scriptName];
|
||||||
|
return _ttPolicy ? _ttPolicy.createScriptURL(scriptName) : defaultUrl;
|
||||||
|
}
|
||||||
|
|
||||||
// Attach source mapping.
|
// Attach source mapping.
|
||||||
var mapperEl = document.createElement("script");
|
var mapperEl = document.createElement("script");
|
||||||
mapperEl.defer = true;
|
mapperEl.defer = true;
|
||||||
mapperEl.async = false;
|
mapperEl.async = false;
|
||||||
mapperEl.src = "$mapperUrl";
|
mapperEl.src = getTTScriptUrl("mapper");
|
||||||
document.head.appendChild(mapperEl);
|
document.head.appendChild(mapperEl);
|
||||||
|
|
||||||
// Attach require JS.
|
// Attach require JS.
|
||||||
var requireEl = document.createElement("script");
|
var requireEl = document.createElement("script");
|
||||||
requireEl.defer = true;
|
requireEl.defer = true;
|
||||||
requireEl.async = false;
|
requireEl.async = false;
|
||||||
requireEl.src = "$requireUrl";
|
requireEl.src = getTTScriptUrl("requireJs");
|
||||||
// This attribute tells require JS what to load as main (defined below).
|
// This attribute tells require JS what to load as main (defined below).
|
||||||
requireEl.setAttribute("data-main", "main_module.bootstrap");
|
requireEl.setAttribute("data-main", "main_module.bootstrap");
|
||||||
document.head.appendChild(requireEl);
|
document.head.appendChild(requireEl);
|
||||||
|
@ -56,12 +56,53 @@ _flutter.loader = null;
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the creation of a TrustedTypes `policy` that validates URLs based
|
||||||
|
* on an (optional) incoming array of RegExes.
|
||||||
|
*/
|
||||||
|
class FlutterTrustedTypesPolicy {
|
||||||
|
/**
|
||||||
|
* Constructs the policy.
|
||||||
|
* @param {[RegExp]} validPatterns the patterns to test URLs
|
||||||
|
* @param {String} policyName the policy name (optional)
|
||||||
|
*/
|
||||||
|
constructor(validPatterns, policyName = "flutter-js") {
|
||||||
|
const patterns = validPatterns || [
|
||||||
|
/\.dart\.js$/,
|
||||||
|
/^flutter_service_worker.js$/
|
||||||
|
];
|
||||||
|
if (window.trustedTypes) {
|
||||||
|
this.policy = trustedTypes.createPolicy(policyName, {
|
||||||
|
createScriptURL: function(url) {
|
||||||
|
const parsed = new URL(url, window.location);
|
||||||
|
const file = parsed.pathname.split("/").pop();
|
||||||
|
const matches = patterns.some((pattern) => pattern.test(file));
|
||||||
|
if (matches) {
|
||||||
|
return parsed.toString();
|
||||||
|
}
|
||||||
|
console.error(
|
||||||
|
"URL rejected by TrustedTypes policy",
|
||||||
|
policyName, ":", url, "(download prevented)");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles loading/reloading Flutter's service worker, if configured.
|
* Handles loading/reloading Flutter's service worker, if configured.
|
||||||
*
|
*
|
||||||
* @see: https://developers.google.com/web/fundamentals/primers/service-workers
|
* @see: https://developers.google.com/web/fundamentals/primers/service-workers
|
||||||
*/
|
*/
|
||||||
class FlutterServiceWorkerLoader {
|
class FlutterServiceWorkerLoader {
|
||||||
|
/**
|
||||||
|
* Injects a TrustedTypesPolicy (or undefined if the feature is not supported).
|
||||||
|
* @param {TrustedTypesPolicy | undefined} policy
|
||||||
|
*/
|
||||||
|
setTrustedTypesPolicy(policy) {
|
||||||
|
this._ttPolicy = policy;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Promise that resolves when the latest Flutter service worker,
|
* Returns a Promise that resolves when the latest Flutter service worker,
|
||||||
* configured by `settings` has been loaded and activated.
|
* configured by `settings` has been loaded and activated.
|
||||||
@ -84,8 +125,14 @@ _flutter.loader = null;
|
|||||||
timeoutMillis = 4000,
|
timeoutMillis = 4000,
|
||||||
} = settings;
|
} = settings;
|
||||||
|
|
||||||
|
// Apply the TrustedTypes policy, if present.
|
||||||
|
let url = serviceWorkerUrl;
|
||||||
|
if (this._ttPolicy != null) {
|
||||||
|
url = this._ttPolicy.createScriptURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
const serviceWorkerActivation = navigator.serviceWorker
|
const serviceWorkerActivation = navigator.serviceWorker
|
||||||
.register(serviceWorkerUrl)
|
.register(url)
|
||||||
.then(this._getNewServiceWorker)
|
.then(this._getNewServiceWorker)
|
||||||
.then(this._waitForServiceWorkerActivation);
|
.then(this._waitForServiceWorkerActivation);
|
||||||
|
|
||||||
@ -173,6 +220,14 @@ _flutter.loader = null;
|
|||||||
this._scriptLoaded = false;
|
this._scriptLoaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects a TrustedTypesPolicy (or undefined if the feature is not supported).
|
||||||
|
* @param {TrustedTypesPolicy | undefined} policy
|
||||||
|
*/
|
||||||
|
setTrustedTypesPolicy(policy) {
|
||||||
|
this._ttPolicy = policy;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads flutter main entrypoint, specified by `entrypointUrl`, and calls a
|
* Loads flutter main entrypoint, specified by `entrypointUrl`, and calls a
|
||||||
* user-specified `onEntrypointLoaded` callback with an EngineInitializer
|
* user-specified `onEntrypointLoaded` callback with an EngineInitializer
|
||||||
@ -262,7 +317,12 @@ _flutter.loader = null;
|
|||||||
_createScriptTag(url) {
|
_createScriptTag(url) {
|
||||||
const scriptTag = document.createElement("script");
|
const scriptTag = document.createElement("script");
|
||||||
scriptTag.type = "application/javascript";
|
scriptTag.type = "application/javascript";
|
||||||
scriptTag.src = url;
|
// Apply TrustedTypes validation, if available.
|
||||||
|
let trustedUrl = url;
|
||||||
|
if (this._ttPolicy != null) {
|
||||||
|
trustedUrl = this._ttPolicy.createScriptURL(url);
|
||||||
|
}
|
||||||
|
scriptTag.src = trustedUrl;
|
||||||
return scriptTag;
|
return scriptTag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -285,9 +345,13 @@ _flutter.loader = null;
|
|||||||
async loadEntrypoint(options) {
|
async loadEntrypoint(options) {
|
||||||
const { serviceWorker, ...entrypoint } = options || {};
|
const { serviceWorker, ...entrypoint } = options || {};
|
||||||
|
|
||||||
|
// A Trusted Types policy that is going to be used by the loader.
|
||||||
|
const flutterTT = new FlutterTrustedTypesPolicy();
|
||||||
|
|
||||||
// The FlutterServiceWorkerLoader instance could be injected as a dependency
|
// The FlutterServiceWorkerLoader instance could be injected as a dependency
|
||||||
// (and dynamically imported from a module if not present).
|
// (and dynamically imported from a module if not present).
|
||||||
const serviceWorkerLoader = new FlutterServiceWorkerLoader();
|
const serviceWorkerLoader = new FlutterServiceWorkerLoader();
|
||||||
|
serviceWorkerLoader.setTrustedTypesPolicy(flutterTT.policy);
|
||||||
await serviceWorkerLoader.loadServiceWorker(serviceWorker).catch(e => {
|
await serviceWorkerLoader.loadServiceWorker(serviceWorker).catch(e => {
|
||||||
// Regardless of what happens with the injection of the SW, the show must go on
|
// Regardless of what happens with the injection of the SW, the show must go on
|
||||||
console.warn("Exception while loading service worker:", e);
|
console.warn("Exception while loading service worker:", e);
|
||||||
@ -296,6 +360,7 @@ _flutter.loader = null;
|
|||||||
// The FlutterEntrypointLoader instance could be injected as a dependency
|
// The FlutterEntrypointLoader instance could be injected as a dependency
|
||||||
// (and dynamically imported from a module if not present).
|
// (and dynamically imported from a module if not present).
|
||||||
const entrypointLoader = new FlutterEntrypointLoader();
|
const entrypointLoader = new FlutterEntrypointLoader();
|
||||||
|
entrypointLoader.setTrustedTypesPolicy(flutterTT.policy);
|
||||||
// Install the `didCreateEngineInitializer` listener where Flutter web expects it to be.
|
// Install the `didCreateEngineInitializer` listener where Flutter web expects it to be.
|
||||||
this.didCreateEngineInitializer =
|
this.didCreateEngineInitializer =
|
||||||
entrypointLoader.didCreateEngineInitializer.bind(entrypointLoader);
|
entrypointLoader.didCreateEngineInitializer.bind(entrypointLoader);
|
||||||
|
@ -14,9 +14,11 @@ void main() {
|
|||||||
mapperUrl: 'mapper.js',
|
mapperUrl: 'mapper.js',
|
||||||
);
|
);
|
||||||
// require js source is interpolated correctly.
|
// require js source is interpolated correctly.
|
||||||
expect(result, contains('requireEl.src = "require.js";'));
|
expect(result, contains('"requireJs": "require.js"'));
|
||||||
|
expect(result, contains('requireEl.src = getTTScriptUrl("requireJs");'));
|
||||||
// stack trace mapper source is interpolated correctly.
|
// stack trace mapper source is interpolated correctly.
|
||||||
expect(result, contains('mapperEl.src = "mapper.js";'));
|
expect(result, contains('"mapper": "mapper.js"'));
|
||||||
|
expect(result, contains('mapperEl.src = getTTScriptUrl("mapper");'));
|
||||||
// data-main is set to correct bootstrap module.
|
// data-main is set to correct bootstrap module.
|
||||||
expect(result, contains('requireEl.setAttribute("data-main", "main_module.bootstrap");'));
|
expect(result, contains('requireEl.setAttribute("data-main", "main_module.bootstrap");'));
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user