Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 03756d2

Browse files
authored
[web] Use TrustedTypes to load canvaskit (where available) (#36608)
1 parent 213e294 commit 03756d2

File tree

3 files changed

+189
-2
lines changed

3 files changed

+189
-2
lines changed

lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2652,7 +2652,7 @@ Future<void> _downloadCanvasKitJs() {
26522652
final String canvasKitJavaScriptUrl = canvasKitJavaScriptBindingsUrl;
26532653

26542654
final DomHTMLScriptElement canvasKitScript = createDomHTMLScriptElement();
2655-
canvasKitScript.src = canvasKitJavaScriptUrl;
2655+
canvasKitScript.src = createTrustedScriptUrl(canvasKitJavaScriptUrl);
26562656

26572657
final Completer<void> canvasKitLoadCompleter = Completer<void>();
26582658
late DomEventListener callback;

lib/web_ui/lib/src/engine/dom.dart

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ extension DomWindowExtension on DomWindow {
6262
targetOrigin,
6363
if (messagePorts != null) js_util.jsify(messagePorts)
6464
]);
65+
66+
/// The Trusted Types API (when available).
67+
/// See: https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API
68+
external DomTrustedTypePolicyFactory? get trustedTypes;
6569
}
6670

6771
typedef DomRequestAnimationFrameCallback = void Function(num highResTime);
@@ -72,6 +76,7 @@ class DomConsole {}
7276

7377
extension DomConsoleExtension on DomConsole {
7478
external void warn(Object? arg);
79+
external void error(Object? arg);
7580
}
7681

7782
@JS('window')
@@ -516,7 +521,7 @@ extension DomHTMLImageElementExtension on DomHTMLImageElement {
516521
class DomHTMLScriptElement extends DomHTMLElement {}
517522

518523
extension DomHTMLScriptElementExtension on DomHTMLScriptElement {
519-
external set src(String value);
524+
external set src(Object /* String|TrustedScriptURL */ value);
520525
}
521526

522527
DomHTMLScriptElement createDomHTMLScriptElement() =>
@@ -1441,6 +1446,127 @@ extension DomCSSRuleListExtension on DomCSSRuleList {
14411446
js_util.getProperty<double>(this, 'length').toInt();
14421447
}
14431448

1449+
/// A factory to create `TrustedTypePolicy` objects.
1450+
/// See: https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicyFactory
1451+
@JS()
1452+
@staticInterop
1453+
abstract class DomTrustedTypePolicyFactory {}
1454+
1455+
/// A subset of TrustedTypePolicyFactory methods.
1456+
extension DomTrustedTypePolicyFactoryExtension on DomTrustedTypePolicyFactory {
1457+
/// Creates a TrustedTypePolicy object named `policyName` that implements the
1458+
/// rules passed as `policyOptions`.
1459+
external DomTrustedTypePolicy createPolicy(
1460+
String policyName,
1461+
DomTrustedTypePolicyOptions? policyOptions,
1462+
);
1463+
}
1464+
1465+
/// Options to create a trusted type policy.
1466+
///
1467+
/// The options are user-defined functions for converting strings into trusted
1468+
/// values.
1469+
///
1470+
/// See: https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicyFactory/createPolicy#policyoptions
1471+
@JS()
1472+
@staticInterop
1473+
@anonymous
1474+
abstract class DomTrustedTypePolicyOptions {
1475+
/// Constructs a TrustedTypePolicyOptions object in JavaScript.
1476+
///
1477+
/// `createScriptURL` is a callback function that contains code to run when
1478+
/// creating a TrustedScriptURL object.
1479+
///
1480+
/// The following properties need to be manually wrapped in [allowInterop]
1481+
/// before being passed to this constructor: [createScriptURL].
1482+
external factory DomTrustedTypePolicyOptions({
1483+
DomCreateScriptUrlOptionFn? createScriptURL,
1484+
});
1485+
}
1486+
1487+
/// Type of the function used to configure createScriptURL.
1488+
typedef DomCreateScriptUrlOptionFn = String? Function(String input);
1489+
1490+
/// A TrustedTypePolicy defines a group of functions which create TrustedType
1491+
/// objects.
1492+
///
1493+
/// TrustedTypePolicy objects are created by `TrustedTypePolicyFactory.createPolicy`,
1494+
/// therefore this class has no constructor.
1495+
///
1496+
/// See: https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicy
1497+
@JS()
1498+
@staticInterop
1499+
abstract class DomTrustedTypePolicy {}
1500+
1501+
/// A subset of TrustedTypePolicy methods.
1502+
extension DomTrustedTypePolicyExtension on DomTrustedTypePolicy {
1503+
/// Creates a `TrustedScriptURL` for the given [input].
1504+
///
1505+
/// `input` is a string containing the data to be _sanitized_ by the policy.
1506+
external DomTrustedScriptURL createScriptURL(String input);
1507+
}
1508+
1509+
/// Represents a string that a developer can insert into an _injection sink_
1510+
/// that will parse it as an external script.
1511+
///
1512+
/// These objects are created via `createScriptURL` and therefore have no
1513+
/// constructor.
1514+
///
1515+
/// See: https://developer.mozilla.org/en-US/docs/Web/API/TrustedScriptURL
1516+
@JS()
1517+
@staticInterop
1518+
abstract class DomTrustedScriptURL {}
1519+
1520+
/// A subset of TrustedScriptURL methods.
1521+
extension DomTrustedScriptUrlExtension on DomTrustedScriptURL {
1522+
/// Exposes the `toString` JS method of TrustedScriptURL.
1523+
String get url => js_util.callMethod<String>(this, 'toString', <String>[]);
1524+
}
1525+
1526+
// The expected set of files that the flutter-engine TrustedType policy is going
1527+
// to accept as valid.
1528+
const Set<String> _expectedFilesForTT = <String>{
1529+
'canvaskit.js',
1530+
};
1531+
1532+
// The definition of the `flutter-engine` TrustedType policy.
1533+
// Only accessible if the Trusted Types API is available.
1534+
final DomTrustedTypePolicy _ttPolicy = domWindow.trustedTypes!.createPolicy(
1535+
'flutter-engine',
1536+
DomTrustedTypePolicyOptions(
1537+
// Validates the given [url].
1538+
createScriptURL: allowInterop(
1539+
(String url) {
1540+
final Uri uri = Uri.parse(url);
1541+
if (_expectedFilesForTT.contains(uri.pathSegments.last)) {
1542+
return uri.toString();
1543+
}
1544+
domWindow.console
1545+
.error('URL rejected by TrustedTypes policy flutter-engine: $url'
1546+
'(download prevented)');
1547+
1548+
return null;
1549+
},
1550+
),
1551+
),
1552+
);
1553+
1554+
/// Converts a String `url` into a [DomTrustedScriptURL] object when the
1555+
/// Trusted Types API is available, else returns the unmodified `url`.
1556+
Object createTrustedScriptUrl(String url) {
1557+
if (domWindow.trustedTypes != null) {
1558+
// Pass `url` through Flutter Engine's TrustedType policy.
1559+
final DomTrustedScriptURL trustedCanvasKitUrl =
1560+
_ttPolicy.createScriptURL(url);
1561+
1562+
assert(trustedCanvasKitUrl.url != '',
1563+
'URL: $url rejected by TrustedTypePolicy');
1564+
1565+
return trustedCanvasKitUrl;
1566+
}
1567+
return url;
1568+
}
1569+
14441570
DomMessageChannel createDomMessageChannel() =>
14451571
domCallConstructorString('MessageChannel', <Object>[])!
14461572
as DomMessageChannel;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:test/bootstrap/browser.dart';
6+
import 'package:test/test.dart';
7+
import 'package:ui/src/engine.dart';
8+
9+
import '../matchers.dart';
10+
import 'canvaskit_api_test.dart';
11+
12+
final bool isBlink = browserEngine == BrowserEngine.blink;
13+
14+
const String goodUrl = 'https://www.unpkg.com/blah-blah/33.x/canvaskit.js';
15+
const String badUrl = 'https://www.unpkg.com/soemthing/not-canvaskit.js';
16+
17+
// These tests need to happen in a separate file, because a Content Security
18+
// Policy cannot be relaxed once set, only made more strict.
19+
void main() {
20+
internalBootstrapBrowserTest(() => testMainWithTTOn);
21+
}
22+
23+
// Enables Trusted Types, runs all `canvaskit_api_test.dart`, then tests the
24+
// createTrustedScriptUrl function.
25+
void testMainWithTTOn() {
26+
enableTrustedTypes();
27+
28+
// Run all standard canvaskit tests, with TT on...
29+
testMain();
30+
31+
group('TrustedTypes API supported', () {
32+
test('createTrustedScriptUrl - returns TrustedScriptURL object', () async {
33+
final Object trusted = createTrustedScriptUrl(goodUrl);
34+
35+
expect(trusted, isA<DomTrustedScriptURL>());
36+
expect((trusted as DomTrustedScriptURL).url, goodUrl);
37+
});
38+
39+
test('createTrustedScriptUrl - rejects bad canvaskit.js URL', () async {
40+
expect(() {
41+
createTrustedScriptUrl(badUrl);
42+
}, throwsAssertionError);
43+
});
44+
}, skip: !isBlink);
45+
46+
group('Trusted Types API NOT supported', () {
47+
test('createTrustedScriptUrl - returns unmodified url', () async {
48+
expect(createTrustedScriptUrl(badUrl), badUrl);
49+
});
50+
}, skip: isBlink);
51+
}
52+
53+
/// Enables Trusted Types by setting the appropriate meta tag in the DOM:
54+
/// <meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'">
55+
void enableTrustedTypes() {
56+
print('Enabling TrustedTypes in browser window...');
57+
final DomHTMLMetaElement enableTTMeta = createDomHTMLMetaElement()
58+
..setAttribute('http-equiv', 'Content-Security-Policy')
59+
..content = "require-trusted-types-for 'script'";
60+
domDocument.head!.append(enableTTMeta);
61+
}

0 commit comments

Comments
 (0)