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 bd626064b580c..826c4cc8e735b 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -3596,7 +3596,8 @@ Future _downloadOneOf(Iterable urls) async { /// Returns a [Future] that completes with `true` if the CanvasKit JavaScript /// file was successfully downloaded, or `false` if it failed. Future _downloadCanvasKitJs(String url) { - final DomHTMLScriptElement canvasKitScript = createDomHTMLScriptElement(); + final DomHTMLScriptElement canvasKitScript = + createDomHTMLScriptElement(configuration.nonce); canvasKitScript.src = createTrustedScriptUrl(url); final Completer canvasKitLoadCompleter = Completer(); diff --git a/lib/web_ui/lib/src/engine/configuration.dart b/lib/web_ui/lib/src/engine/configuration.dart index 62b05c7a4fc0f..022dee08b97fb 100644 --- a/lib/web_ui/lib/src/engine/configuration.dart +++ b/lib/web_ui/lib/src/engine/configuration.dart @@ -269,6 +269,11 @@ class FlutterConfiguration { /// to render, or `null` if the user hasn't specified anything. DomElement? get hostElement => _configuration?.hostElement; + /// Returns a `nonce` to allowlist the inline styles that Flutter web needs. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce + String? get nonce => _configuration?.nonce; + /// Returns the [requestedRendererType] to be used with the current Flutter /// application, normally 'canvaskit' or 'auto'. /// @@ -320,6 +325,10 @@ extension JsFlutterConfigurationExtension on JsFlutterConfiguration { external DomElement? get hostElement; + @JS('nonce') + external JSString? get _nonce; + String? get nonce => _nonce?.toDart; + @JS('renderer') external JSString? get _renderer; String? get renderer => _renderer?.toDart; diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index 583ab2736fe0d..0b24dc5a8d45a 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -929,10 +929,20 @@ extension DomHTMLScriptElementExtension on DomHTMLScriptElement { external set _src(JSAny value); set src(Object /* String|TrustedScriptURL */ value) => _src = value.toJSAnyShallow; + + @JS('nonce') + external set _nonce(JSString? value); + set nonce(String? value) => _nonce = value?.toJS; } -DomHTMLScriptElement createDomHTMLScriptElement() => - domDocument.createElement('script') as DomHTMLScriptElement; +DomHTMLScriptElement createDomHTMLScriptElement(String? nonce) { + final DomHTMLScriptElement script = + domDocument.createElement('script') as DomHTMLScriptElement; + if (nonce != null) { + script.nonce = nonce; + } + return script; +} @JS() @staticInterop @@ -971,11 +981,25 @@ extension DomHTMLStyleElementExtension on DomHTMLStyleElement { external set _type(JSString? value); set type(String? value) => _type = value?.toJS; + @JS('nonce') + external set _nonce(JSString? value); + set nonce(String? value) => _nonce = value?.toJS; + + @JS('nonce') + external JSString? get _nonce; + String? get nonce => _nonce?.toDart; + external DomStyleSheet? get sheet; } -DomHTMLStyleElement createDomHTMLStyleElement() => - domDocument.createElement('style') as DomHTMLStyleElement; +DomHTMLStyleElement createDomHTMLStyleElement(String? nonce) { + final DomHTMLStyleElement style = + domDocument.createElement('style') as DomHTMLStyleElement; + if (nonce != null) { + style.nonce = nonce; + } + return style; +} @JS() @staticInterop diff --git a/lib/web_ui/lib/src/engine/embedder.dart b/lib/web_ui/lib/src/engine/embedder.dart index e949fdd0d2efd..623d6aa9bdd1c 100644 --- a/lib/web_ui/lib/src/engine/embedder.dart +++ b/lib/web_ui/lib/src/engine/embedder.dart @@ -188,7 +188,7 @@ class FlutterViewEmbedder { }); _glassPaneShadow = shadowRoot; - final DomHTMLStyleElement shadowRootStyleElement = createDomHTMLStyleElement(); + final DomHTMLStyleElement shadowRootStyleElement = createDomHTMLStyleElement(configuration.nonce); shadowRootStyleElement.id = 'flt-internals-stylesheet'; // The shadowRootStyleElement must be appended to the DOM, or its `sheet` will be null later. shadowRoot.appendChild(shadowRootStyleElement); @@ -198,7 +198,7 @@ class FlutterViewEmbedder { ); _textEditingHostNode = - createTextEditingHostNode(flutterViewElement, defaultCssFont); + createTextEditingHostNode(flutterViewElement, defaultCssFont, configuration.nonce); // Don't allow the scene to receive pointer events. _sceneHostElement = domDocument.createElement('flt-scene-host') @@ -434,10 +434,10 @@ FlutterViewEmbedder ensureFlutterViewEmbedderInitialized() => /// Creates a node to host text editing elements and applies a stylesheet /// to Flutter nodes that exist outside of the shadowDOM. -DomElement createTextEditingHostNode(DomElement root, String defaultFont) { +DomElement createTextEditingHostNode(DomElement root, String defaultFont, String? nonce) { final DomElement domElement = domDocument.createElement('flt-text-editing-host'); - final DomHTMLStyleElement styleElement = createDomHTMLStyleElement(); + final DomHTMLStyleElement styleElement = createDomHTMLStyleElement(nonce); styleElement.id = 'flt-text-editing-stylesheet'; root.appendChild(styleElement); diff --git a/lib/web_ui/test/engine/global_styles_test.dart b/lib/web_ui/test/engine/global_styles_test.dart index bfdcc31b1811a..ec801bd94839d 100644 --- a/lib/web_ui/test/engine/global_styles_test.dart +++ b/lib/web_ui/test/engine/global_styles_test.dart @@ -16,17 +16,25 @@ void testMain() { late DomHTMLStyleElement styleElement; setUp(() { - styleElement = createDomHTMLStyleElement(); + styleElement = createDomHTMLStyleElement(null); domDocument.body!.append(styleElement); applyGlobalCssRulesToSheet( styleElement, defaultCssFont: _kDefaultCssFont, ); }); + tearDown(() { styleElement.remove(); }); + test('createDomHTMLStyleElement sets a nonce value, when passed', () { + expect(styleElement.nonce, isEmpty); + + final DomHTMLStyleElement style = createDomHTMLStyleElement('a-nonce-value'); + expect(style.nonce, 'a-nonce-value'); + }); + test('(Self-test) hasCssRule can extract rules', () { final bool hasRule = hasCssRule(styleElement, selector: '.flt-text-editing::placeholder', declaration: 'opacity: 0');