[web] fix screen orientation lock bug (flutter/engine#36253)

This commit is contained in:
Yegor 2022-09-19 13:14:16 -07:00 committed by GitHub
parent 9dffe4a887
commit f72b62d672
6 changed files with 121 additions and 32 deletions

View File

@ -7,7 +7,6 @@ import 'package:ui/ui.dart' as ui;
import 'browser_detection.dart';
import 'dom.dart';
import 'services.dart';
import 'util.dart';
/// Handles clipboard related platform messages.
class ClipboardMessageHandler {
@ -89,7 +88,7 @@ class ClipboardMessageHandler {
/// APIs and the browser.
abstract class CopyToClipboardStrategy {
factory CopyToClipboardStrategy() {
return !unsafeIsNull(domWindow.navigator.clipboard)
return domWindow.navigator.clipboard != null
? ClipboardAPICopyStrategy()
: ExecCommandCopyStrategy();
}
@ -109,7 +108,7 @@ abstract class CopyToClipboardStrategy {
abstract class PasteFromClipboardStrategy {
factory PasteFromClipboardStrategy() {
return (browserEngine == BrowserEngine.firefox ||
unsafeIsNull(domWindow.navigator.clipboard))
domWindow.navigator.clipboard == null)
? ExecCommandPasteStrategy()
: ClipboardAPIPasteStrategy();
}

View File

@ -1298,8 +1298,10 @@ extension DomScreenExtension on DomScreen {
class DomScreenOrientation extends DomEventTarget {}
extension DomScreenOrientationExtension on DomScreenOrientation {
Future<dynamic> lock(String orientation) => js_util
.promiseToFuture(js_util.callMethod(this, 'lock', <String>[orientation]));
Future<dynamic> lock(String orientation) {
final Object jsResult = js_util.callMethod<Object>(this, 'lock', <String>[orientation]);
return js_util.promiseToFuture(jsResult);
}
external void unlock();
}

View File

@ -437,12 +437,12 @@ class FlutterViewEmbedder {
///
/// See w3c screen api: https://www.w3.org/TR/screen-orientation/
Future<bool> setPreferredOrientation(List<dynamic> orientations) {
final DomScreen screen = domWindow.screen!;
if (!unsafeIsNull(screen)) {
final DomScreen? screen = domWindow.screen;
if (screen != null) {
final DomScreenOrientation? screenOrientation = screen.orientation;
if (!unsafeIsNull(screenOrientation)) {
if (screenOrientation != null) {
if (orientations.isEmpty) {
screenOrientation!.unlock();
screenOrientation.unlock();
return Future<bool>.value(true);
} else {
final String? lockType =
@ -450,7 +450,7 @@ class FlutterViewEmbedder {
if (lockType != null) {
final Completer<bool> completer = Completer<bool>();
try {
screenOrientation!.lock(lockType).then((dynamic _) {
screenOrientation.lock(lockType).then((dynamic _) {
completer.complete(true);
}).catchError((dynamic error) {
// On Chrome desktop an error with 'not supported on this device
@ -470,13 +470,15 @@ class FlutterViewEmbedder {
}
// Converts device orientation to w3c OrientationLockType enum.
//
// See also: https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation/lock
static String? _deviceOrientationToLockType(String? deviceOrientation) {
switch (deviceOrientation) {
case 'DeviceOrientation.portraitUp':
return orientationLockTypePortraitPrimary;
case 'DeviceOrientation.landscapeLeft':
return orientationLockTypePortraitSecondary;
case 'DeviceOrientation.portraitDown':
return orientationLockTypePortraitSecondary;
case 'DeviceOrientation.landscapeLeft':
return orientationLockTypeLandscapePrimary;
case 'DeviceOrientation.landscapeRight':
return orientationLockTypeLandscapeSecondary;

View File

@ -242,15 +242,19 @@ void debugResetBrowserSupportsImageDecoder() {
_imageDecoderConstructor != null;
}
/// The signature of the function passed to the constructor of JavaScript `Promise`.
typedef JsPromiseCallback = void Function(void Function(Object? value) resolve, void Function(Object? error) reject);
/// Corresponds to JavaScript's `Promise`.
///
/// This type doesn't need any members. Instead, it should be first converted
/// to Dart's [Future] using [promiseToFuture] then interacted with through the
/// [Future] API.
@JS()
@anonymous
@JS('window.Promise')
@staticInterop
class JsPromise {}
class JsPromise {
external factory JsPromise(JsPromiseCallback callback);
}
/// Corresponds to the browser's `ImageDecoder` type.
///

View File

@ -552,11 +552,6 @@ String blurSigmasToCssString(double sigmaX, double sigmaY) {
return 'blur(${(sigmaX + sigmaY) * 0.5}px)';
}
/// Checks if the dynamic [object] is equal to null.
bool unsafeIsNull(dynamic object) {
return object == null;
}
/// A typed variant of [domWindow.fetch].
Future<DomResponse> httpFetch(String url) async {
final Object? result = await domWindow.fetch(url);

View File

@ -299,25 +299,112 @@ Future<void> testMain() async {
expect(responded, isTrue);
});
/// Regression test for https://github.com/flutter/flutter/issues/66128.
test("setPreferredOrientation responds even if browser doesn't support api", () async {
final DomScreen screen = domWindow.screen!;
js_util.setProperty(screen, 'orientation', null);
// Emulates the framework sending a request for screen orientation lock.
Future<bool> sendSetPreferredOrientations(List<dynamic> orientations) {
final Completer<bool> completer = Completer<bool>();
final ByteData inputData = const JSONMethodCodec().encodeMethodCall(const MethodCall(
'SystemChrome.setPreferredOrientations',
<dynamic>[]))!;
final ByteData? inputData = const JSONMethodCodec().encodeMethodCall(MethodCall(
'SystemChrome.setPreferredOrientations',
orientations,
));
window.sendPlatformMessage(
'flutter/platform',
inputData,
(ByteData? outputData) {
completer.complete(true);
inputData,
(ByteData? outputData) {
const MethodCodec codec = JSONMethodCodec();
completer.complete(codec.decodeEnvelope(outputData!) as bool);
},
);
expect(await completer.future, isTrue);
return completer.future;
}
// Regression test for https://github.com/flutter/flutter/issues/88269
test('sets preferred screen orientation', () async {
final DomScreen original = domWindow.screen!;
final List<String> lockCalls = <String>[];
int unlockCount = 0;
bool simulateError = false;
// The `orientation` property cannot be overridden, so this test overrides the entire `screen`.
js_util.setProperty(domWindow, 'screen', js_util.jsify(<Object?, Object?>{
'orientation': <Object?, Object?>{
'lock': allowInterop((String lockType) {
lockCalls.add(lockType);
return JsPromise(allowInterop((Function(Object? value) resolve, Function reject) {
if (!simulateError) {
resolve(null);
} else {
reject('Simulating error');
}
}));
}),
'unlock': allowInterop(() {
unlockCount += 1;
}),
},
}));
// Sanity-check the test setup.
expect(lockCalls, <String>[]);
expect(unlockCount, 0);
await domWindow.screen!.orientation!.lock('hi');
domWindow.screen!.orientation!.unlock();
expect(lockCalls, <String>['hi']);
expect(unlockCount, 1);
lockCalls.clear();
unlockCount = 0;
expect(await sendSetPreferredOrientations(<dynamic>['DeviceOrientation.portraitUp']), isTrue);
expect(lockCalls, <String>[FlutterViewEmbedder.orientationLockTypePortraitPrimary]);
expect(unlockCount, 0);
lockCalls.clear();
unlockCount = 0;
expect(await sendSetPreferredOrientations(<dynamic>['DeviceOrientation.portraitDown']), isTrue);
expect(lockCalls, <String>[FlutterViewEmbedder.orientationLockTypePortraitSecondary]);
expect(unlockCount, 0);
lockCalls.clear();
unlockCount = 0;
expect(await sendSetPreferredOrientations(<dynamic>['DeviceOrientation.landscapeLeft']), isTrue);
expect(lockCalls, <String>[FlutterViewEmbedder.orientationLockTypeLandscapePrimary]);
expect(unlockCount, 0);
lockCalls.clear();
unlockCount = 0;
expect(await sendSetPreferredOrientations(<dynamic>['DeviceOrientation.landscapeRight']), isTrue);
expect(lockCalls, <String>[FlutterViewEmbedder.orientationLockTypeLandscapeSecondary]);
expect(unlockCount, 0);
lockCalls.clear();
unlockCount = 0;
expect(await sendSetPreferredOrientations(<dynamic>[]), isTrue);
expect(lockCalls, <String>[]);
expect(unlockCount, 1);
lockCalls.clear();
unlockCount = 0;
simulateError = true;
expect(await sendSetPreferredOrientations(<dynamic>['DeviceOrientation.portraitDown']), isFalse);
expect(lockCalls, <String>[FlutterViewEmbedder.orientationLockTypePortraitSecondary]);
expect(unlockCount, 0);
js_util.setProperty(domWindow, 'screen', original);
});
/// Regression test for https://github.com/flutter/flutter/issues/66128.
test("setPreferredOrientation responds even if browser doesn't support api", () async {
final DomScreen original = domWindow.screen!;
// The `orientation` property cannot be overridden, so this test overrides the entire `screen`.
js_util.setProperty(domWindow, 'screen', js_util.jsify(<Object?, Object?>{
'orientation': null,
}));
expect(domWindow.screen!.orientation, isNull);
expect(await sendSetPreferredOrientations(<dynamic>[]), isFalse);
js_util.setProperty(domWindow, 'screen', original);
});
test('SingletonFlutterWindow implements locale, locales, and locale change notifications', () async {