Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2652,7 +2652,7 @@ Future<void> _downloadCanvasKitJs() {
final String canvasKitJavaScriptUrl = canvasKitJavaScriptBindingsUrl;

final DomHTMLScriptElement canvasKitScript = createDomHTMLScriptElement();
canvasKitScript.src = canvasKitJavaScriptUrl;
canvasKitScript.src = createTrustedScriptUrl(canvasKitJavaScriptUrl);

final Completer<void> canvasKitLoadCompleter = Completer<void>();
late DomEventListener callback;
Expand Down
134 changes: 133 additions & 1 deletion lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ extension DomWindowExtension on DomWindow {
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);
Expand All @@ -72,6 +76,7 @@ class DomConsole {}

extension DomConsoleExtension on DomConsole {
external void warn(Object? arg);
external void error(Object? arg);
}

@JS('window')
Expand Down Expand Up @@ -516,7 +521,7 @@ extension DomHTMLImageElementExtension on DomHTMLImageElement {
class DomHTMLScriptElement extends DomHTMLElement {}

extension DomHTMLScriptElementExtension on DomHTMLScriptElement {
external set src(String value);
external set src(Object /* String|TrustedScriptURL */ value);
}

DomHTMLScriptElement createDomHTMLScriptElement() =>
Expand Down Expand Up @@ -1439,6 +1444,133 @@ extension DomCSSRuleListExtension on DomCSSRuleList {
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 to configure createScriptURL.
typedef DomCreateScriptUrlOptionFn = String? Function(String input);

/// Dart binding of a JavaScript TrustedTypePolicy instance.
///
/// 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the string expected to have a specific format? E.g. is it a URL? "data" and "input" do not convey that meaning.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the string expected to have a specific format?

No, in the current version of the API, it can literally be anything that the "validation" function understands. It doesn't need to be a URL, it doesn't need to have special formatting... nothing.

input is the name used in MDN and the spec.

external DomTrustedScriptURL createScriptURL(String input);
}

/// Dart binding of a JavaScript TrustedScriptURL instance.
///
/// 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;
Expand Down
61 changes: 61 additions & 0 deletions lib/web_ui/test/canvaskit/canvaskit_api_tt_on_test.dart
Original file line number Diff line number Diff line change
@@ -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);
}