Skip to content

Commit

Permalink
[web] Use platform detection from Flutter web engine. (#147346)
Browse files Browse the repository at this point in the history
> [!IMPORTANT]
> Requires the following engine PR:
> * flutter/engine#52380
> ----

This PR refactors Flutter `foundation`'s library `platform` for the web with the same code we use to detect platforms in the engine.

## Issues

* Fixes: #128943

## Testing

Demo app deployed here:

* https://dit-browser-detect.web.app
  • Loading branch information
ditman committed May 7, 2024
1 parent f20c853 commit d7656f2
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 37 deletions.
72 changes: 35 additions & 37 deletions packages/flutter/lib/src/foundation/_platform_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import 'dart:ui_web' as ui_web;

import '../web.dart' as web;
import 'platform.dart' as platform;

export 'platform.dart' show TargetPlatform;
Expand All @@ -19,46 +18,45 @@ platform.TargetPlatform get defaultTargetPlatform {
_browserPlatform;
}

final platform.TargetPlatform? _testPlatform = () {
platform.TargetPlatform? result;
// The TargetPlatform used on Web tests, unless overridden.
//
// Respects the `ui_web.browser.debugOperatingSystemOverride` value (when set).
platform.TargetPlatform? get _testPlatform {
platform.TargetPlatform? testPlatform;
assert(() {
if (ui_web.debugEmulateFlutterTesterEnvironment) {
result = platform.TargetPlatform.android;
// Return the overridden operatingSystem in tests, if any...
if (ui_web.browser.debugOperatingSystemOverride != null) {
testPlatform =
_operatingSystemToTargetPlatform(ui_web.browser.operatingSystem);
} else {
// Fall back to `android` for tests.
testPlatform = platform.TargetPlatform.android;
}
}
return true;
}());
return result;
}();
return testPlatform;
}

// Lazy-initialized and forever cached current browser platform.
// Current browser platform.
//
// The computation of `operatingSystem` is cached in the ui_web package;
// this getter may be called dozens of times per frame.
//
// Computing the platform is expensive as it uses `window.matchMedia`, which
// needs to parse and evaluate a CSS selector. On some devices this takes up to
// 0.20ms. As `defaultTargetPlatform` is routinely called dozens of times per
// frame this value should be cached.
final platform.TargetPlatform _browserPlatform = () {
final String navigatorPlatform = web.window.navigator.platform.toLowerCase();
if (navigatorPlatform.startsWith('mac')) {
return platform.TargetPlatform.macOS;
}
if (navigatorPlatform.startsWith('win')) {
return platform.TargetPlatform.windows;
}
if (navigatorPlatform.contains('iphone') ||
navigatorPlatform.contains('ipad') ||
navigatorPlatform.contains('ipod')) {
return platform.TargetPlatform.iOS;
}
if (navigatorPlatform.contains('android')) {
return platform.TargetPlatform.android;
}
// Since some phones can report a window.navigator.platform as Linux, fall
// back to use CSS to disambiguate Android vs Linux desktop. If the CSS
// indicates that a device has a "fine pointer" (mouse) as the primary
// pointing device, then we'll assume desktop linux, and otherwise we'll
// assume Android.
if (web.window.matchMedia('only screen and (pointer: fine)').matches) {
return platform.TargetPlatform.linux;
}
return platform.TargetPlatform.android;
}();
// _browserPlatform is lazily initialized, and cached forever.
final platform.TargetPlatform _browserPlatform =
_operatingSystemToTargetPlatform(ui_web.browser.operatingSystem);

// Converts an ui_web.OperatingSystem enum into a platform.TargetPlatform.
platform.TargetPlatform _operatingSystemToTargetPlatform(ui_web.OperatingSystem os) {
return switch (os) {
ui_web.OperatingSystem.android => platform.TargetPlatform.android,
ui_web.OperatingSystem.iOs => platform.TargetPlatform.iOS,
ui_web.OperatingSystem.linux => platform.TargetPlatform.linux,
ui_web.OperatingSystem.macOs => platform.TargetPlatform.macOS,
ui_web.OperatingSystem.windows => platform.TargetPlatform.windows,
// Resolve 'unknown' OS values to `android`.
ui_web.OperatingSystem.unknown => platform.TargetPlatform.android,
};
}
33 changes: 33 additions & 0 deletions packages/flutter/test/foundation/platform_web_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@TestOn('chrome')
library;

import 'dart:ui_web' as ui_web;

import 'package:flutter/foundation.dart' show TargetPlatform, defaultTargetPlatform;
import 'package:flutter_test/flutter_test.dart';

void main() {
tearDown(() {
// Remove the `debugOperatingSystemOverride`.
ui_web.browser.debugOperatingSystemOverride = null;
});

group('defaultTargetPlatform', () {
testWidgets('returns what ui_web says', (WidgetTester _) async {
// Set the OS reported by web_ui to anything that is not linux.
ui_web.browser.debugOperatingSystemOverride = ui_web.OperatingSystem.iOs;

expect(defaultTargetPlatform, TargetPlatform.iOS);
});

testWidgets('defaults `unknown` to android', (WidgetTester _) async {
ui_web.browser.debugOperatingSystemOverride = ui_web.OperatingSystem.unknown;

expect(defaultTargetPlatform, TargetPlatform.android);
});
});
}

0 comments on commit d7656f2

Please sign in to comment.