[url_launcher_web] Disallows launching "javascript:" URLs. (#5180)

This PR updates the URL launcher to:

* Implement the new `launchUrl` method.
* Disallow `launch` and `launchUrl` for URLs with the `javascript:` scheme. 
* Prevent _Tabnabbing_.

### Issues

Fixes https://github.com/flutter/flutter/issues/136657

### Tests

Integration tests (and mocks) updated.
diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md
index 4fd4abb..836afdc 100644
--- a/packages/url_launcher/url_launcher_web/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.1.0
+
+* Adds `launchUrl` implementation.
+* Prevents _Tabnabbing_ and disallows `javascript:` URLs on `launch` and `launchUrl`. 
+
 ## 2.0.20
 
 * Migrates to `dart:ui_web` APIs.
diff --git a/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart
index f2dac2b..c909186 100644
--- a/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart
+++ b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart
@@ -28,7 +28,7 @@
       when(mockWindow.navigator).thenReturn(mockNavigator);
 
       // Simulate that window.open does something.
-      when(mockWindow.open(any, any)).thenReturn(MockWindow());
+      when(mockWindow.open(any, any, any)).thenReturn(MockWindow());
 
       when(mockNavigator.userAgent).thenReturn(
           'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36');
@@ -59,6 +59,10 @@
         expect(plugin.canLaunch('sms:+19725551212?body=hello%20there'),
             completion(isTrue));
       });
+
+      testWidgets('"javascript" URLs -> false', (WidgetTester _) async {
+        expect(plugin.canLaunch('javascript:alert("1")'), completion(isFalse));
+      });
     });
 
     group('launch', () {
@@ -93,6 +97,11 @@
             ),
             completion(isTrue));
       });
+
+      testWidgets('launching a "javascript" returns false',
+          (WidgetTester _) async {
+        expect(plugin.launch('javascript:alert("1")'), completion(isFalse));
+      });
     });
 
     group('openNewWindow', () {
@@ -100,42 +109,47 @@
           (WidgetTester _) async {
         plugin.openNewWindow('http://www.google.com');
 
-        verify(mockWindow.open('http://www.google.com', ''));
+        verify(mockWindow.open(
+            'http://www.google.com', '', 'noopener,noreferrer'));
       });
 
       testWidgets('https urls should be launched in a new window',
           (WidgetTester _) async {
         plugin.openNewWindow('https://www.google.com');
 
-        verify(mockWindow.open('https://www.google.com', ''));
+        verify(mockWindow.open(
+            'https://www.google.com', '', 'noopener,noreferrer'));
       });
 
       testWidgets('mailto urls should be launched on a new window',
           (WidgetTester _) async {
         plugin.openNewWindow('mailto:name@mydomain.com');
 
-        verify(mockWindow.open('mailto:name@mydomain.com', ''));
+        verify(mockWindow.open(
+            'mailto:name@mydomain.com', '', 'noopener,noreferrer'));
       });
 
       testWidgets('tel urls should be launched on a new window',
           (WidgetTester _) async {
         plugin.openNewWindow('tel:5551234567');
 
-        verify(mockWindow.open('tel:5551234567', ''));
+        verify(mockWindow.open('tel:5551234567', '', 'noopener,noreferrer'));
       });
 
       testWidgets('sms urls should be launched on a new window',
           (WidgetTester _) async {
         plugin.openNewWindow('sms:+19725551212?body=hello%20there');
 
-        verify(mockWindow.open('sms:+19725551212?body=hello%20there', ''));
+        verify(mockWindow.open(
+            'sms:+19725551212?body=hello%20there', '', 'noopener,noreferrer'));
       });
       testWidgets(
           'setting webOnlyLinkTarget as _self opens the url in the same tab',
           (WidgetTester _) async {
         plugin.openNewWindow('https://www.google.com',
             webOnlyWindowName: '_self');
-        verify(mockWindow.open('https://www.google.com', '_self'));
+        verify(mockWindow.open(
+            'https://www.google.com', '_self', 'noopener,noreferrer'));
       });
 
       testWidgets(
@@ -143,7 +157,8 @@
           (WidgetTester _) async {
         plugin.openNewWindow('https://www.google.com',
             webOnlyWindowName: '_blank');
-        verify(mockWindow.open('https://www.google.com', '_blank'));
+        verify(mockWindow.open(
+            'https://www.google.com', '_blank', 'noopener,noreferrer'));
       });
 
       group('Safari', () {
@@ -158,43 +173,48 @@
             (WidgetTester _) async {
           plugin.openNewWindow('http://www.google.com');
 
-          verify(mockWindow.open('http://www.google.com', ''));
+          verify(mockWindow.open(
+              'http://www.google.com', '', 'noopener,noreferrer'));
         });
 
         testWidgets('https urls should be launched in a new window',
             (WidgetTester _) async {
           plugin.openNewWindow('https://www.google.com');
 
-          verify(mockWindow.open('https://www.google.com', ''));
+          verify(mockWindow.open(
+              'https://www.google.com', '', 'noopener,noreferrer'));
         });
 
         testWidgets('mailto urls should be launched on the same window',
             (WidgetTester _) async {
           plugin.openNewWindow('mailto:name@mydomain.com');
 
-          verify(mockWindow.open('mailto:name@mydomain.com', '_top'));
+          verify(mockWindow.open(
+              'mailto:name@mydomain.com', '_top', 'noopener,noreferrer'));
         });
 
         testWidgets('tel urls should be launched on the same window',
             (WidgetTester _) async {
           plugin.openNewWindow('tel:5551234567');
 
-          verify(mockWindow.open('tel:5551234567', '_top'));
+          verify(
+              mockWindow.open('tel:5551234567', '_top', 'noopener,noreferrer'));
         });
 
         testWidgets('sms urls should be launched on the same window',
             (WidgetTester _) async {
           plugin.openNewWindow('sms:+19725551212?body=hello%20there');
 
-          verify(
-              mockWindow.open('sms:+19725551212?body=hello%20there', '_top'));
+          verify(mockWindow.open('sms:+19725551212?body=hello%20there', '_top',
+              'noopener,noreferrer'));
         });
         testWidgets(
             'mailto urls should use _blank if webOnlyWindowName is set as _blank',
             (WidgetTester _) async {
           plugin.openNewWindow('mailto:name@mydomain.com',
               webOnlyWindowName: '_blank');
-          verify(mockWindow.open('mailto:name@mydomain.com', '_blank'));
+          verify(mockWindow.open(
+              'mailto:name@mydomain.com', '_blank', 'noopener,noreferrer'));
         });
       });
     });
diff --git a/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.mocks.dart b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.mocks.dart
index 004ac13..13ff194 100644
--- a/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.mocks.dart
+++ b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.mocks.dart
@@ -1,7 +1,9 @@
-// Mocks generated by Mockito 5.4.0 from annotations
+// Mocks generated by Mockito 5.4.1 from annotations
 // in regular_integration_tests/integration_test/url_launcher_web_test.dart.
 // Do not manually edit this file.
 
+// @dart=2.19
+
 // ignore_for_file: no_leading_underscores_for_library_prefixes
 import 'dart:async' as _i3;
 import 'dart:html' as _i2;
@@ -196,6 +198,7 @@
         Invocation.getter(#animationFrame),
         returnValue: _i3.Future<num>.value(0),
       ) as _i3.Future<num>);
+
   @override
   _i2.Document get document => (super.noSuchMethod(
         Invocation.getter(#document),
@@ -204,6 +207,7 @@
           Invocation.getter(#document),
         ),
       ) as _i2.Document);
+
   @override
   _i2.Location get location => (super.noSuchMethod(
         Invocation.getter(#location),
@@ -212,6 +216,7 @@
           Invocation.getter(#location),
         ),
       ) as _i2.Location);
+
   @override
   set location(_i2.LocationBase? value) => super.noSuchMethod(
         Invocation.setter(
@@ -220,6 +225,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   _i2.Console get console => (super.noSuchMethod(
         Invocation.getter(#console),
@@ -228,6 +234,7 @@
           Invocation.getter(#console),
         ),
       ) as _i2.Console);
+
   @override
   set defaultStatus(String? value) => super.noSuchMethod(
         Invocation.setter(
@@ -236,6 +243,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   set defaultstatus(String? value) => super.noSuchMethod(
         Invocation.setter(
@@ -244,11 +252,13 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   num get devicePixelRatio => (super.noSuchMethod(
         Invocation.getter(#devicePixelRatio),
         returnValue: 0,
       ) as num);
+
   @override
   _i2.History get history => (super.noSuchMethod(
         Invocation.getter(#history),
@@ -257,6 +267,7 @@
           Invocation.getter(#history),
         ),
       ) as _i2.History);
+
   @override
   _i2.Storage get localStorage => (super.noSuchMethod(
         Invocation.getter(#localStorage),
@@ -265,6 +276,7 @@
           Invocation.getter(#localStorage),
         ),
       ) as _i2.Storage);
+
   @override
   set name(String? value) => super.noSuchMethod(
         Invocation.setter(
@@ -273,6 +285,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   _i2.Navigator get navigator => (super.noSuchMethod(
         Invocation.getter(#navigator),
@@ -281,6 +294,7 @@
           Invocation.getter(#navigator),
         ),
       ) as _i2.Navigator);
+
   @override
   set opener(_i2.WindowBase? value) => super.noSuchMethod(
         Invocation.setter(
@@ -289,16 +303,19 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   int get outerHeight => (super.noSuchMethod(
         Invocation.getter(#outerHeight),
         returnValue: 0,
       ) as int);
+
   @override
   int get outerWidth => (super.noSuchMethod(
         Invocation.getter(#outerWidth),
         returnValue: 0,
       ) as int);
+
   @override
   _i2.Performance get performance => (super.noSuchMethod(
         Invocation.getter(#performance),
@@ -307,6 +324,7 @@
           Invocation.getter(#performance),
         ),
       ) as _i2.Performance);
+
   @override
   _i2.Storage get sessionStorage => (super.noSuchMethod(
         Invocation.getter(#sessionStorage),
@@ -315,6 +333,7 @@
           Invocation.getter(#sessionStorage),
         ),
       ) as _i2.Storage);
+
   @override
   set status(String? value) => super.noSuchMethod(
         Invocation.setter(
@@ -323,413 +342,495 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   _i3.Stream<_i2.Event> get onContentLoaded => (super.noSuchMethod(
         Invocation.getter(#onContentLoaded),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onAbort => (super.noSuchMethod(
         Invocation.getter(#onAbort),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onBlur => (super.noSuchMethod(
         Invocation.getter(#onBlur),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onCanPlay => (super.noSuchMethod(
         Invocation.getter(#onCanPlay),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onCanPlayThrough => (super.noSuchMethod(
         Invocation.getter(#onCanPlayThrough),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onChange => (super.noSuchMethod(
         Invocation.getter(#onChange),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onClick => (super.noSuchMethod(
         Invocation.getter(#onClick),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onContextMenu => (super.noSuchMethod(
         Invocation.getter(#onContextMenu),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.Event> get onDoubleClick => (super.noSuchMethod(
         Invocation.getter(#onDoubleClick),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.DeviceMotionEvent> get onDeviceMotion => (super.noSuchMethod(
         Invocation.getter(#onDeviceMotion),
         returnValue: _i3.Stream<_i2.DeviceMotionEvent>.empty(),
       ) as _i3.Stream<_i2.DeviceMotionEvent>);
+
   @override
   _i3.Stream<_i2.DeviceOrientationEvent> get onDeviceOrientation =>
       (super.noSuchMethod(
         Invocation.getter(#onDeviceOrientation),
         returnValue: _i3.Stream<_i2.DeviceOrientationEvent>.empty(),
       ) as _i3.Stream<_i2.DeviceOrientationEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onDrag => (super.noSuchMethod(
         Invocation.getter(#onDrag),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onDragEnd => (super.noSuchMethod(
         Invocation.getter(#onDragEnd),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onDragEnter => (super.noSuchMethod(
         Invocation.getter(#onDragEnter),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onDragLeave => (super.noSuchMethod(
         Invocation.getter(#onDragLeave),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onDragOver => (super.noSuchMethod(
         Invocation.getter(#onDragOver),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onDragStart => (super.noSuchMethod(
         Invocation.getter(#onDragStart),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onDrop => (super.noSuchMethod(
         Invocation.getter(#onDrop),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.Event> get onDurationChange => (super.noSuchMethod(
         Invocation.getter(#onDurationChange),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onEmptied => (super.noSuchMethod(
         Invocation.getter(#onEmptied),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onEnded => (super.noSuchMethod(
         Invocation.getter(#onEnded),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onError => (super.noSuchMethod(
         Invocation.getter(#onError),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onFocus => (super.noSuchMethod(
         Invocation.getter(#onFocus),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onHashChange => (super.noSuchMethod(
         Invocation.getter(#onHashChange),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onInput => (super.noSuchMethod(
         Invocation.getter(#onInput),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onInvalid => (super.noSuchMethod(
         Invocation.getter(#onInvalid),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.KeyboardEvent> get onKeyDown => (super.noSuchMethod(
         Invocation.getter(#onKeyDown),
         returnValue: _i3.Stream<_i2.KeyboardEvent>.empty(),
       ) as _i3.Stream<_i2.KeyboardEvent>);
+
   @override
   _i3.Stream<_i2.KeyboardEvent> get onKeyPress => (super.noSuchMethod(
         Invocation.getter(#onKeyPress),
         returnValue: _i3.Stream<_i2.KeyboardEvent>.empty(),
       ) as _i3.Stream<_i2.KeyboardEvent>);
+
   @override
   _i3.Stream<_i2.KeyboardEvent> get onKeyUp => (super.noSuchMethod(
         Invocation.getter(#onKeyUp),
         returnValue: _i3.Stream<_i2.KeyboardEvent>.empty(),
       ) as _i3.Stream<_i2.KeyboardEvent>);
+
   @override
   _i3.Stream<_i2.Event> get onLoad => (super.noSuchMethod(
         Invocation.getter(#onLoad),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onLoadedData => (super.noSuchMethod(
         Invocation.getter(#onLoadedData),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onLoadedMetadata => (super.noSuchMethod(
         Invocation.getter(#onLoadedMetadata),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onLoadStart => (super.noSuchMethod(
         Invocation.getter(#onLoadStart),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.MessageEvent> get onMessage => (super.noSuchMethod(
         Invocation.getter(#onMessage),
         returnValue: _i3.Stream<_i2.MessageEvent>.empty(),
       ) as _i3.Stream<_i2.MessageEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onMouseDown => (super.noSuchMethod(
         Invocation.getter(#onMouseDown),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onMouseEnter => (super.noSuchMethod(
         Invocation.getter(#onMouseEnter),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onMouseLeave => (super.noSuchMethod(
         Invocation.getter(#onMouseLeave),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onMouseMove => (super.noSuchMethod(
         Invocation.getter(#onMouseMove),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onMouseOut => (super.noSuchMethod(
         Invocation.getter(#onMouseOut),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onMouseOver => (super.noSuchMethod(
         Invocation.getter(#onMouseOver),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.MouseEvent> get onMouseUp => (super.noSuchMethod(
         Invocation.getter(#onMouseUp),
         returnValue: _i3.Stream<_i2.MouseEvent>.empty(),
       ) as _i3.Stream<_i2.MouseEvent>);
+
   @override
   _i3.Stream<_i2.WheelEvent> get onMouseWheel => (super.noSuchMethod(
         Invocation.getter(#onMouseWheel),
         returnValue: _i3.Stream<_i2.WheelEvent>.empty(),
       ) as _i3.Stream<_i2.WheelEvent>);
+
   @override
   _i3.Stream<_i2.Event> get onOffline => (super.noSuchMethod(
         Invocation.getter(#onOffline),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onOnline => (super.noSuchMethod(
         Invocation.getter(#onOnline),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onPageHide => (super.noSuchMethod(
         Invocation.getter(#onPageHide),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onPageShow => (super.noSuchMethod(
         Invocation.getter(#onPageShow),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onPause => (super.noSuchMethod(
         Invocation.getter(#onPause),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onPlay => (super.noSuchMethod(
         Invocation.getter(#onPlay),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onPlaying => (super.noSuchMethod(
         Invocation.getter(#onPlaying),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.PopStateEvent> get onPopState => (super.noSuchMethod(
         Invocation.getter(#onPopState),
         returnValue: _i3.Stream<_i2.PopStateEvent>.empty(),
       ) as _i3.Stream<_i2.PopStateEvent>);
+
   @override
   _i3.Stream<_i2.Event> get onProgress => (super.noSuchMethod(
         Invocation.getter(#onProgress),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onRateChange => (super.noSuchMethod(
         Invocation.getter(#onRateChange),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onReset => (super.noSuchMethod(
         Invocation.getter(#onReset),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onResize => (super.noSuchMethod(
         Invocation.getter(#onResize),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onScroll => (super.noSuchMethod(
         Invocation.getter(#onScroll),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onSearch => (super.noSuchMethod(
         Invocation.getter(#onSearch),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onSeeked => (super.noSuchMethod(
         Invocation.getter(#onSeeked),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onSeeking => (super.noSuchMethod(
         Invocation.getter(#onSeeking),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onSelect => (super.noSuchMethod(
         Invocation.getter(#onSelect),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onStalled => (super.noSuchMethod(
         Invocation.getter(#onStalled),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.StorageEvent> get onStorage => (super.noSuchMethod(
         Invocation.getter(#onStorage),
         returnValue: _i3.Stream<_i2.StorageEvent>.empty(),
       ) as _i3.Stream<_i2.StorageEvent>);
+
   @override
   _i3.Stream<_i2.Event> get onSubmit => (super.noSuchMethod(
         Invocation.getter(#onSubmit),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onSuspend => (super.noSuchMethod(
         Invocation.getter(#onSuspend),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onTimeUpdate => (super.noSuchMethod(
         Invocation.getter(#onTimeUpdate),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.TouchEvent> get onTouchCancel => (super.noSuchMethod(
         Invocation.getter(#onTouchCancel),
         returnValue: _i3.Stream<_i2.TouchEvent>.empty(),
       ) as _i3.Stream<_i2.TouchEvent>);
+
   @override
   _i3.Stream<_i2.TouchEvent> get onTouchEnd => (super.noSuchMethod(
         Invocation.getter(#onTouchEnd),
         returnValue: _i3.Stream<_i2.TouchEvent>.empty(),
       ) as _i3.Stream<_i2.TouchEvent>);
+
   @override
   _i3.Stream<_i2.TouchEvent> get onTouchMove => (super.noSuchMethod(
         Invocation.getter(#onTouchMove),
         returnValue: _i3.Stream<_i2.TouchEvent>.empty(),
       ) as _i3.Stream<_i2.TouchEvent>);
+
   @override
   _i3.Stream<_i2.TouchEvent> get onTouchStart => (super.noSuchMethod(
         Invocation.getter(#onTouchStart),
         returnValue: _i3.Stream<_i2.TouchEvent>.empty(),
       ) as _i3.Stream<_i2.TouchEvent>);
+
   @override
   _i3.Stream<_i2.TransitionEvent> get onTransitionEnd => (super.noSuchMethod(
         Invocation.getter(#onTransitionEnd),
         returnValue: _i3.Stream<_i2.TransitionEvent>.empty(),
       ) as _i3.Stream<_i2.TransitionEvent>);
+
   @override
   _i3.Stream<_i2.Event> get onUnload => (super.noSuchMethod(
         Invocation.getter(#onUnload),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onVolumeChange => (super.noSuchMethod(
         Invocation.getter(#onVolumeChange),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.Event> get onWaiting => (super.noSuchMethod(
         Invocation.getter(#onWaiting),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.AnimationEvent> get onAnimationEnd => (super.noSuchMethod(
         Invocation.getter(#onAnimationEnd),
         returnValue: _i3.Stream<_i2.AnimationEvent>.empty(),
       ) as _i3.Stream<_i2.AnimationEvent>);
+
   @override
   _i3.Stream<_i2.AnimationEvent> get onAnimationIteration =>
       (super.noSuchMethod(
         Invocation.getter(#onAnimationIteration),
         returnValue: _i3.Stream<_i2.AnimationEvent>.empty(),
       ) as _i3.Stream<_i2.AnimationEvent>);
+
   @override
   _i3.Stream<_i2.AnimationEvent> get onAnimationStart => (super.noSuchMethod(
         Invocation.getter(#onAnimationStart),
         returnValue: _i3.Stream<_i2.AnimationEvent>.empty(),
       ) as _i3.Stream<_i2.AnimationEvent>);
+
   @override
   _i3.Stream<_i2.Event> get onBeforeUnload => (super.noSuchMethod(
         Invocation.getter(#onBeforeUnload),
         returnValue: _i3.Stream<_i2.Event>.empty(),
       ) as _i3.Stream<_i2.Event>);
+
   @override
   _i3.Stream<_i2.WheelEvent> get onWheel => (super.noSuchMethod(
         Invocation.getter(#onWheel),
         returnValue: _i3.Stream<_i2.WheelEvent>.empty(),
       ) as _i3.Stream<_i2.WheelEvent>);
+
   @override
   int get pageXOffset => (super.noSuchMethod(
         Invocation.getter(#pageXOffset),
         returnValue: 0,
       ) as int);
+
   @override
   int get pageYOffset => (super.noSuchMethod(
         Invocation.getter(#pageYOffset),
         returnValue: 0,
       ) as int);
+
   @override
   int get scrollX => (super.noSuchMethod(
         Invocation.getter(#scrollX),
         returnValue: 0,
       ) as int);
+
   @override
   int get scrollY => (super.noSuchMethod(
         Invocation.getter(#scrollY),
         returnValue: 0,
       ) as int);
+
   @override
   _i2.Events get on => (super.noSuchMethod(
         Invocation.getter(#on),
@@ -738,6 +839,7 @@
           Invocation.getter(#on),
         ),
       ) as _i2.Events);
+
   @override
   _i2.WindowBase open(
     String? url,
@@ -765,6 +867,7 @@
           ),
         ),
       ) as _i2.WindowBase);
+
   @override
   int requestAnimationFrame(_i2.FrameRequestCallback? callback) =>
       (super.noSuchMethod(
@@ -774,6 +877,7 @@
         ),
         returnValue: 0,
       ) as int);
+
   @override
   void cancelAnimationFrame(int? id) => super.noSuchMethod(
         Invocation.method(
@@ -782,6 +886,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   _i3.Future<_i2.FileSystem> requestFileSystem(
     int? size, {
@@ -802,6 +907,7 @@
           ),
         )),
       ) as _i3.Future<_i2.FileSystem>);
+
   @override
   void alert([String? message]) => super.noSuchMethod(
         Invocation.method(
@@ -810,6 +916,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   void cancelIdleCallback(int? handle) => super.noSuchMethod(
         Invocation.method(
@@ -818,6 +925,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   void close() => super.noSuchMethod(
         Invocation.method(
@@ -826,6 +934,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   bool confirm([String? message]) => (super.noSuchMethod(
         Invocation.method(
@@ -834,6 +943,7 @@
         ),
         returnValue: false,
       ) as bool);
+
   @override
   _i3.Future<dynamic> fetch(
     dynamic input, [
@@ -849,6 +959,7 @@
         ),
         returnValue: _i3.Future<dynamic>.value(),
       ) as _i3.Future<dynamic>);
+
   @override
   bool find(
     String? string,
@@ -874,6 +985,7 @@
         ),
         returnValue: false,
       ) as bool);
+
   @override
   _i2.StylePropertyMapReadonly getComputedStyleMap(
     _i2.Element? element,
@@ -898,6 +1010,7 @@
           ),
         ),
       ) as _i2.StylePropertyMapReadonly);
+
   @override
   List<_i2.CssRule> getMatchedCssRules(
     _i2.Element? element,
@@ -913,6 +1026,7 @@
         ),
         returnValue: <_i2.CssRule>[],
       ) as List<_i2.CssRule>);
+
   @override
   _i2.MediaQueryList matchMedia(String? query) => (super.noSuchMethod(
         Invocation.method(
@@ -927,6 +1041,7 @@
           ),
         ),
       ) as _i2.MediaQueryList);
+
   @override
   void moveBy(
     int? x,
@@ -942,6 +1057,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   void postMessage(
     dynamic message,
@@ -959,6 +1075,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   void print() => super.noSuchMethod(
         Invocation.method(
@@ -967,6 +1084,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   int requestIdleCallback(
     _i2.IdleRequestCallback? callback, [
@@ -982,6 +1100,7 @@
         ),
         returnValue: 0,
       ) as int);
+
   @override
   void resizeBy(
     int? x,
@@ -997,6 +1116,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   void resizeTo(
     int? x,
@@ -1012,6 +1132,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   void scroll([
     dynamic options_OR_x,
@@ -1029,6 +1150,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   void scrollBy([
     dynamic options_OR_x,
@@ -1046,6 +1168,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   void scrollTo([
     dynamic options_OR_x,
@@ -1063,6 +1186,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   void stop() => super.noSuchMethod(
         Invocation.method(
@@ -1071,6 +1195,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   _i3.Future<_i2.Entry> resolveLocalFileSystemUrl(String? url) =>
       (super.noSuchMethod(
@@ -1086,6 +1211,7 @@
           ),
         )),
       ) as _i3.Future<_i2.Entry>);
+
   @override
   String atob(String? atob) => (super.noSuchMethod(
         Invocation.method(
@@ -1094,6 +1220,7 @@
         ),
         returnValue: '',
       ) as String);
+
   @override
   String btoa(String? btoa) => (super.noSuchMethod(
         Invocation.method(
@@ -1102,6 +1229,7 @@
         ),
         returnValue: '',
       ) as String);
+
   @override
   void moveTo(_i4.Point<num>? p) => super.noSuchMethod(
         Invocation.method(
@@ -1110,6 +1238,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   void addEventListener(
     String? type,
@@ -1127,6 +1256,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   void removeEventListener(
     String? type,
@@ -1144,6 +1274,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   bool dispatchEvent(_i2.Event? event) => (super.noSuchMethod(
         Invocation.method(
@@ -1167,6 +1298,7 @@
         Invocation.getter(#language),
         returnValue: '',
       ) as String);
+
   @override
   _i2.Geolocation get geolocation => (super.noSuchMethod(
         Invocation.getter(#geolocation),
@@ -1175,41 +1307,49 @@
           Invocation.getter(#geolocation),
         ),
       ) as _i2.Geolocation);
+
   @override
   String get vendor => (super.noSuchMethod(
         Invocation.getter(#vendor),
         returnValue: '',
       ) as String);
+
   @override
   String get vendorSub => (super.noSuchMethod(
         Invocation.getter(#vendorSub),
         returnValue: '',
       ) as String);
+
   @override
   String get appCodeName => (super.noSuchMethod(
         Invocation.getter(#appCodeName),
         returnValue: '',
       ) as String);
+
   @override
   String get appName => (super.noSuchMethod(
         Invocation.getter(#appName),
         returnValue: '',
       ) as String);
+
   @override
   String get appVersion => (super.noSuchMethod(
         Invocation.getter(#appVersion),
         returnValue: '',
       ) as String);
+
   @override
   String get product => (super.noSuchMethod(
         Invocation.getter(#product),
         returnValue: '',
       ) as String);
+
   @override
   String get userAgent => (super.noSuchMethod(
         Invocation.getter(#userAgent),
         returnValue: '',
       ) as String);
+
   @override
   List<_i2.Gamepad?> getGamepads() => (super.noSuchMethod(
         Invocation.method(
@@ -1218,6 +1358,7 @@
         ),
         returnValue: <_i2.Gamepad?>[],
       ) as List<_i2.Gamepad?>);
+
   @override
   _i3.Future<_i2.MediaStream> getUserMedia({
     dynamic audio = false,
@@ -1244,6 +1385,7 @@
           ),
         )),
       ) as _i3.Future<_i2.MediaStream>);
+
   @override
   void cancelKeyboardLock() => super.noSuchMethod(
         Invocation.method(
@@ -1252,6 +1394,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   _i3.Future<dynamic> getBattery() => (super.noSuchMethod(
         Invocation.method(
@@ -1260,6 +1403,7 @@
         ),
         returnValue: _i3.Future<dynamic>.value(),
       ) as _i3.Future<dynamic>);
+
   @override
   _i3.Future<_i2.RelatedApplication> getInstalledRelatedApps() =>
       (super.noSuchMethod(
@@ -1276,6 +1420,7 @@
           ),
         )),
       ) as _i3.Future<_i2.RelatedApplication>);
+
   @override
   _i3.Future<dynamic> getVRDisplays() => (super.noSuchMethod(
         Invocation.method(
@@ -1284,6 +1429,7 @@
         ),
         returnValue: _i3.Future<dynamic>.value(),
       ) as _i3.Future<dynamic>);
+
   @override
   void registerProtocolHandler(
     String? scheme,
@@ -1301,6 +1447,7 @@
         ),
         returnValueForMissingStub: null,
       );
+
   @override
   _i3.Future<dynamic> requestKeyboardLock([List<String>? keyCodes]) =>
       (super.noSuchMethod(
@@ -1310,6 +1457,7 @@
         ),
         returnValue: _i3.Future<dynamic>.value(),
       ) as _i3.Future<dynamic>);
+
   @override
   _i3.Future<dynamic> requestMidiAccess([Map<dynamic, dynamic>? options]) =>
       (super.noSuchMethod(
@@ -1319,6 +1467,7 @@
         ),
         returnValue: _i3.Future<dynamic>.value(),
       ) as _i3.Future<dynamic>);
+
   @override
   _i3.Future<dynamic> requestMediaKeySystemAccess(
     String? keySystem,
@@ -1334,6 +1483,7 @@
         ),
         returnValue: _i3.Future<dynamic>.value(),
       ) as _i3.Future<dynamic>);
+
   @override
   bool sendBeacon(
     String? url,
@@ -1349,6 +1499,7 @@
         ),
         returnValue: false,
       ) as bool);
+
   @override
   _i3.Future<dynamic> share([Map<dynamic, dynamic>? data]) =>
       (super.noSuchMethod(
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 be40539..bf96bf2 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
@@ -6,7 +6,7 @@
 import 'dart:html' as html;
 import 'dart:ui_web' as ui_web;
 
-import 'package:flutter/foundation.dart' show visibleForTesting;
+import 'package:flutter/foundation.dart' show kDebugMode, visibleForTesting;
 import 'package:flutter_web_plugins/flutter_web_plugins.dart' show Registrar;
 import 'package:url_launcher_platform_interface/link.dart';
 import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
@@ -20,8 +20,14 @@
 };
 String? _getUrlScheme(String url) => Uri.tryParse(url)?.scheme;
 
-bool _isSafariTargetTopScheme(String url) =>
-    _safariTargetTopSchemes.contains(_getUrlScheme(url));
+bool _isSafariTargetTopScheme(String? scheme) =>
+    _safariTargetTopSchemes.contains(scheme);
+
+// The set of schemes that are explicitly disallowed by the plugin.
+const Set<String> _disallowedSchemes = <String>{
+  'javascript',
+};
+bool _isDisallowedScheme(String? scheme) => _disallowedSchemes.contains(scheme);
 
 bool _navigatorIsSafari(html.Navigator navigator) =>
     navigator.userAgent.contains('Safari') &&
@@ -40,7 +46,7 @@
   final html.Window _window;
   bool _isSafari = false;
 
-  // The set of schemes that can be handled by the plugin
+  // The set of schemes that can be handled by the plugin.
   static final Set<String> _supportedSchemes = <String>{
     'http',
     'https',
@@ -62,13 +68,23 @@
   ///
   /// Returns the newly created window.
   @visibleForTesting
-  html.WindowBase openNewWindow(String url, {String? webOnlyWindowName}) {
-    // We need to open mailto, tel and sms urls on the _top window context on safari browsers.
-    // See https://github.com/flutter/flutter/issues/51461 for reference.
+  html.WindowBase? openNewWindow(String url, {String? webOnlyWindowName}) {
+    final String? scheme = _getUrlScheme(url);
+    // Actively disallow opening some schemes, like javascript.
+    // See https://github.com/flutter/flutter/issues/136657
+    if (_isDisallowedScheme(scheme)) {
+      if (kDebugMode) {
+        print('Disallowed URL with scheme: $scheme');
+      }
+      return null;
+    }
+    // Some schemes need to be opened on the _top window context on Safari.
+    // See https://github.com/flutter/flutter/issues/51461
     final String target = webOnlyWindowName ??
-        ((_isSafari && _isSafariTargetTopScheme(url)) ? '_top' : '');
+        ((_isSafari && _isSafariTargetTopScheme(scheme)) ? '_top' : '');
+
     // ignore: unsafe_html
-    return _window.open(url, target);
+    return _window.open(url, target, 'noopener,noreferrer');
   }
 
   @override
@@ -87,7 +103,12 @@
     Map<String, String> headers = const <String, String>{},
     String? webOnlyWindowName,
   }) async {
-    openNewWindow(url, webOnlyWindowName: webOnlyWindowName);
-    return true;
+    return launchUrl(url, LaunchOptions(webOnlyWindowName: webOnlyWindowName));
+  }
+
+  @override
+  Future<bool> launchUrl(String url, LaunchOptions options) async {
+    final String? windowName = options.webOnlyWindowName;
+    return openNewWindow(url, webOnlyWindowName: windowName) != null;
   }
 }
diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml
index fce7da4..95dd915 100644
--- a/packages/url_launcher/url_launcher_web/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_web/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Web platform implementation of url_launcher
 repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_web
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
-version: 2.0.20
+version: 2.1.0
 
 environment:
   sdk: ">=3.1.0 <4.0.0"
@@ -21,7 +21,7 @@
     sdk: flutter
   flutter_web_plugins:
     sdk: flutter
-  url_launcher_platform_interface: ^2.0.3
+  url_launcher_platform_interface: ^2.1.0
 
 dev_dependencies:
   flutter_test: