[web] Use TrustedTypes to load canvaskit (where available) (#36608)

diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
index 74cd573..c1e6003 100644
--- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
+++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
@@ -2652,7 +2652,7 @@
   final String canvasKitJavaScriptUrl = canvasKitJavaScriptBindingsUrl;
 
   final DomHTMLScriptElement canvasKitScript = createDomHTMLScriptElement();
-  canvasKitScript.src = canvasKitJavaScriptUrl;
+  canvasKitScript.src = createTrustedScriptUrl(canvasKitJavaScriptUrl);
 
   final Completer<void> canvasKitLoadCompleter = Completer<void>();
   late DomEventListener callback;
diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart
index ee75499..b1e5146 100644
--- a/lib/web_ui/lib/src/engine/dom.dart
+++ b/lib/web_ui/lib/src/engine/dom.dart
@@ -62,6 +62,10 @@
         targetOrigin,
         if (messagePorts != null) js_util.jsify(messagePorts)
       ]);
+
+  /// The Trusted Types API (when available).
+  /// See: https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API
+  external DomTrustedTypePolicyFactory? get trustedTypes;
 }
 
 typedef DomRequestAnimationFrameCallback = void Function(num highResTime);
@@ -72,6 +76,7 @@
 
 extension DomConsoleExtension on DomConsole {
   external void warn(Object? arg);
+  external void error(Object? arg);
 }
 
 @JS('window')
@@ -516,7 +521,7 @@
 class DomHTMLScriptElement extends DomHTMLElement {}
 
 extension DomHTMLScriptElementExtension on DomHTMLScriptElement {
-  external set src(String value);
+  external set src(Object /* String|TrustedScriptURL */ value);
 }
 
 DomHTMLScriptElement createDomHTMLScriptElement() =>
@@ -1441,6 +1446,127 @@
       js_util.getProperty<double>(this, 'length').toInt();
 }
 
+/// A factory to create `TrustedTypePolicy` objects.
+/// See: https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicyFactory
+@JS()
+@staticInterop
+abstract class DomTrustedTypePolicyFactory {}
+
+/// A subset of TrustedTypePolicyFactory methods.
+extension DomTrustedTypePolicyFactoryExtension on DomTrustedTypePolicyFactory {
+  /// Creates a TrustedTypePolicy object named `policyName` that implements the
+  /// rules passed as `policyOptions`.
+  external DomTrustedTypePolicy createPolicy(
+    String policyName,
+    DomTrustedTypePolicyOptions? policyOptions,
+  );
+}
+
+/// Options to create a trusted type policy.
+///
+/// The options are user-defined functions for converting strings into trusted
+/// values.
+///
+/// See: https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicyFactory/createPolicy#policyoptions
+@JS()
+@staticInterop
+@anonymous
+abstract class DomTrustedTypePolicyOptions {
+  /// Constructs a TrustedTypePolicyOptions object in JavaScript.
+  ///
+  /// `createScriptURL` is a callback function that contains code to run when
+  /// creating a TrustedScriptURL object.
+  ///
+  /// The following properties need to be manually wrapped in [allowInterop]
+  /// before being passed to this constructor: [createScriptURL].
+  external factory DomTrustedTypePolicyOptions({
+    DomCreateScriptUrlOptionFn? createScriptURL,
+  });
+}
+
+/// Type of the function used to configure createScriptURL.
+typedef DomCreateScriptUrlOptionFn = String? Function(String input);
+
+/// A TrustedTypePolicy defines a group of functions which create TrustedType
+/// objects.
+///
+/// TrustedTypePolicy objects are created by `TrustedTypePolicyFactory.createPolicy`,
+/// therefore this class has no constructor.
+///
+/// See: https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicy
+@JS()
+@staticInterop
+abstract class DomTrustedTypePolicy {}
+
+/// A subset of TrustedTypePolicy methods.
+extension DomTrustedTypePolicyExtension on DomTrustedTypePolicy {
+  /// Creates a `TrustedScriptURL` for the given [input].
+  ///
+  /// `input` is a string containing the data to be _sanitized_ by the policy.
+  external DomTrustedScriptURL createScriptURL(String input);
+}
+
+/// Represents a string that a developer can insert into an _injection sink_
+/// that will parse it as an external script.
+///
+/// These objects are created via `createScriptURL` and therefore have no
+/// constructor.
+///
+/// See: https://developer.mozilla.org/en-US/docs/Web/API/TrustedScriptURL
+@JS()
+@staticInterop
+abstract class DomTrustedScriptURL {}
+
+/// A subset of TrustedScriptURL methods.
+extension DomTrustedScriptUrlExtension on DomTrustedScriptURL {
+  /// Exposes the `toString` JS method of TrustedScriptURL.
+  String get url => js_util.callMethod<String>(this, 'toString', <String>[]);
+}
+
+// The expected set of files that the flutter-engine TrustedType policy is going
+// to accept as valid.
+const Set<String> _expectedFilesForTT = <String>{
+  'canvaskit.js',
+};
+
+// The definition of the `flutter-engine` TrustedType policy.
+// Only accessible if the Trusted Types API is available.
+final DomTrustedTypePolicy _ttPolicy = domWindow.trustedTypes!.createPolicy(
+  'flutter-engine',
+  DomTrustedTypePolicyOptions(
+    // Validates the given [url].
+    createScriptURL: allowInterop(
+      (String url) {
+        final Uri uri = Uri.parse(url);
+        if (_expectedFilesForTT.contains(uri.pathSegments.last)) {
+          return uri.toString();
+        }
+        domWindow.console
+            .error('URL rejected by TrustedTypes policy flutter-engine: $url'
+                '(download prevented)');
+
+        return null;
+      },
+    ),
+  ),
+);
+
+/// Converts a String `url` into a [DomTrustedScriptURL] object when the
+/// Trusted Types API is available, else returns the unmodified `url`.
+Object createTrustedScriptUrl(String url) {
+  if (domWindow.trustedTypes != null) {
+    // Pass `url` through Flutter Engine's TrustedType policy.
+    final DomTrustedScriptURL trustedCanvasKitUrl =
+        _ttPolicy.createScriptURL(url);
+
+    assert(trustedCanvasKitUrl.url != '',
+        'URL: $url rejected by TrustedTypePolicy');
+
+    return trustedCanvasKitUrl;
+  }
+  return url;
+}
+
 DomMessageChannel createDomMessageChannel() =>
     domCallConstructorString('MessageChannel', <Object>[])!
         as DomMessageChannel;
diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_tt_on_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_tt_on_test.dart
new file mode 100644
index 0000000..05dee0b
--- /dev/null
+++ b/lib/web_ui/test/canvaskit/canvaskit_api_tt_on_test.dart
@@ -0,0 +1,61 @@
+// Copyright 2013 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.
+
+import 'package:test/bootstrap/browser.dart';
+import 'package:test/test.dart';
+import 'package:ui/src/engine.dart';
+
+import '../matchers.dart';
+import 'canvaskit_api_test.dart';
+
+final bool isBlink = browserEngine == BrowserEngine.blink;
+
+const String goodUrl = 'https://www.unpkg.com/blah-blah/33.x/canvaskit.js';
+const String badUrl = 'https://www.unpkg.com/soemthing/not-canvaskit.js';
+
+// These tests need to happen in a separate file, because a Content Security
+// Policy cannot be relaxed once set, only made more strict.
+void main() {
+  internalBootstrapBrowserTest(() => testMainWithTTOn);
+}
+
+// Enables Trusted Types, runs all `canvaskit_api_test.dart`, then tests the
+// createTrustedScriptUrl function.
+void testMainWithTTOn() {
+  enableTrustedTypes();
+
+  // Run all standard canvaskit tests, with TT on...
+  testMain();
+
+  group('TrustedTypes API supported', () {
+    test('createTrustedScriptUrl - returns TrustedScriptURL object', () async {
+      final Object trusted = createTrustedScriptUrl(goodUrl);
+
+      expect(trusted, isA<DomTrustedScriptURL>());
+      expect((trusted as DomTrustedScriptURL).url, goodUrl);
+    });
+
+    test('createTrustedScriptUrl - rejects bad canvaskit.js URL', () async {
+      expect(() {
+        createTrustedScriptUrl(badUrl);
+      }, throwsAssertionError);
+    });
+  }, skip: !isBlink);
+
+  group('Trusted Types API NOT supported', () {
+    test('createTrustedScriptUrl - returns unmodified url', () async {
+      expect(createTrustedScriptUrl(badUrl), badUrl);
+    });
+  }, skip: isBlink);
+}
+
+/// Enables Trusted Types by setting the appropriate meta tag in the DOM:
+/// <meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'">
+void enableTrustedTypes() {
+  print('Enabling TrustedTypes in browser window...');
+  final DomHTMLMetaElement enableTTMeta = createDomHTMLMetaElement()
+    ..setAttribute('http-equiv', 'Content-Security-Policy')
+    ..content = "require-trusted-types-for 'script'";
+  domDocument.head!.append(enableTTMeta);
+}