diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md index a8de9d0147e..8476953537f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 0.5.5 -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Migrates to `dart:js_interop` and `package:web` APIs. +* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3. ## 0.5.4+3 diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart index 51c36a55a47..5c3ccbdc8c7 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart @@ -3,13 +3,14 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; +// ignore: implementation_imports +import 'package:google_maps_flutter_web/src/utils.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -230,7 +231,7 @@ void main() { polygons = MockPolygonsController(); polylines = MockPolylinesController(); tileOverlays = MockTileOverlaysController(); - map = gmaps.GMap(html.DivElement()); + map = gmaps.GMap(createDivElement()); }); testWidgets('listens to map events', (WidgetTester tester) async { @@ -471,7 +472,7 @@ void main() { setUp(() { map = gmaps.GMap( - html.DivElement(), + createDivElement(), gmaps.MapOptions() ..zoom = 10 ..center = gmaps.LatLng(0, 0), diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart index 2e2d77b71de..0b284cac766 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart @@ -3,11 +3,12 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; +// ignore: implementation_imports +import 'package:google_maps_flutter_web/src/utils.dart'; import 'package:integration_test/integration_test.dart'; /// Test Markers @@ -123,7 +124,7 @@ void main() { testWidgets('showInfoWindow', (WidgetTester tester) async { final gmaps.InfoWindow infoWindow = gmaps.InfoWindow(); - final gmaps.GMap map = gmaps.GMap(html.DivElement()); + final gmaps.GMap map = gmaps.GMap(createDivElement()); marker.set('map', map); final MarkerController controller = MarkerController( marker: marker, @@ -138,7 +139,7 @@ void main() { testWidgets('hideInfoWindow', (WidgetTester tester) async { final gmaps.InfoWindow infoWindow = gmaps.InfoWindow(); - final gmaps.GMap map = gmaps.GMap(html.DivElement()); + final gmaps.GMap map = gmaps.GMap(createDivElement()); marker.set('map', map); final MarkerController controller = MarkerController( marker: marker, @@ -156,7 +157,7 @@ void main() { setUp(() { final gmaps.InfoWindow infoWindow = gmaps.InfoWindow(); - final gmaps.GMap map = gmaps.GMap(html.DivElement()); + final gmaps.GMap map = gmaps.GMap(createDivElement()); marker.set('map', map); controller = MarkerController(marker: marker, infoWindow: infoWindow); }); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart index 6a264064a3c..d965435b3a5 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart @@ -4,7 +4,6 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:html' as html; import 'dart:typed_data'; import 'dart:ui'; @@ -12,8 +11,11 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; +// ignore: implementation_imports +import 'package:google_maps_flutter_web/src/utils.dart'; import 'package:http/http.dart' as http; import 'package:integration_test/integration_test.dart'; +import 'package:web/web.dart'; import 'resources/icon_image_base64.dart'; @@ -28,7 +30,7 @@ void main() { setUp(() { events = StreamController>(); controller = MarkersController(stream: events); - map = gmaps.GMap(html.DivElement()); + map = gmaps.GMap(createDivElement()); controller.bindToMap(123, map); }); @@ -274,11 +276,11 @@ void main() { controller.addMarkers(markers); expect(controller.markers.length, 1); - final html.HtmlElement? content = controller.markers[const MarkerId('1')] - ?.infoWindow?.content as html.HtmlElement?; - expect(content?.innerHtml, contains('title for test')); + final HTMLElement? content = controller + .markers[const MarkerId('1')]?.infoWindow?.content as HTMLElement?; + expect(content?.innerHTML, contains('title for test')); expect( - content?.innerHtml, + content?.innerHTML, contains( 'Go to Google >>>', )); @@ -299,8 +301,8 @@ void main() { controller.addMarkers(markers); expect(controller.markers.length, 1); - final html.HtmlElement? content = controller.markers[const MarkerId('1')] - ?.infoWindow?.content as html.HtmlElement?; + final HTMLElement? content = controller + .markers[const MarkerId('1')]?.infoWindow?.content as HTMLElement?; content?.click(); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlay_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlay_test.dart index 29f902f7f1e..0724da21127 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlay_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlay_test.dart @@ -3,13 +3,14 @@ // found in the LICENSE file. import 'dart:convert'; -import 'dart:html' as html; +import 'dart:js_interop'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:web/web.dart'; import 'resources/tile16_base64.dart'; @@ -43,8 +44,10 @@ void main() { final gmaps.Size size = controller.gmMapType.tileSize!; expect(size.width, TileOverlayController.logicalTileSize); expect(size.height, TileOverlayController.logicalTileSize); - expect(controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document), - null); + expect( + controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, document), + null, + ); }); testWidgets('produces image tiles', (WidgetTester tester) async { @@ -55,16 +58,16 @@ void main() { ), ); - final html.ImageElement img = - controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document)! - as html.ImageElement; + final HTMLImageElement img = + controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, document)! + as HTMLImageElement; expect(img.naturalWidth, 0); expect(img.naturalHeight, 0); expect(img.hidden, true); - // Wait until the image is fully loaded and decoded before re-reading its attributes. await img.onLoad.first; - await img.decode(); + + await img.decode().toDart; expect(img.hidden, false); expect(img.naturalWidth, 16); @@ -79,9 +82,9 @@ void main() { ), ); { - final html.ImageElement img = - controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document)! - as html.ImageElement; + final HTMLImageElement img = + controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, document)! + as HTMLImageElement; await null; // let `getTile` `then` complete expect( img.src, @@ -95,10 +98,12 @@ void main() { tileProvider: TestTileProvider(), )); { - final html.ImageElement img = - controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document)! - as html.ImageElement; + final HTMLImageElement img = + controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, document)! + as HTMLImageElement; + await img.onLoad.first; + expect( img.src, isNotEmpty, @@ -109,7 +114,7 @@ void main() { controller.update(const TileOverlay(tileOverlayId: id)); { expect( - controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document), + controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, document), null, reason: 'Setting a null tileProvider should work.', ); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart index 8b6b34694f4..c680c97a993 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -11,9 +10,12 @@ import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' hide GoogleMapController; +// ignore: implementation_imports +import 'package:google_maps_flutter_web/src/utils.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:web/web.dart'; @GenerateNiceMocks(>[MockSpec()]) import 'overlays_test.mocks.dart'; @@ -38,13 +40,13 @@ void main() { /// 0. void probeTiles() { for (final gmaps.MapType? mapType in map.overlayMapTypes!.array!) { - mapType?.getTile!(gmaps.Point(0, 0), 0, html.document); + mapType?.getTile!(gmaps.Point(0, 0), 0, document); } } setUp(() { controller = TileOverlaysController(); - map = gmaps.GMap(html.DivElement()); + map = gmaps.GMap(createDivElement()); controller.googleMap = map; tileProviders = [ diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart index b9bc2d371c9..ad3d3d4a8a4 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; @@ -11,6 +10,8 @@ import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps/google_maps_geometry.dart' as geometry; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; +// ignore: implementation_imports +import 'package:google_maps_flutter_web/src/utils.dart'; import 'package:integration_test/integration_test.dart'; // This value is used when comparing the results of @@ -25,7 +26,7 @@ void main() { late gmaps.GMap map; setUp(() { - map = gmaps.GMap(html.DivElement()); + map = gmaps.GMap(createDivElement()); }); group('CirclesController', () { diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/lib/main.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/lib/main.dart index 6bd536109f7..c32edb4b8ba 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/lib/main.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/lib/main.dart @@ -20,6 +20,9 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { @override Widget build(BuildContext context) { - return const Text('Testing... Look at the console output for results!'); + return const Directionality( + textDirection: TextDirection.ltr, + child: Text('Testing... Look at the console output for results!'), + ); } } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml index 7b2c31b6f53..1a02cfa2a65 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml @@ -3,8 +3,8 @@ publish_to: none # Tests require flutter beta or greater to run. environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: flutter: @@ -12,12 +12,13 @@ dependencies: google_maps_flutter_platform_interface: ^2.4.0 google_maps_flutter_web: path: ../ + web: ^0.5.0 dev_dependencies: build_runner: ^2.1.1 flutter_test: sdk: flutter - google_maps: ^6.1.0 + google_maps: ^7.1.0 google_maps_flutter: ^2.2.0 # Needed for projection_test.dart http: ">=0.13.0 <2.0.0" integration_test: diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart index d1ef18d3067..fe44d41aa48 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart @@ -6,8 +6,7 @@ library google_maps_flutter_web; import 'dart:async'; import 'dart:convert'; -import 'dart:html' hide VoidCallback; -import 'dart:js_util'; +import 'dart:js_interop'; import 'dart:ui_web' as ui_web; import 'package:collection/collection.dart'; @@ -20,10 +19,14 @@ import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:sanitize_html/sanitize_html.dart'; import 'package:stream_transform/stream_transform.dart'; +import 'package:web/web.dart'; +import 'src/dom_window_extension.dart'; import 'src/google_maps_inspector_web.dart'; +import 'src/map_styler.dart'; import 'src/third_party/to_screen_location/to_screen_location.dart'; import 'src/types.dart'; +import 'src/utils.dart'; part 'src/circle.dart'; part 'src/circles.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart index 8ca6a75559f..d8d77a7728a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart @@ -28,7 +28,7 @@ double _getCssOpacity(Color color) { // mapToolbarEnabled is unused in web, there's no "map toolbar" // myLocationButtonEnabled Widget not available in web yet, it needs to be built on top of the maps widget // See: https://developers.google.com/maps/documentation/javascript/examples/control-custom -// myLocationEnabled needs to be built through dart:html navigator.geolocation +// myLocationEnabled needs to be built through `navigator.geolocation` from package:web. // See: https://api.dart.dev/stable/2.8.4/dart-html/Geolocation-class.html // trafficEnabled is handled when creating the GMap object, since it needs to be added as a layer. // trackCameraPosition is just a boolean value that indicates if the map has an onCameraMove handler. @@ -139,10 +139,11 @@ List _mapStyles(String? mapStyleJson) { styles = (json.decode(mapStyleJson, reviver: (Object? key, Object? value) { if (value is Map && _isJsonMapStyle(value as Map)) { - List stylers = []; + List stylers = []; if (value['stylers'] != null) { stylers = (value['stylers']! as List) - .map((Object? e) => e != null ? jsify(e) : null) + .whereType>() + .map(MapStyler.fromJson) .toList(); } return gmaps.MapTypeStyle() @@ -203,30 +204,44 @@ gmaps.InfoWindowOptions? _infoWindowOptionsFromMarker(Marker marker) { // Add an outer wrapper to the contents of the infowindow, we need it to listen // to click events... - final HtmlElement container = DivElement() + final HTMLElement container = createDivElement() ..id = 'gmaps-marker-${marker.markerId.value}-infowindow'; if (markerTitle.isNotEmpty) { - final HtmlElement title = HeadingElement.h3() - ..className = 'infowindow-title' - ..innerText = markerTitle; - container.children.add(title); + final HTMLHeadingElement title = + (document.createElement('h3') as HTMLHeadingElement) + ..className = 'infowindow-title' + ..innerText = markerTitle; + container.appendChild(title); } if (markerSnippet.isNotEmpty) { - final HtmlElement snippet = DivElement() - ..className = 'infowindow-snippet' + final HTMLElement snippet = createDivElement() + ..className = 'infowindow-snippet'; + + // Firefox and Safari don't support Trusted Types yet. + // See https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicyFactory#browser_compatibility + if (window.nullableTrustedTypes != null) { + final GoogleMapsTrustedTypePolicy trustedTypePolicy = + window.nullableTrustedTypes!.getGoogleMapsTrustedTypesPolicy( + GoogleMapsTrustedTypePolicyOptions( + createHTML: (String html, JSAny? arguments) { + return sanitizeHtml(html); + }.toJS, + ), + ); + + snippet.trustedInnerHTML = + trustedTypePolicy.createHTML(markerSnippet, null); + } else { // `sanitizeHtml` is used to clean the (potential) user input from (potential) // XSS attacks through the contents of the marker InfoWindow. // See: https://pub.dev/documentation/sanitize_html/latest/sanitize_html/sanitizeHtml.html // See: b/159137885, b/159598165 - // The NodeTreeSanitizer.trusted just tells setInnerHtml to leave the output - // of `sanitizeHtml` untouched. // ignore: unsafe_html - ..setInnerHtml( - sanitizeHtml(markerSnippet), - treeSanitizer: NodeTreeSanitizer.trusted, - ); - container.children.add(snippet); + snippet.innerHTML = sanitizeHtml(markerSnippet); + } + + container.appendChild(snippet); } return gmaps.InfoWindowOptions() @@ -274,8 +289,18 @@ gmaps.Icon? _gmIconFromBitmapDescriptor(BitmapDescriptor bitmapDescriptor) { // Grab the bytes, and put them into a blob final List bytes = iconConfig[1]! as List; // Create a Blob from bytes, but let the browser figure out the encoding - final Blob blob = Blob([bytes]); - icon = gmaps.Icon()..url = Url.createObjectUrlFromBlob(blob); + final Blob blob; + + assert( + bytes is Uint8List, + 'The bytes for a BitmapDescriptor icon must be a Uint8List', + ); + + // TODO(ditman): Improve this conversion + // See https://github.com/dart-lang/web/issues/180 + blob = Blob([(bytes as Uint8List).toJS].toJS); + + icon = gmaps.Icon()..url = URL.createObjectURL(blob as JSObject); final gmaps.Size? size = _gmSizeFromIconConfig(iconConfig, 2); if (size != null) { diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/dom_window_extension.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/dom_window_extension.dart new file mode 100644 index 00000000000..f1b9ba34393 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/dom_window_extension.dart @@ -0,0 +1,89 @@ +// 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 'dart:js_interop'; + +import 'package:web/web.dart' as web; + +/// This extension gives [web.Window] a nullable getter to the `trustedTypes` +/// property, which is used to check for feature support. +extension NullableTrustedTypesGetter on web.Window { + /// (Nullable) Bindings to window.trustedTypes. + /// + /// This may be null if the browser doesn't support the Trusted Types API. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API + @JS('trustedTypes') + external GoogleMapsTrustedTypePolicyFactory? get nullableTrustedTypes; +} + +// TODO(ditman): remove this extension type when we depend on package:web 0.5.1 +/// This extension exists as a stop gap until `package:web 0.5.1` is released. +/// That version provides the `TrustedTypes` API. +@JS('TrustedTypePolicyFactory') +extension type GoogleMapsTrustedTypePolicyFactory._(JSObject _) + implements JSObject { + /// The `TrustedTypePolicy` for Google Maps Flutter. + static GoogleMapsTrustedTypePolicy? _policy; + + @JS('createPolicy') + external GoogleMapsTrustedTypePolicy _createPolicy( + String policyName, [ + GoogleMapsTrustedTypePolicyOptions policyOptions, + ]); + + /// Get a new [GoogleMapsTrustedTypePolicy]. + /// + /// If a policy already exists, it will be returned. + /// Otherwise, a new policy is created. + /// + /// Because of we only cache one _policy, this method + /// specifically hardcoded to the GoogleMaps use case. + GoogleMapsTrustedTypePolicy getGoogleMapsTrustedTypesPolicy( + GoogleMapsTrustedTypePolicyOptions policyOptions, + ) { + const String policyName = 'google_maps_flutter_sanitize'; + _policy ??= _createPolicy(policyName, policyOptions); + + return _policy!; + } +} + +// TODO(ditman): remove this extension type when we depend on package:web 0.5.1 +/// This extension exists as a stop gap until `package:web 0.5.1` is released. +/// That version provides the `TrustedTypes` API. +@JS('TrustedTypePolicy') +extension type GoogleMapsTrustedTypePolicy._(JSObject _) implements JSObject { + /// Create a new `TrustedHTML` instance with the given [input] and [arguments]. + external GoogleMapsTrustedHTML createHTML( + String input, + JSAny? arguments, + ); +} + +// TODO(ditman): remove this extension type when we depend on package:web 0.5.1 +/// This extension exists as a stop gap until `package:web 0.5.1` is released. +/// That version provides the `TrustedTypes` API. +@JS('TrustedTypePolicyOptions') +extension type GoogleMapsTrustedTypePolicyOptions._(JSObject _) + implements JSObject { + /// Create a new `TrustedTypePolicyOptions` instance. + external factory GoogleMapsTrustedTypePolicyOptions({ + JSFunction createHTML, + }); +} + +// TODO(ditman): remove this extension type when we depend on package:web 0.5.1 +/// This extension exists as a stop gap until `package:web 0.5.1` is released. +/// That version provides the `TrustedTypes` API. +@JS('TrustedHTML') +extension type GoogleMapsTrustedHTML._(JSObject _) implements JSObject {} + +/// This extension provides a setter for the [web.HTMLElement] `innerHTML` property, +/// that accepts trusted HTML only. +extension TrustedInnerHTML on web.HTMLElement { + /// Set the inner HTML of this element to the given [trustedHTML]. + @JS('innerHTML') + external set trustedInnerHTML(GoogleMapsTrustedHTML trustedHTML); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart index 494029ce02c..c577ba30021 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart @@ -7,7 +7,7 @@ part of '../google_maps_flutter_web.dart'; /// Type used when passing an override to the _createMap function. @visibleForTesting typedef DebugCreateMapFunction = gmaps.GMap Function( - HtmlElement div, gmaps.MapOptions options); + HTMLElement div, gmaps.MapOptions options); /// Encapsulates a [gmaps.GMap], its events, and where in the DOM it's rendered. class GoogleMapController { @@ -36,7 +36,7 @@ class GoogleMapController { // Register the view factory that will hold the `_div` that holds the map in the DOM. // The `_div` needs to be created outside of the ViewFactory (and cached!) so we can // use it to create the [gmaps.GMap] in the `init()` method of this class. - _div = DivElement() + _div = createDivElement() ..id = _getViewType(mapId) ..style.width = '100%' ..style.height = '100%'; @@ -74,7 +74,7 @@ class GoogleMapController { // The Flutter widget that contains the rendered Map. HtmlElementView? _widget; - late HtmlElement _div; + late HTMLElement _div; /// The Flutter widget that will contain the rendered Map. Used for caching. Widget? get widget { @@ -138,7 +138,7 @@ class GoogleMapController { DebugCreateMapFunction? _overrideCreateMap; - gmaps.GMap _createMap(HtmlElement div, gmaps.MapOptions options) { + gmaps.GMap _createMap(HTMLElement div, gmaps.MapOptions options) { if (_overrideCreateMap != null) { return _overrideCreateMap!(div, options); } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/map_styler.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/map_styler.dart new file mode 100644 index 00000000000..e6a871599cb --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/map_styler.dart @@ -0,0 +1,40 @@ +// 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 'dart:js_interop'; + +/// The interop type for a Google Maps Map Styler. +/// +/// See: https://developers.google.com/maps/documentation/javascript/style-reference#stylers +@JS() +@staticInterop +@anonymous +extension type MapStyler._(JSObject _) implements JSObject { + /// Create a new [MapStyler] instance. + external factory MapStyler({ + String? hue, + num? lightness, + num? saturation, + num? gamma, + // ignore: non_constant_identifier_names + bool? invert_lightness, + String? visibility, + String? color, + int? weight, + }); + + /// Create a new [MapStyler] instance from the given [json]. + factory MapStyler.fromJson(Map json) { + return MapStyler( + hue: json['hue'] as String?, + lightness: json['lightness'] as num?, + saturation: json['saturation'] as num?, + gamma: json['gamma'] as num?, + invert_lightness: json['invert_lightness'] as bool?, + visibility: json['visibility'] as String?, + color: json['color'] as String?, + weight: json['weight'] as int?, + ); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart index 77258504ef6..c1b0772a139 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart @@ -69,12 +69,12 @@ class MarkerController { /// This cannot be called after [remove]. void update( gmaps.MarkerOptions options, { - HtmlElement? newInfoWindowContent, + HTMLElement? newInfoWindowContent, }) { assert(_marker != null, 'Cannot `update` Marker after calling `remove`.'); _marker!.options = options; if (_infoWindow != null && newInfoWindowContent != null) { - _infoWindow!.content = newInfoWindowContent; + _infoWindow.content = newInfoWindowContent; } } @@ -94,7 +94,7 @@ class MarkerController { void hideInfoWindow() { assert(_marker != null, 'Cannot `hideInfoWindow` on a `remove`d Marker.'); if (_infoWindow != null) { - _infoWindow!.close(); + _infoWindow.close(); _infoWindowShown = false; } } @@ -105,7 +105,7 @@ class MarkerController { void showInfoWindow() { assert(_marker != null, 'Cannot `showInfoWindow` on a `remove`d Marker.'); if (_infoWindow != null) { - _infoWindow!.open(_marker!.map, _marker); + _infoWindow.open(_marker!.map, _marker); _infoWindowShown = true; } } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart index 52a659874f4..26cb94f9ad2 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart @@ -39,11 +39,12 @@ class MarkersController extends GeometryController { // Google Maps' JS SDK does not have a click event on the InfoWindow, so // we make one... if (infoWindowOptions.content != null && - infoWindowOptions.content is HtmlElement) { - final HtmlElement content = infoWindowOptions.content! as HtmlElement; - content.onClick.listen((_) { + infoWindowOptions.content is HTMLElement) { + final HTMLElement content = infoWindowOptions.content! as HTMLElement; + + content.onclick = (JSAny? _) { _onInfoWindowTap(marker.markerId); - }); + }.toJS; } } @@ -91,7 +92,7 @@ class MarkersController extends GeometryController { _infoWindowOptionsFromMarker(marker); markerController.update( markerOptions, - newInfoWindowContent: infoWindow?.content as HtmlElement?, + newInfoWindowContent: infoWindow?.content as HTMLElement?, ); } } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlay.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlay.dart index 5dd092c67ee..29823d00b9f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlay.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlay.dart @@ -33,7 +33,7 @@ class TileOverlayController { } /// Renders a Tile for gmaps; delegating to the configured [TileProvider]. - HtmlElement? _getTile( + HTMLElement? _getTile( gmaps.Point? tileCoord, num? zoom, Document? ownerDocument, @@ -42,10 +42,10 @@ class TileOverlayController { return null; } - final ImageElement img = - ownerDocument!.createElement('img') as ImageElement; + final HTMLImageElement img = + ownerDocument!.createElement('img') as HTMLImageElement; img.width = img.height = logicalTileSize; - img.hidden = true; + img.hidden = true.toJS; img.setAttribute('decoding', 'async'); _tileOverlay.tileProvider! @@ -55,12 +55,14 @@ class TileOverlayController { return; } // Using img lets us take advantage of native decoding. - final String src = Url.createObjectUrl(Blob([tile.data])); + final String src = URL.createObjectURL( + Blob([tile.data!.toJS].toJS) as JSObject, + ); img.src = src; - img.addEventListener('load', (_) { - img.hidden = false; - Url.revokeObjectUrl(src); - }); + img.onload = (JSAny? _) { + img.hidden = false.toJS; + URL.revokeObjectURL(src); + }.toJS; }); return img; diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/utils.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/utils.dart new file mode 100644 index 00000000000..94bc0b6b90d --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/utils.dart @@ -0,0 +1,10 @@ +// 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:web/web.dart'; + +/// Convenience method to create a new [HTMLDivElement] element. +HTMLDivElement createDivElement() { + return document.createElement('div') as HTMLDivElement; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml index 2293fe78c14..f78289d04fc 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml @@ -2,11 +2,11 @@ name: google_maps_flutter_web description: Web platform implementation of google_maps_flutter repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 0.5.4+3 +version: 0.5.5 environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" flutter: plugin: @@ -22,10 +22,11 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - google_maps: ^6.1.0 + google_maps: ^7.1.0 google_maps_flutter_platform_interface: ^2.4.0 sanitize_html: ^2.0.0 stream_transform: ^2.0.0 + web: ^0.5.0 dev_dependencies: flutter_test: