[url_launcher_web] Launch mailto urls in same window in Safari (#2740)

This uses package:platform_detect to determine whether the app is running in Safari or not.

https://pub.dev/packages/platform_detect

Co-authored-by: David Iglesias Teixeira <ditman@gmail.com>
diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md
index 0702685..df1b2c9 100644
--- a/packages/url_launcher/url_launcher_web/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md
@@ -1,10 +1,15 @@
+# 0.1.1+6
+
+- Open "mailto" urls with target set as "\_top" on Safari browsers.
+- Update lower bound of dart dependency to 2.2.0.
+
 # 0.1.1+5
 
-* Update lower bound of dart dependency to 2.1.0.
+- Update lower bound of dart dependency to 2.1.0.
 
 # 0.1.1+4
 
-* Declare API stability and compatibility with `1.0.0` (more details at: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0).
+- Declare API stability and compatibility with `1.0.0` (more details at: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0).
 
 # 0.1.1+3
 
@@ -12,7 +17,7 @@
 
 # 0.1.1+2
 
-- Open urls with target "_top" on iOS PWAs.
+- Open urls with target "\_top" on iOS PWAs.
 
 # 0.1.1+1
 
diff --git a/packages/url_launcher/url_launcher_web/lib/src/navigator.dart b/packages/url_launcher/url_launcher_web/lib/src/navigator.dart
deleted file mode 100644
index 4c7a99d..0000000
--- a/packages/url_launcher/url_launcher_web/lib/src/navigator.dart
+++ /dev/null
@@ -1,15 +0,0 @@
-@JS()
-library navigator;
-
-import 'package:js/js.dart';
-import 'package:meta/meta.dart';
-
-@JS('window.navigator.standalone')
-external bool get _standalone;
-
-/// The window.navigator.standalone DOM property.
-bool get standalone => _standalone ?? false;
-
-@visibleForTesting
-@JS('window.navigator.standalone')
-external set standalone(bool enabled);
diff --git a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart
index cce2bbf..e55ceb2 100644
--- a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart
+++ b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart
@@ -4,7 +4,10 @@
 import 'package:flutter_web_plugins/flutter_web_plugins.dart';
 import 'package:meta/meta.dart';
 import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
-import 'src/navigator.dart' as navigator;
+
+import 'package:platform_detect/platform_detect.dart' show browser;
+
+const _mailtoScheme = 'mailto';
 
 /// The web implementation of [UrlLauncherPlatform].
 ///
@@ -12,6 +15,9 @@
 class UrlLauncherPlugin extends UrlLauncherPlatform {
   html.Window _window;
 
+  // The set of schemes that can be handled by the plugin
+  static final _supportedSchemes = {'http', 'https', _mailtoScheme};
+
   /// A constructor that allows tests to override the window object used by the plugin.
   UrlLauncherPlugin({@visibleForTesting html.Window window})
       : _window = window ?? html.window;
@@ -21,25 +27,24 @@
     UrlLauncherPlatform.instance = UrlLauncherPlugin();
   }
 
+  String _getUrlScheme(String url) => Uri.tryParse(url)?.scheme;
+
+  bool _isMailtoScheme(String url) => _getUrlScheme(url) == _mailtoScheme;
+
   /// Opens the given [url] in a new window.
   ///
   /// Returns the newly created window.
   @visibleForTesting
   html.WindowBase openNewWindow(String url) {
-    // We need to open on _top in ios browsers in standalone mode.
+    // We need to open mailto urls on the _top window context on safari browsers.
     // See https://github.com/flutter/flutter/issues/51461 for reference.
-    final target = navigator.standalone ? '_top' : '';
+    final target = browser.isSafari && _isMailtoScheme(url) ? '_top' : '';
     return _window.open(url, target);
   }
 
   @override
   Future<bool> canLaunch(String url) {
-    final Uri parsedUrl = Uri.tryParse(url);
-    if (parsedUrl == null) return Future<bool>.value(false);
-
-    return Future<bool>.value(parsedUrl.isScheme('http') ||
-        parsedUrl.isScheme('https') ||
-        parsedUrl.isScheme('mailto'));
+    return Future<bool>.value(_supportedSchemes.contains(_getUrlScheme(url)));
   }
 
   @override
diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml
index 16bc4a5..207f2dc 100644
--- a/packages/url_launcher/url_launcher_web/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_web/pubspec.yaml
@@ -4,7 +4,7 @@
 # 0.1.y+z is compatible with 1.0.0, if you land a breaking change bump
 # the version to 2.0.0.
 # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0
-version: 0.1.1+5
+version: 0.1.1+6
 
 flutter:
   plugin:
@@ -15,12 +15,12 @@
 
 dependencies:
   url_launcher_platform_interface: ^1.0.1
+  platform_detect: ^1.4.0
   flutter:
     sdk: flutter
   flutter_web_plugins:
     sdk: flutter
   meta: ^1.1.7
-  js: ^0.6.0
 
 dev_dependencies:
   flutter_test:
@@ -30,5 +30,5 @@
   mockito: ^4.1.1
 
 environment:
-  sdk: ">=2.1.0 <3.0.0"
+  sdk: ">=2.2.0 <3.0.0"
   flutter: ">=1.10.0 <2.0.0"
diff --git a/packages/url_launcher/url_launcher_web/test/url_launcher_web_test.dart b/packages/url_launcher/url_launcher_web/test/url_launcher_web_test.dart
index b6cf8b7..4cf9206 100644
--- a/packages/url_launcher/url_launcher_web/test/url_launcher_web_test.dart
+++ b/packages/url_launcher/url_launcher_web/test/url_launcher_web_test.dart
@@ -7,9 +7,10 @@
 import 'dart:html' as html;
 import 'package:flutter_test/flutter_test.dart';
 import 'package:url_launcher_web/url_launcher_web.dart';
-import 'package:url_launcher_web/src/navigator.dart' as navigator;
 import 'package:mockito/mockito.dart';
 
+import 'package:platform_detect/test_utils.dart' as platform;
+
 class MockWindow extends Mock implements html.Window {}
 
 void main() {
@@ -17,6 +18,10 @@
     MockWindow mockWindow = MockWindow();
     UrlLauncherPlugin plugin = UrlLauncherPlugin(window: mockWindow);
 
+    setUp(() {
+      platform.configurePlatformForTesting(browser: platform.chrome);
+    });
+
     group('canLaunch', () {
       test('"http" URLs -> true', () {
         expect(plugin.canLaunch('http://google.com'), completion(isTrue));
@@ -75,28 +80,46 @@
     });
 
     group('openNewWindow', () {
-      bool _standalone;
+      test('http urls should be launched in a new window', () {
+        plugin.openNewWindow('http://www.google.com');
 
-      setUp(() {
-        _standalone = navigator.standalone;
+        verify(mockWindow.open('http://www.google.com', ''));
       });
 
-      tearDown(() {
-        navigator.standalone = _standalone;
-      });
-
-      test('the window that is launched is a new window', () {
+      test('https urls should be launched in a new window', () {
         plugin.openNewWindow('https://www.google.com');
 
         verify(mockWindow.open('https://www.google.com', ''));
       });
 
-      test('the window that is launched is in the same window', () {
-        navigator.standalone = true;
+      test('mailto urls should be launched on a new window', () {
+        plugin.openNewWindow('mailto:name@mydomain.com');
 
-        plugin.openNewWindow('https://www.google.com');
+        verify(mockWindow.open('mailto:name@mydomain.com', ''));
+      });
 
-        verify(mockWindow.open('https://www.google.com', '_top'));
+      group('Safari', () {
+        setUp(() {
+          platform.configurePlatformForTesting(browser: platform.safari);
+        });
+
+        test('http urls should be launched in a new window', () {
+          plugin.openNewWindow('http://www.google.com');
+
+          verify(mockWindow.open('http://www.google.com', ''));
+        });
+
+        test('https urls should be launched in a new window', () {
+          plugin.openNewWindow('https://www.google.com');
+
+          verify(mockWindow.open('https://www.google.com', ''));
+        });
+
+        test('mailto urls should be launched on the same window', () {
+          plugin.openNewWindow('mailto:name@mydomain.com');
+
+          verify(mockWindow.open('mailto:name@mydomain.com', '_top'));
+        });
       });
     });
   });