diff --git a/AUTHORS b/AUTHORS index 1211ffab5ac3a..e0e7902bbba3f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -23,4 +23,5 @@ Koutaro Mori TheOneWithTheBraid Twin Sun, LLC Qixing Cao -LinXunFeng \ No newline at end of file +LinXunFeng +Tim Maffett diff --git a/lib/web_ui/README.md b/lib/web_ui/README.md index 4eac836446d40..24bc7e42b61e2 100644 --- a/lib/web_ui/README.md +++ b/lib/web_ui/README.md @@ -27,7 +27,7 @@ The `build` subcommand builds web engine gn/ninja targets. Targets can be individually specified in the command line invocation, or if none are specified, all web engine targets are built. Common targets are as follows: * `sdk` - The flutter_web_sdk itself. - * `canvaskit` - Flutter's version of canvakit. + * `canvaskit` - Flutter's version of canvaskit. * `canvaskit_chromium` - A version of canvaskit optimized for use with chromium-based browsers. * `skwasm` - Builds experimental skia wasm module renderer. diff --git a/lib/web_ui/lib/src/engine/mouse_cursor.dart b/lib/web_ui/lib/src/engine/mouse_cursor.dart index 0ccfbed03b129..cedabffbf6cf6 100644 --- a/lib/web_ui/lib/src/engine/mouse_cursor.dart +++ b/lib/web_ui/lib/src/engine/mouse_cursor.dart @@ -62,7 +62,19 @@ class MouseCursor { 'zoomOut': 'zoom-out', }; static String _mapKindToCssValue(String? kind) { - return _kindToCssValueMap[kind] ?? 'default'; + // Allow 'image-set(...)'/'-webkit-image-set(...)' css commands thru for setting DevicePixelRatio + // aware images for cursors (for newer browsers) and allow 'url(...)' strings thru as fallback + // for older browsers (bare url() commands are always 1.0x dpr). The hotspot coordinates are always + // supplied in 1.0x dpr coordinates. All of these css methods can use data-uri versions of url's + // which allow for inline definition of image data to define the cursor. + if (kind != null && + (kind.startsWith('url') || + kind.startsWith('image-set') || + kind.startsWith('-webkit-image-set'))) { + return kind; + } else { + return _kindToCssValueMap[kind] ?? 'default'; + } } void activateSystemCursor(String? kind) { diff --git a/lib/web_ui/test/engine/mouse_cursor_test.dart b/lib/web_ui/test/engine/mouse_cursor_test.dart new file mode 100644 index 0000000000000..75114e778659d --- /dev/null +++ b/lib/web_ui/test/engine/mouse_cursor_test.dart @@ -0,0 +1,124 @@ +// 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'; + +const String flutterLogoPngAsDataUri = 'data:image/png;base64,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'; + +// Following [_kindToCssValueMap] array definition is from flutter engine /lib/web_ui/lib/src/engine/mouse_cursor.dart +// +// Map from Flutter's kind values to CSS's cursor values. +// +// This map must be kept in sync with Flutter framework's +// rendering/mouse_cursor.dart. +const Map _kindToCssValueMap = { + 'alias': 'alias', + 'allScroll': 'all-scroll', + 'basic': 'default', + 'cell': 'cell', + 'click': 'pointer', + 'contextMenu': 'context-menu', + 'copy': 'copy', + 'forbidden': 'not-allowed', + 'grab': 'grab', + 'grabbing': 'grabbing', + 'help': 'help', + 'move': 'move', + 'none': 'none', + 'noDrop': 'no-drop', + 'precise': 'crosshair', + 'progress': 'progress', + 'text': 'text', + 'resizeColumn': 'col-resize', + 'resizeDown': 's-resize', + 'resizeDownLeft': 'sw-resize', + 'resizeDownRight': 'se-resize', + 'resizeLeft': 'w-resize', + 'resizeLeftRight': 'ew-resize', + 'resizeRight': 'e-resize', + 'resizeRow': 'row-resize', + 'resizeUp': 'n-resize', + 'resizeUpDown': 'ns-resize', + 'resizeUpLeft': 'nw-resize', + 'resizeUpRight': 'ne-resize', + 'resizeUpLeftDownRight': 'nwse-resize', + 'resizeUpRightDownLeft': 'nesw-resize', + 'verticalText': 'vertical-text', + 'wait': 'wait', + 'zoomIn': 'zoom-in', + 'zoomOut': 'zoom-out', +}; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +void testMain() { + ensureFlutterViewEmbedderInitialized(); + final DomElement flutterViewElement = flutterViewEmbedder.flutterViewElement; + + setUp(() { + }); + + MouseCursor.initialize(); + final MouseCursor? mouseCursorTry = MouseCursor.instance; + + assert(mouseCursorTry!=null,'Error MouseCursor.instance is null'); + + final MouseCursor mouseCursorInstance = mouseCursorTry!; + + void setCursorKind(String kindToTest) { + mouseCursorInstance.activateSystemCursor( kindToTest ); + } + + String getSetCursorCSSStyle() { + final DomCSSStyleDeclaration style = domWindow.getComputedStyle(flutterViewElement); + return style.getPropertyValue('cursor'); + } + + // Make sure that all 'built in' MouseCursor kinds ([_kindToCssValueMap]) make it through to there css style mapping + test('MouseCursor.activateSystemCursor test built in system cursor to browser css map', () { + _kindToCssValueMap.forEach( (String key, String expectedResult) { + setCursorKind(key); + expect( getSetCursorCSSStyle(), equals(expectedResult) ); + } + ); + }); + + // We need to test that cursor key/kind values that start with + // 'url(..)', 'image-set(...)'/'-webkit-image-set(...)' css commands make it through. + test('MouseCursor.activateSystemCursor allows url/-webkit-image-set kind value prefix to pass', () { + const String urlCss = 'url("cursor.png")'; + const String webkitImageSetCss = '-webkit-image-set($urlCss 1x) 0 0, $urlCss, help'; + + setCursorKind('grabbing'); // reset to different cursor before testing 'url...' + + setCursorKind('$urlCss 4 12, default'); + expect( getSetCursorCSSStyle(), startsWith('url') ); + + setCursorKind('progress'); // reset to different cursor before testing '-webkit-image-set...' + + // we do test for '-webkit-image-set' or 'image-set' prefix, browsers may change '-webkit-image-set' to 'image-set'. + setCursorKind(webkitImageSetCss); + expect( getSetCursorCSSStyle(), anyOf(startsWith('-webkit-image-set'),startsWith('image-set')) ); + }); + + test('MouseCursor.activateSystemCursor allows url/-webkit-image-set (with datauri url) kind value prefix to pass', () { + const String urlCss = 'url("$flutterLogoPngAsDataUri")'; + const String webkitImageSetCss = '-webkit-image-set($urlCss 1x) 0 0, $urlCss, help'; + + setCursorKind('move'); // reset to different cursor before testing 'url...' + + setCursorKind('$urlCss 4 12, default'); + expect( getSetCursorCSSStyle(), startsWith('url') ); + + setCursorKind('progress'); // reset to different cursor before testing '-webkit-image-set...' + + // we do test for '-webkit-image-set' or 'image-set' prefix, browsers may change '-webkit-image-set' to 'image-set'. + setCursorKind(webkitImageSetCss); + expect( getSetCursorCSSStyle(), anyOf(startsWith('-webkit-image-set'),startsWith('image-set')) ); + }); +}