diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index 4fd4abb5ec9..836afdcde8a 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 f2dac2bb143..c9091863ad3 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { ), completion(isTrue)); }); + + testWidgets('launching a "javascript" returns false', + (WidgetTester _) async { + expect(plugin.launch('javascript:alert("1")'), completion(isFalse)); + }); }); group('openNewWindow', () { @@ -100,42 +109,47 @@ void main() { (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 @@ void main() { (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 @@ void main() { (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 004ac135ae4..13ff194eabc 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 @@ class MockWindow extends _i1.Mock implements _i2.Window { Invocation.getter(#animationFrame), returnValue: _i3.Future.value(0), ) as _i3.Future); + @override _i2.Document get document => (super.noSuchMethod( Invocation.getter(#document), @@ -204,6 +207,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { Invocation.getter(#document), ), ) as _i2.Document); + @override _i2.Location get location => (super.noSuchMethod( Invocation.getter(#location), @@ -212,6 +216,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { Invocation.getter(#location), ), ) as _i2.Location); + @override set location(_i2.LocationBase? value) => super.noSuchMethod( Invocation.setter( @@ -220,6 +225,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override _i2.Console get console => (super.noSuchMethod( Invocation.getter(#console), @@ -228,6 +234,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { Invocation.getter(#console), ), ) as _i2.Console); + @override set defaultStatus(String? value) => super.noSuchMethod( Invocation.setter( @@ -236,6 +243,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override set defaultstatus(String? value) => super.noSuchMethod( Invocation.setter( @@ -244,11 +252,13 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), 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 @@ class MockWindow extends _i1.Mock implements _i2.Window { Invocation.getter(#history), ), ) as _i2.History); + @override _i2.Storage get localStorage => (super.noSuchMethod( Invocation.getter(#localStorage), @@ -265,6 +276,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { Invocation.getter(#localStorage), ), ) as _i2.Storage); + @override set name(String? value) => super.noSuchMethod( Invocation.setter( @@ -273,6 +285,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override _i2.Navigator get navigator => (super.noSuchMethod( Invocation.getter(#navigator), @@ -281,6 +294,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { Invocation.getter(#navigator), ), ) as _i2.Navigator); + @override set opener(_i2.WindowBase? value) => super.noSuchMethod( Invocation.setter( @@ -289,16 +303,19 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), 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 @@ class MockWindow extends _i1.Mock implements _i2.Window { Invocation.getter(#performance), ), ) as _i2.Performance); + @override _i2.Storage get sessionStorage => (super.noSuchMethod( Invocation.getter(#sessionStorage), @@ -315,6 +333,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { Invocation.getter(#sessionStorage), ), ) as _i2.Storage); + @override set status(String? value) => super.noSuchMethod( Invocation.setter( @@ -323,413 +342,495 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), 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 @@ class MockWindow extends _i1.Mock implements _i2.Window { Invocation.getter(#on), ), ) as _i2.Events); + @override _i2.WindowBase open( String? url, @@ -765,6 +867,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), ), ) as _i2.WindowBase); + @override int requestAnimationFrame(_i2.FrameRequestCallback? callback) => (super.noSuchMethod( @@ -774,6 +877,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValue: 0, ) as int); + @override void cancelAnimationFrame(int? id) => super.noSuchMethod( Invocation.method( @@ -782,6 +886,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override _i3.Future<_i2.FileSystem> requestFileSystem( int? size, { @@ -802,6 +907,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), )), ) as _i3.Future<_i2.FileSystem>); + @override void alert([String? message]) => super.noSuchMethod( Invocation.method( @@ -810,6 +916,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override void cancelIdleCallback(int? handle) => super.noSuchMethod( Invocation.method( @@ -818,6 +925,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override void close() => super.noSuchMethod( Invocation.method( @@ -826,6 +934,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override bool confirm([String? message]) => (super.noSuchMethod( Invocation.method( @@ -834,6 +943,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValue: false, ) as bool); + @override _i3.Future fetch( dynamic input, [ @@ -849,6 +959,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValue: _i3.Future.value(), ) as _i3.Future); + @override bool find( String? string, @@ -874,6 +985,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValue: false, ) as bool); + @override _i2.StylePropertyMapReadonly getComputedStyleMap( _i2.Element? element, @@ -898,6 +1010,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), ), ) as _i2.StylePropertyMapReadonly); + @override List<_i2.CssRule> getMatchedCssRules( _i2.Element? element, @@ -913,6 +1026,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValue: <_i2.CssRule>[], ) as List<_i2.CssRule>); + @override _i2.MediaQueryList matchMedia(String? query) => (super.noSuchMethod( Invocation.method( @@ -927,6 +1041,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), ), ) as _i2.MediaQueryList); + @override void moveBy( int? x, @@ -942,6 +1057,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override void postMessage( dynamic message, @@ -959,6 +1075,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override void print() => super.noSuchMethod( Invocation.method( @@ -967,6 +1084,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override int requestIdleCallback( _i2.IdleRequestCallback? callback, [ @@ -982,6 +1100,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValue: 0, ) as int); + @override void resizeBy( int? x, @@ -997,6 +1116,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override void resizeTo( int? x, @@ -1012,6 +1132,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override void scroll([ dynamic options_OR_x, @@ -1029,6 +1150,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override void scrollBy([ dynamic options_OR_x, @@ -1046,6 +1168,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override void scrollTo([ dynamic options_OR_x, @@ -1063,6 +1186,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override void stop() => super.noSuchMethod( Invocation.method( @@ -1071,6 +1195,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override _i3.Future<_i2.Entry> resolveLocalFileSystemUrl(String? url) => (super.noSuchMethod( @@ -1086,6 +1211,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), )), ) as _i3.Future<_i2.Entry>); + @override String atob(String? atob) => (super.noSuchMethod( Invocation.method( @@ -1094,6 +1220,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValue: '', ) as String); + @override String btoa(String? btoa) => (super.noSuchMethod( Invocation.method( @@ -1102,6 +1229,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValue: '', ) as String); + @override void moveTo(_i4.Point? p) => super.noSuchMethod( Invocation.method( @@ -1110,6 +1238,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override void addEventListener( String? type, @@ -1127,6 +1256,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override void removeEventListener( String? type, @@ -1144,6 +1274,7 @@ class MockWindow extends _i1.Mock implements _i2.Window { ), returnValueForMissingStub: null, ); + @override bool dispatchEvent(_i2.Event? event) => (super.noSuchMethod( Invocation.method( @@ -1167,6 +1298,7 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { Invocation.getter(#language), returnValue: '', ) as String); + @override _i2.Geolocation get geolocation => (super.noSuchMethod( Invocation.getter(#geolocation), @@ -1175,41 +1307,49 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { 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 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { ), returnValue: <_i2.Gamepad?>[], ) as List<_i2.Gamepad?>); + @override _i3.Future<_i2.MediaStream> getUserMedia({ dynamic audio = false, @@ -1244,6 +1385,7 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { ), )), ) as _i3.Future<_i2.MediaStream>); + @override void cancelKeyboardLock() => super.noSuchMethod( Invocation.method( @@ -1252,6 +1394,7 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { ), returnValueForMissingStub: null, ); + @override _i3.Future getBattery() => (super.noSuchMethod( Invocation.method( @@ -1260,6 +1403,7 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { ), returnValue: _i3.Future.value(), ) as _i3.Future); + @override _i3.Future<_i2.RelatedApplication> getInstalledRelatedApps() => (super.noSuchMethod( @@ -1276,6 +1420,7 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { ), )), ) as _i3.Future<_i2.RelatedApplication>); + @override _i3.Future getVRDisplays() => (super.noSuchMethod( Invocation.method( @@ -1284,6 +1429,7 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { ), returnValue: _i3.Future.value(), ) as _i3.Future); + @override void registerProtocolHandler( String? scheme, @@ -1301,6 +1447,7 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { ), returnValueForMissingStub: null, ); + @override _i3.Future requestKeyboardLock([List? keyCodes]) => (super.noSuchMethod( @@ -1310,6 +1457,7 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { ), returnValue: _i3.Future.value(), ) as _i3.Future); + @override _i3.Future requestMidiAccess([Map? options]) => (super.noSuchMethod( @@ -1319,6 +1467,7 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { ), returnValue: _i3.Future.value(), ) as _i3.Future); + @override _i3.Future requestMediaKeySystemAccess( String? keySystem, @@ -1334,6 +1483,7 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { ), returnValue: _i3.Future.value(), ) as _i3.Future); + @override bool sendBeacon( String? url, @@ -1349,6 +1499,7 @@ class MockNavigator extends _i1.Mock implements _i2.Navigator { ), returnValue: false, ) as bool); + @override _i3.Future share([Map? 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 be40539d473..bf96bf2aa29 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:async'; 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 @@ const Set _safariTargetTopSchemes = { }; 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 _disallowedSchemes = { + 'javascript', +}; +bool _isDisallowedScheme(String? scheme) => _disallowedSchemes.contains(scheme); bool _navigatorIsSafari(html.Navigator navigator) => navigator.userAgent.contains('Safari') && @@ -40,7 +46,7 @@ class UrlLauncherPlugin extends UrlLauncherPlatform { 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 _supportedSchemes = { 'http', 'https', @@ -62,13 +68,23 @@ class UrlLauncherPlugin extends UrlLauncherPlatform { /// /// 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 @@ class UrlLauncherPlugin extends UrlLauncherPlatform { Map headers = const {}, String? webOnlyWindowName, }) async { - openNewWindow(url, webOnlyWindowName: webOnlyWindowName); - return true; + return launchUrl(url, LaunchOptions(webOnlyWindowName: webOnlyWindowName)); + } + + @override + Future 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 fce7da421b1..95dd915d287 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_web 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 @@ dependencies: 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: