mirror of
https://github.com/flutter/flutter
synced 2024-08-27 20:10:11 +00:00
[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);
|
||||
|
||||
enum ServiceWorkerTestType {
|
||||
// Mocks how FF disables service workers.
|
||||
blockedServiceWorkers,
|
||||
// Drops the main.dart.js directly on the page.
|
||||
withoutFlutterJs,
|
||||
// Uses the standard, promise-based, flutterJS initialization.
|
||||
withFlutterJs,
|
||||
// Uses the shorthand engineInitializer.autoStart();
|
||||
withFlutterJsShort,
|
||||
// Uses onEntrypointLoaded callback instead of returned promise.
|
||||
withFlutterJsEntrypointLoadedEvent,
|
||||
|
||||
// Same as withFlutterJsEntrypointLoadedEvent, but with TrustedTypes enabled.
|
||||
withFlutterJsTrustedTypesOn,
|
||||
// Entrypoint generated by `flutter create`.
|
||||
generatedEntrypoint,
|
||||
}
|
||||
|
@ -44,10 +50,12 @@ Future<void> main() async {
|
|||
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
|
||||
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
||||
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.withFlutterJs);
|
||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent);
|
||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn);
|
||||
await runWebServiceWorkerTestWithGeneratedEntrypoint(headless: false);
|
||||
await runWebServiceWorkerTestWithBlockedServiceWorkers(headless: false);
|
||||
|
||||
|
@ -112,6 +120,9 @@ String _testTypeToIndexFile(ServiceWorkerTestType type) {
|
|||
case ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent:
|
||||
indexFile = 'index_with_flutterjs_entrypoint_loaded.html';
|
||||
break;
|
||||
case ServiceWorkerTestType.withFlutterJsTrustedTypesOn:
|
||||
indexFile = 'index_with_flutterjs_el_tt_on.html';
|
||||
break;
|
||||
case ServiceWorkerTestType.generatedEntrypoint:
|
||||
indexFile = 'generated_entrypoint.html';
|
||||
break;
|
||||
|
|
|
@ -1195,10 +1195,12 @@ Future<void> _runWebLongRunningTests() async {
|
|||
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
||||
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
||||
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent),
|
||||
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn),
|
||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs),
|
||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent),
|
||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn),
|
||||
() => runWebServiceWorkerTestWithGeneratedEntrypoint(headless: true),
|
||||
() => runWebServiceWorkerTestWithBlockedServiceWorkers(headless: true),
|
||||
() => _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);
|
||||
});
|
||||
|
||||
// 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.
|
||||
var mapperEl = document.createElement("script");
|
||||
mapperEl.defer = true;
|
||||
mapperEl.async = false;
|
||||
mapperEl.src = "$mapperUrl";
|
||||
mapperEl.src = getTTScriptUrl("mapper");
|
||||
document.head.appendChild(mapperEl);
|
||||
|
||||
// Attach require JS.
|
||||
var requireEl = document.createElement("script");
|
||||
requireEl.defer = true;
|
||||
requireEl.async = false;
|
||||
requireEl.src = "$requireUrl";
|
||||
requireEl.src = getTTScriptUrl("requireJs");
|
||||
// This attribute tells require JS what to load as main (defined below).
|
||||
requireEl.setAttribute("data-main", "main_module.bootstrap");
|
||||
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.
|
||||
*
|
||||
* @see: https://developers.google.com/web/fundamentals/primers/service-workers
|
||||
*/
|
||||
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,
|
||||
* configured by `settings` has been loaded and activated.
|
||||
|
@ -84,8 +125,14 @@ _flutter.loader = null;
|
|||
timeoutMillis = 4000,
|
||||
} = settings;
|
||||
|
||||
// Apply the TrustedTypes policy, if present.
|
||||
let url = serviceWorkerUrl;
|
||||
if (this._ttPolicy != null) {
|
||||
url = this._ttPolicy.createScriptURL(url);
|
||||
}
|
||||
|
||||
const serviceWorkerActivation = navigator.serviceWorker
|
||||
.register(serviceWorkerUrl)
|
||||
.register(url)
|
||||
.then(this._getNewServiceWorker)
|
||||
.then(this._waitForServiceWorkerActivation);
|
||||
|
||||
|
@ -173,6 +220,14 @@ _flutter.loader = null;
|
|||
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
|
||||
* user-specified `onEntrypointLoaded` callback with an EngineInitializer
|
||||
|
@ -262,7 +317,12 @@ _flutter.loader = null;
|
|||
_createScriptTag(url) {
|
||||
const scriptTag = document.createElement("script");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -285,9 +345,13 @@ _flutter.loader = null;
|
|||
async loadEntrypoint(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
|
||||
// (and dynamically imported from a module if not present).
|
||||
const serviceWorkerLoader = new FlutterServiceWorkerLoader();
|
||||
serviceWorkerLoader.setTrustedTypesPolicy(flutterTT.policy);
|
||||
await serviceWorkerLoader.loadServiceWorker(serviceWorker).catch(e => {
|
||||
// Regardless of what happens with the injection of the SW, the show must go on
|
||||
console.warn("Exception while loading service worker:", e);
|
||||
|
@ -296,6 +360,7 @@ _flutter.loader = null;
|
|||
// The FlutterEntrypointLoader instance could be injected as a dependency
|
||||
// (and dynamically imported from a module if not present).
|
||||
const entrypointLoader = new FlutterEntrypointLoader();
|
||||
entrypointLoader.setTrustedTypesPolicy(flutterTT.policy);
|
||||
// Install the `didCreateEngineInitializer` listener where Flutter web expects it to be.
|
||||
this.didCreateEngineInitializer =
|
||||
entrypointLoader.didCreateEngineInitializer.bind(entrypointLoader);
|
||||
|
|
|
@ -14,9 +14,11 @@ void main() {
|
|||
mapperUrl: 'mapper.js',
|
||||
);
|
||||
// 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.
|
||||
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.
|
||||
expect(result, contains('requireEl.setAttribute("data-main", "main_module.bootstrap");'));
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue