Skip to content

Commit

Permalink
[web] Use TrustedTypes to load canvaskit (where available) (flutter#3…
Browse files Browse the repository at this point in the history
  • Loading branch information
ditman authored Oct 19, 2022
1 parent 213e294 commit 03756d2
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 2 deletions.
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
128 changes: 127 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 @@ -1441,6 +1446,127 @@ 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 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;
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);
}

0 comments on commit 03756d2

Please sign in to comment.