From f377fc827eb154a74e6ec84c6bc877e4628d8fe1 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen <1961493+harryterkelsen@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:06:11 -0800 Subject: [PATCH] [canvaskit] Disable createImageBitmap support on Chrome 110 or older on Windows. (flutter/engine#48475) On Chrome 110 or older, there is a bug where an ImageBitmap will be read back upside down if it is stored upside down on the GPU. This only happens on Windows. So if we are on Windows and running on Chrome 110 or older, then fall back to `drawImage` based rendering. Fixes https://github.com/flutter/flutter/issues/138827 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- .../lib/src/engine/browser_detection.dart | 41 +++++++++++++++++-- .../lib/web_ui/lib/src/engine/dom.dart | 6 ++- .../no_create_image_bitmap_test.dart | 15 ++++++- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/browser_detection.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/browser_detection.dart index b167480f80..f771c9ea64 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/browser_detection.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/browser_detection.dart @@ -81,7 +81,8 @@ BrowserEngine detectBrowserEngineByVendorAgent(String vendor, String agent) { } // Assume Blink otherwise, but issue a warning. - print('WARNING: failed to detect current browser engine. Assuming this is a Chromium-compatible browser.'); + print( + 'WARNING: failed to detect current browser engine. Assuming this is a Chromium-compatible browser.'); return BrowserEngine.blink; } @@ -141,8 +142,9 @@ OperatingSystem detectOperatingSystem({ if (platform.startsWith('Mac')) { // iDevices requesting a "desktop site" spoof their UA so it looks like a Mac. // This checks if we're in a touch device, or on a real mac. - final int maxTouchPoints = - overrideMaxTouchPoints ?? domWindow.navigator.maxTouchPoints?.toInt() ?? 0; + final int maxTouchPoints = overrideMaxTouchPoints ?? + domWindow.navigator.maxTouchPoints?.toInt() ?? + 0; if (maxTouchPoints > 2) { return OperatingSystem.iOs; } @@ -204,6 +206,36 @@ bool get isIOS15 { domWindow.navigator.userAgent.contains('OS 15_'); } +/// Detect if running on Chrome version 110 or older on Windows. +/// +/// These versions of Chrome have a bug on Windows which causes +/// rendering to be flipped upside down. +// TODO(harryterkelsen): Remove this check once we stop supporting Chrome 110 +// and earlier, https://github.com/flutter/flutter/issues/139186. +bool get isChrome110OrOlderOnWindows { + if (debugIsChrome110OrOlderOnWindows != null) { + return debugIsChrome110OrOlderOnWindows!; + } + if (_cachedIsChrome110OrOlderOnWindows != null) { + return _cachedIsChrome110OrOlderOnWindows!; + } + if (operatingSystem != OperatingSystem.windows) { + return _cachedIsChrome110OrOlderOnWindows = false; + } + final RegExp chromeRegexp = RegExp(r'Chrom(e|ium)\/([0-9]+)\.'); + final RegExpMatch? match = + chromeRegexp.firstMatch(domWindow.navigator.userAgent); + if (match != null) { + final int chromeVersion = int.parse(match.group(2)!); + return _cachedIsChrome110OrOlderOnWindows = chromeVersion <= 110; + } + return _cachedIsChrome110OrOlderOnWindows = false; +} + +// Cache the result of checking if the app is running on Chrome 110 on Windows +// since we check this on every frame. +bool? _cachedIsChrome110OrOlderOnWindows; + /// If set to true pretends that the current browser is iOS Safari. /// /// Useful for tests. Do not use in production code. @@ -234,6 +266,9 @@ bool get isWasm => const bool.fromEnvironment('dart.library.ffi'); /// Use in tests to simulate the detection of iOS 15. bool? debugIsIOS15; +/// Use in tests to simulated the detection of Chrome 110 or older on Windows. +bool? debugIsChrome110OrOlderOnWindows; + int? _cachedWebGLVersion; /// The highest WebGL version supported by the current browser, or -1 if WebGL diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart index 284cd72968..47c464a8de 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart @@ -3672,8 +3672,10 @@ external JSAny? get _createImageBitmapFunction; /// Set to `true` to disable `createImageBitmap` support. Used in tests. bool debugDisableCreateImageBitmapSupport = false; -bool browserSupportsCreateImageBitmap = - !debugDisableCreateImageBitmapSupport || _createImageBitmapFunction != null; +bool get browserSupportsCreateImageBitmap => + _createImageBitmapFunction != null && + !isChrome110OrOlderOnWindows && + !debugDisableCreateImageBitmapSupport; @JS() @staticInterop diff --git a/engine/src/flutter/lib/web_ui/test/canvaskit/no_create_image_bitmap_test.dart b/engine/src/flutter/lib/web_ui/test/canvaskit/no_create_image_bitmap_test.dart index 4ee6b09aa3..d23270e1e8 100644 --- a/engine/src/flutter/lib/web_ui/test/canvaskit/no_create_image_bitmap_test.dart +++ b/engine/src/flutter/lib/web_ui/test/canvaskit/no_create_image_bitmap_test.dart @@ -20,14 +20,18 @@ void testMain() { setUpCanvasKitTest(); setUp(() async { EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(1.0); - debugDisableCreateImageBitmapSupport = true; }); tearDown(() { debugDisableCreateImageBitmapSupport = false; + debugIsChrome110OrOlderOnWindows = null; }); test('can render without createImageBitmap', () async { + debugDisableCreateImageBitmapSupport = true; + + expect(browserSupportsCreateImageBitmap, isFalse); + final CkPictureRecorder recorder = CkPictureRecorder(); final CkCanvas canvas = recorder.beginRecording(region); @@ -61,5 +65,14 @@ void testMain() { region: region, ); }); + + test( + 'createImageBitmap support is disabled on ' + 'Windows on Chrome version 110 or older', () async { + debugIsChrome110OrOlderOnWindows = true; + debugDisableCreateImageBitmapSupport = false; + + expect(browserSupportsCreateImageBitmap, isFalse); + }); }); }