Unverified Commit e148bf87 authored by David Iglesias's avatar David Iglesias Committed by GitHub

[flutter_web_plugins] Migrate to null safety. (#69844)

parent 2b512781
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
/// The platform channels and plugin registry implementations for
/// the web implementations of Flutter plugins.
///
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
@JS()
library js_location_strategy;
......@@ -16,7 +14,7 @@ import 'package:meta/meta.dart';
import 'url_strategy.dart';
typedef _JsSetUrlStrategy = void Function(JsUrlStrategy);
typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?);
/// A JavaScript hook to customize the URL strategy of a Flutter app.
//
......@@ -29,7 +27,7 @@ external _JsSetUrlStrategy get jsSetUrlStrategy;
typedef _PathGetter = String Function();
typedef _StateGetter = Object Function();
typedef _StateGetter = Object? Function();
typedef _AddPopStateListener = ui.VoidCallback Function(html.EventListener);
......@@ -43,10 +41,6 @@ typedef _HistoryMove = Future<void> Function(int count);
/// Given a Dart implementation of URL strategy, converts it to a JavaScript
/// URL strategy to be passed through JS interop.
JsUrlStrategy convertToJsUrlStrategy(UrlStrategy strategy) {
if (strategy == null) {
return null;
}
return JsUrlStrategy(
getPath: allowInterop(strategy.getPath),
getState: allowInterop(strategy.getState),
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:async';
import 'dart:html' as html;
import 'dart:ui' as ui;
......@@ -14,8 +12,12 @@ import 'utils.dart';
/// Change the strategy to use for handling browser URL.
///
/// Setting this to null disables all integration with the browser history.
void setUrlStrategy(UrlStrategy strategy) {
jsSetUrlStrategy(convertToJsUrlStrategy(strategy));
void setUrlStrategy(UrlStrategy? strategy) {
JsUrlStrategy? jsUrlStrategy;
if (strategy != null) {
jsUrlStrategy = convertToJsUrlStrategy(strategy);
}
jsSetUrlStrategy(jsUrlStrategy);
}
/// Represents and reads route state from the browser's URL.
......@@ -37,7 +39,7 @@ abstract class UrlStrategy {
/// The state of the current browser history entry.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state
Object getState();
Object? getState();
/// Given a path that's internal to the app, create the external url that
/// will be used in the browser.
......@@ -101,7 +103,7 @@ class HashUrlStrategy extends UrlStrategy {
String getPath() {
// the hash value is always prefixed with a `#`
// and if it is empty then it will stay empty
final String path = _platformLocation.hash ?? '';
final String path = _platformLocation.hash;
assert(path.isEmpty || path.startsWith('#'));
// We don't want to return an empty string as a path. Instead we default to "/".
......@@ -113,7 +115,7 @@ class HashUrlStrategy extends UrlStrategy {
}
@override
Object getState() => _platformLocation.state;
Object? getState() => _platformLocation.state;
@override
String prepareExternalUrl(String internalUrl) {
......@@ -148,7 +150,7 @@ class HashUrlStrategy extends UrlStrategy {
/// `history.back` transition.
Future<void> _waitForPopState() {
final Completer<void> completer = Completer<void>();
ui.VoidCallback unsubscribe;
late ui.VoidCallback unsubscribe;
unsubscribe = addPopStateListener((_) {
unsubscribe();
completer.complete();
......@@ -238,7 +240,7 @@ abstract class PlatformLocation {
/// The `state` in the current history entry.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state
Object get state;
Object? get state;
/// Adds a new entry to the browser history stack.
///
......@@ -266,7 +268,7 @@ abstract class PlatformLocation {
/// The base href where the Flutter app is being served.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
String getBaseHref();
String? getBaseHref();
}
/// Delegates to real browser APIs to provide platform location functionality.
......@@ -274,6 +276,14 @@ class BrowserPlatformLocation extends PlatformLocation {
/// Default constructor for [BrowserPlatformLocation].
const BrowserPlatformLocation();
// Default value for [pathname] when it's not set in window.location.
// According to MDN this should be ''. Chrome seems to return '/'.
static const String _defaultPathname = '';
// Default value for [search] when it's not set in window.location.
// According to both chrome, and the MDN, this is ''.
static const String _defaultSearch = '';
html.Location get _location => html.window.location;
html.History get _history => html.window.history;
......@@ -288,16 +298,16 @@ class BrowserPlatformLocation extends PlatformLocation {
}
@override
String get pathname => _location.pathname;
String get pathname => _location.pathname ?? _defaultPathname;
@override
String get search => _location.search;
String get search => _location.search ?? _defaultSearch;
@override
String get hash => _location.hash;
@override
Object get state => _history.state;
Object? get state => _history.state;
@override
void pushState(Object state, String title, String url) {
......@@ -315,6 +325,5 @@ class BrowserPlatformLocation extends PlatformLocation {
}
@override
String getBaseHref() => getBaseElementHrefFromDom();
// String getBaseHref() => html.document.baseUri;
String? getBaseHref() => getBaseElementHrefFromDom();
}
......@@ -2,43 +2,33 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:html';
AnchorElement _urlParsingNode;
// TODO(mdebbar): Use the `URI` class instead?
final AnchorElement _urlParsingNode = AnchorElement();
/// Extracts the pathname part of a full [url].
///
/// Example: for the url `http://example.com/foo`, the extracted pathname will
/// be `/foo`.
String extractPathname(String url) {
// TODO(mdebbar): Use the `URI` class instead?
_urlParsingNode ??= AnchorElement();
_urlParsingNode.href = url;
final String pathname = _urlParsingNode.pathname;
final String pathname = _urlParsingNode.pathname ?? '';
return (pathname.isEmpty || pathname[0] == '/') ? pathname : '/$pathname';
}
Element _baseElement;
// The <base> element in the document.
final Element? _baseElement = document.querySelector('base');
/// Finds the <base> element in the document and returns its `href` attribute.
/// Returns the `href` attribute of the <base> element in the document.
///
/// Returns null if the element isn't found.
String getBaseElementHrefFromDom() {
if (_baseElement == null) {
_baseElement = document.querySelector('base');
if (_baseElement == null) {
return null;
}
}
return _baseElement.getAttribute('href');
}
String? getBaseElementHrefFromDom() => _baseElement?.getAttribute('href');
/// Checks that [baseHref] is set.
///
/// Throws an exception otherwise.
String checkBaseHref(String baseHref) {
String checkBaseHref(String? baseHref) {
if (baseHref == null) {
throw Exception('Please add a <base> element to your index.html');
}
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:async';
import 'package:flutter/services.dart';
......@@ -28,14 +26,14 @@ import 'plugin_registry.dart';
///
/// The first method is `listen`. When called, it begins forwarding
/// messages to the framework side when they are added to the
/// [controller]. This triggers the [StreamController.onListen] callback
/// on the [controller].
/// `controller`. This triggers the [StreamController.onListen] callback
/// on the `controller`.
///
/// The other method is `cancel`. When called, it stops forwarding
/// events to the framework. This triggers the [StreamController.onCancel]
/// callback on the [controller].
/// callback on the `controller`.
///
/// Events added to the [controller] when the framework is not
/// Events added to the `controller` when the framework is not
/// subscribed are silently discarded.
class PluginEventChannel<T> {
/// Creates a new plugin event channel.
......@@ -45,8 +43,8 @@ class PluginEventChannel<T> {
this.name, [
this.codec = const StandardMethodCodec(),
this.binaryMessenger,
]) : assert(name != null),
assert(codec != null);
]) : assert(name != null), // ignore: unnecessary_null_comparison
assert(codec != null); // ignore: unnecessary_null_comparison
/// The logical channel on which communication happens.
///
......@@ -63,7 +61,7 @@ class PluginEventChannel<T> {
/// When this is null, the [pluginBinaryMessenger] is used instead,
/// which sends messages from the platform-side to the
/// framework-side.
final BinaryMessenger binaryMessenger;
final BinaryMessenger? binaryMessenger;
/// Use [setController] instead.
///
......@@ -81,7 +79,7 @@ class PluginEventChannel<T> {
///
/// Setting the controller to null disconnects from the channel (setting
/// the message handler on the [binaryMessenger] to null).
void setController(StreamController<T> controller) {
void setController(StreamController<T>? controller) {
final BinaryMessenger messenger = binaryMessenger ?? pluginBinaryMessenger;
if (controller == null) {
messenger.setMessageHandler(name, null);
......@@ -105,16 +103,21 @@ class PluginEventChannel<T> {
}
class _EventChannelHandler<T> {
_EventChannelHandler(this.name, this.codec, this.controller, this.messenger) : assert(messenger != null);
_EventChannelHandler(
this.name,
this.codec,
this.controller,
this.messenger,
) : assert(messenger != null); // ignore: unnecessary_null_comparison
final String name;
final MethodCodec codec;
final StreamController<T> controller;
final BinaryMessenger messenger;
StreamSubscription<T> subscription;
StreamSubscription<T>? subscription;
Future<ByteData> handle(ByteData message) {
Future<ByteData>? handle(ByteData? message) {
final MethodCall call = codec.decodeMethodCall(message);
switch (call.method) {
case 'listen':
......@@ -128,9 +131,8 @@ class _EventChannelHandler<T> {
}
Future<ByteData> _listen() async {
if (subscription != null) {
await subscription.cancel();
}
// Cancel any existing subscription.
await subscription?.cancel();
subscription = controller.stream.listen((dynamic event) {
messenger.send(name, codec.encodeSuccessEnvelope(event));
}, onError: (dynamic error) {
......@@ -146,7 +148,7 @@ class _EventChannelHandler<T> {
message: 'No active subscription to cancel.',
);
}
await subscription.cancel();
await subscription!.cancel();
subscription = null;
return codec.encodeSuccessEnvelope(null);
}
......
......@@ -2,15 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
typedef _MessageHandler = Future<ByteData> Function(ByteData);
// TODO(hterkelsen): Why is this _MessageHandler duplicated here?
typedef _MessageHandler = Future<ByteData?>? Function(ByteData?);
/// This class registers web platform plugins.
///
......@@ -97,12 +96,12 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
@override
Future<void> handlePlatformMessage(
String channel,
ByteData data,
ui.PlatformMessageResponseCallback callback,
ByteData? data,
ui.PlatformMessageResponseCallback? callback,
) async {
ByteData response;
ByteData? response;
try {
final MessageHandler handler = _handlers[channel];
final MessageHandler? handler = _handlers[channel];
if (handler != null) {
response = await handler(data);
}
......@@ -122,9 +121,9 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
/// Sends a platform message from the platform side back to the framework.
@override
Future<ByteData> send(String channel, ByteData message) {
Future<ByteData> send(String channel, ByteData? message) {
final Completer<ByteData> completer = Completer<ByteData>();
ui.window.onPlatformMessage(channel, message, (ByteData reply) {
ui.window.onPlatformMessage!(channel, message, (ByteData? reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
......@@ -140,7 +139,7 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
}
@override
void setMessageHandler(String channel, MessageHandler handler) {
void setMessageHandler(String channel, MessageHandler? handler) {
if (handler == null)
_handlers.remove(channel);
else
......@@ -148,12 +147,12 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
}
@override
bool checkMessageHandler(String channel, MessageHandler handler) => _handlers[channel] == handler;
bool checkMessageHandler(String channel, MessageHandler? handler) => _handlers[channel] == handler;
@override
void setMockMessageHandler(
String channel,
Future<ByteData> Function(ByteData message) handler,
MessageHandler? handler,
) {
throw FlutterError(
'Setting mock handlers is not supported on the platform side.',
......@@ -161,7 +160,7 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
}
@override
bool checkMockMessageHandler(String channel, MessageHandler handler) {
bool checkMockMessageHandler(String channel, MessageHandler? handler) {
throw FlutterError(
'Setting mock handlers is not supported on the platform side.',
);
......
......@@ -4,7 +4,7 @@ author: Flutter Authors <flutter-dev@googlegroups.com>
homepage: http://flutter.dev
environment:
sdk: ">=2.0.0-dev.28.0 <3.0.0"
sdk: ">=2.12.0-0 <3.0.0"
dependencies:
flutter:
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:html';
@TestOn('chrome') // Uses web-only Flutter SDK
......@@ -13,16 +11,12 @@ import 'package:flutter_web_plugins/flutter_web_plugins.dart';
void main() {
group('$HashUrlStrategy', () {
TestPlatformLocation location;
late TestPlatformLocation location;
setUp(() {
location = TestPlatformLocation();
});
tearDown(() {
location = null;
});
test('leading slash is optional', () {
final HashUrlStrategy strategy = HashUrlStrategy(location);
......@@ -48,16 +42,12 @@ void main() {
});
group('$PathUrlStrategy', () {
TestPlatformLocation location;
late TestPlatformLocation location;
setUp(() {
location = TestPlatformLocation();
});
tearDown(() {
location = null;
});
test('validates base href', () {
location.baseHref = '/';
expect(
......@@ -153,7 +143,7 @@ class TestPlatformLocation extends PlatformLocation {
String hash = '';
@override
dynamic state;
Object? Function() state = () => null;
/// Mocks the base href of the document.
String baseHref = '';
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
@TestOn('browser') // Uses web-only Flutter SDK
import 'package:flutter_test/flutter_test.dart';
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
@TestOn('chrome') // Uses web-only Flutter SDK
import 'dart:async';
......@@ -29,7 +27,7 @@ void main() {
PluginEventChannel<String>('test');
final StreamController<String> controller = StreamController<String>();
sendingChannel.controller = controller;
sendingChannel.setController(controller);
expect(listeningChannel.receiveBroadcastStream(),
emitsInOrder(<String>['hello', 'world']));
......@@ -61,7 +59,7 @@ void main() {
PluginEventChannel<String>('test2');
final StreamController<String> controller = StreamController<String>();
sendingChannel.controller = controller;
sendingChannel.setController(controller);
expect(
listeningChannel.receiveBroadcastStream(),
......@@ -96,7 +94,7 @@ void main() {
final StreamController<String> controller = StreamController<String>(
onListen: expectAsync0<void>(() {}, count: 1));
sendingChannel.controller = controller;
sendingChannel.setController(controller);
expect(listeningChannel.receiveBroadcastStream(),
emitsInOrder(<String>['hello']));
......@@ -128,11 +126,11 @@ void main() {
final StreamController<String> controller =
StreamController<String>(onCancel: expectAsync0<void>(() {}));
sendingChannel.controller = controller;
sendingChannel.setController(controller);
final Stream<dynamic> eventStream =
listeningChannel.receiveBroadcastStream();
StreamSubscription<dynamic> subscription;
late StreamSubscription<dynamic> subscription;
subscription =
eventStream.listen(expectAsync1<void, dynamic>((dynamic x) {
expect(x, equals('hello'));
......@@ -153,7 +151,7 @@ void main() {
final Stream<dynamic> eventStream =
listeningChannel.receiveBroadcastStream();
StreamSubscription<dynamic> subscription;
late StreamSubscription<dynamic> subscription;
subscription =
eventStream.listen(expectAsync1<void, dynamic>((dynamic x) {
expect(x, equals('hello'));
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
@TestOn('chrome') // Uses web-only Flutter SDK
import 'dart:ui' as ui; // ignore: unused_import, it looks unused as web-only elements are the only elements used.
......@@ -56,8 +54,8 @@ void main() {
const StandardMessageCodec codec = StandardMessageCodec();
final List<String> loggedMessages = <String>[];
ServicesBinding.instance.defaultBinaryMessenger
.setMessageHandler('test_send', (ByteData data) {
ServicesBinding.instance!.defaultBinaryMessenger
.setMessageHandler('test_send', (ByteData? data) {
loggedMessages.add(codec.decodeMessage(data) as String);
return null;
});
......@@ -70,14 +68,14 @@ void main() {
'test_send', codec.encodeMessage('world'));
expect(loggedMessages, equals(<String>['hello', 'world']));
ServicesBinding.instance.defaultBinaryMessenger
ServicesBinding.instance!.defaultBinaryMessenger
.setMessageHandler('test_send', null);
});
test('throws when trying to set a mock handler', () {
expect(
() => pluginBinaryMessenger.setMockMessageHandler(
'test', (ByteData data) async => ByteData(0)),
'test', (ByteData? data) async => ByteData(0)),
throwsFlutterError);
});
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment