Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ vars = {
# Dart is: https://github.com/dart-lang/sdk/blob/master/DEPS.
# You can use //tools/dart/create_updated_flutter_deps.py to produce
# updated revision list of existing dependencies.
'dart_revision': '9a1827339c8e4e55a9b214d033aeba7d30daa28f',
'dart_revision': 'aa7d19d185583b221093ad7385cf91fca3e8779c',

# WARNING: DO NOT EDIT MANUALLY
# The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py
Expand Down
2 changes: 1 addition & 1 deletion ci/licenses_golden/licenses_third_party
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Signature: 762dc8deadc282521ea04cfc1f0973e9
Signature: 988f1584a445473114bc2516f591f26b

UNUSED LICENSES:

Expand Down
18 changes: 16 additions & 2 deletions lib/web_ui/lib/src/engine/navigation/history.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ import '../services/message_codec.dart';
import '../services/message_codecs.dart';
import 'url_strategy.dart';

/// Infers the history mode from the existing browser history state, then
/// creates the appropriate instance of [BrowserHistory] for it.
///
/// If it can't infer, it creates a [MultiEntriesBrowserHistory] by default.
BrowserHistory createHistoryForExistingState(UrlStrategy? urlStrategy) {
if (urlStrategy != null) {
final Object? state = urlStrategy.getState();
if (SingleEntryBrowserHistory._isOriginEntry(state) || SingleEntryBrowserHistory._isFlutterEntry(state)) {
return SingleEntryBrowserHistory(urlStrategy: urlStrategy);
}
}
return MultiEntriesBrowserHistory(urlStrategy: urlStrategy);
}

/// An abstract class that provides the API for [EngineWindow] to delegate its
/// navigating events.
///
Expand Down Expand Up @@ -263,14 +277,14 @@ class SingleEntryBrowserHistory extends BrowserHistory {

/// The origin entry is the history entry that the Flutter app landed on. It's
/// created by the browser when the user navigates to the url of the app.
bool _isOriginEntry(Object? state) {
static bool _isOriginEntry(Object? state) {
return state is Map && state[_kOriginTag] == true;
}

/// The flutter entry is a history entry that we maintain on top of the origin
/// entry. It allows us to catch popstate events when the user hits the back
/// button.
bool _isFlutterEntry(Object? state) {
static bool _isFlutterEntry(Object? state) {
return state is Map && state[_kFlutterTag] == true;
}

Expand Down
55 changes: 34 additions & 21 deletions lib/web_ui/lib/src/engine/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import 'package:js/js.dart';
import 'package:meta/meta.dart';
import 'package:ui/ui.dart' as ui;

import '../engine.dart' show registerHotRestartListener;
import 'browser_detection.dart';
import 'navigation/history.dart';
import 'navigation/js_url_strategy.dart';
Expand Down Expand Up @@ -49,12 +48,8 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
engineDispatcher.windows[_windowId] = this;
engineDispatcher.windowConfigurations[_windowId] = const ui.ViewConfiguration();
if (_isUrlStrategySet) {
_browserHistory =
MultiEntriesBrowserHistory(urlStrategy: _customUrlStrategy);
_browserHistory = createHistoryForExistingState(_customUrlStrategy);
}
registerHotRestartListener(() {
window.resetHistory();
});
}

final Object _windowId;
Expand All @@ -66,7 +61,7 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
/// button, etc.
BrowserHistory get browserHistory {
return _browserHistory ??=
MultiEntriesBrowserHistory(urlStrategy: _urlStrategyForInitialization);
createHistoryForExistingState(_urlStrategyForInitialization);
}

UrlStrategy? get _urlStrategyForInitialization {
Expand All @@ -81,32 +76,50 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
_browserHistory; // Must be either SingleEntryBrowserHistory or MultiEntriesBrowserHistory.

Future<void> _useSingleEntryBrowserHistory() async {
// Recreate the browser history mode that's appropriate for the existing
// history state.
//
// If it happens to be a single-entry one, then there's nothing further to do.
//
// But if it's a multi-entry one, it will be torn down below and replaced
// with a single-entry history.
//
// See: https://github.com/flutter/flutter/issues/79241
_browserHistory ??=
createHistoryForExistingState(_urlStrategyForInitialization);

if (_browserHistory is SingleEntryBrowserHistory) {
return;
}

final UrlStrategy? strategy;
if (_browserHistory == null) {
strategy = _urlStrategyForInitialization;
} else {
strategy = _browserHistory?.urlStrategy;
await _browserHistory?.tearDown();
}
// At this point, we know that `_browserHistory` is a non-null
// `MultiEntriesBrowserHistory` instance.
final UrlStrategy? strategy = _browserHistory?.urlStrategy;
await _browserHistory?.tearDown();
_browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy);
}

Future<void> _useMultiEntryBrowserHistory() async {
// Recreate the browser history mode that's appropriate for the existing
// history state.
//
// If it happens to be a multi-entry one, then there's nothing further to do.
//
// But if it's a single-entry one, it will be torn down below and replaced
// with a multi-entry history.
//
// See: https://github.com/flutter/flutter/issues/79241
_browserHistory ??=
createHistoryForExistingState(_urlStrategyForInitialization);

if (_browserHistory is MultiEntriesBrowserHistory) {
return;
}

final UrlStrategy? strategy;
if (_browserHistory == null) {
strategy = _urlStrategyForInitialization;
} else {
strategy = _browserHistory?.urlStrategy;
await _browserHistory?.tearDown();
}
// At this point, we know that `_browserHistory` is a non-null
// `SingleEntryBrowserHistory` instance.
final UrlStrategy? strategy = _browserHistory?.urlStrategy;
await _browserHistory?.tearDown();
_browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy);
}

Expand Down
44 changes: 44 additions & 0 deletions lib/web_ui/test/engine/history_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,50 @@ void main() {
}

void testMain() {
test('createHistoryForExistingState', () {
TestUrlStrategy strategy;
BrowserHistory history;

// No url strategy.
history = createHistoryForExistingState(null);
expect(history, isA<MultiEntriesBrowserHistory>());
expect(history.urlStrategy, isNull);

// Random history state.
strategy = TestUrlStrategy.fromEntry(
const TestHistoryEntry(<dynamic, dynamic>{'foo': 123}, null, '/'),
);
history = createHistoryForExistingState(strategy);
expect(history, isA<MultiEntriesBrowserHistory>());
expect(history.urlStrategy, strategy);

// Multi-entry history state.
final Map<dynamic, dynamic> state = <dynamic, dynamic>{
'serialCount': 1,
'state': <dynamic, dynamic>{'foo': 123},
};
strategy = TestUrlStrategy.fromEntry(TestHistoryEntry(state, null, '/'));
history = createHistoryForExistingState(strategy);
expect(history, isA<MultiEntriesBrowserHistory>());
expect(history.urlStrategy, strategy);

// Single-entry history "origin" state.
strategy = TestUrlStrategy.fromEntry(
const TestHistoryEntry(<dynamic, dynamic>{'origin': true}, null, '/'),
);
history = createHistoryForExistingState(strategy);
expect(history, isA<SingleEntryBrowserHistory>());
expect(history.urlStrategy, strategy);

// Single-entry history "flutter" state.
strategy = TestUrlStrategy.fromEntry(
const TestHistoryEntry(<dynamic, dynamic>{'flutter': true}, null, '/'),
);
history = createHistoryForExistingState(strategy);
expect(history, isA<SingleEntryBrowserHistory>());
expect(history.urlStrategy, strategy);
});

group('$SingleEntryBrowserHistory', () {
final PlatformMessagesSpy spy = PlatformMessagesSpy();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,15 +374,17 @@ private void setSystemChromeSystemUIOverlayStyle(
// If transparent, SDK 29 and higher may apply a translucent scrim behind the bar to ensure
// proper contrast. This can be overridden with
// SystemChromeStyle.systemStatusBarContrastEnforced.
if (systemChromeStyle.statusBarIconBrightness != null && Build.VERSION.SDK_INT >= 23) {
switch (systemChromeStyle.statusBarIconBrightness) {
case DARK:
// View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
flags |= 0x2000;
break;
case LIGHT:
flags &= ~0x2000;
break;
if (Build.VERSION.SDK_INT >= 23) {
if (systemChromeStyle.statusBarIconBrightness != null) {
switch (systemChromeStyle.statusBarIconBrightness) {
case DARK:
// View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
flags |= 0x2000;
break;
case LIGHT:
flags &= ~0x2000;
break;
}
}

if (systemChromeStyle.statusBarColor != null) {
Expand All @@ -403,16 +405,17 @@ private void setSystemChromeSystemUIOverlayStyle(
// If transparent, SDK 29 and higher may apply a translucent scrim behind 2/3 button navigation
// bars to ensure proper contrast. This can be overridden with
// SystemChromeStyle.systemNavigationBarContrastEnforced.
if (systemChromeStyle.systemNavigationBarIconBrightness != null
&& Build.VERSION.SDK_INT >= 26) {
switch (systemChromeStyle.systemNavigationBarIconBrightness) {
case DARK:
// View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
flags |= 0x10;
break;
case LIGHT:
flags &= ~0x10;
break;
if (Build.VERSION.SDK_INT >= 26) {
if (systemChromeStyle.systemNavigationBarIconBrightness != null) {
switch (systemChromeStyle.systemNavigationBarIconBrightness) {
case DARK:
// View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
flags |= 0x10;
break;
case LIGHT:
flags &= ~0x10;
break;
}
}

if (systemChromeStyle.systemNavigationBarColor != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,41 @@ public void setNavigationBarDividerColor() {
when(fakeActivity.getWindow()).thenReturn(fakeWindow);
PlatformChannel fakePlatformChannel = mock(PlatformChannel.class);
PlatformPlugin platformPlugin = new PlatformPlugin(fakeActivity, fakePlatformChannel);
// Default style test
SystemChromeStyle style =
new SystemChromeStyle(0XFF000000, null, true, 0XFFC70039, null, 0XFF006DB3, true);
new SystemChromeStyle(
0XFF000000, // statusBarColor
null, // statusBarIconBrightness
true, // systemStatusBarContrastEnforced
0XFFC70039, // systemNavigationBarColor
null, // systemNavigationBarIconBrightness
0XFF006DB3, // systemNavigationBarDividerColor
true); // systemNavigationBarContrastEnforced

if (Build.VERSION.SDK_INT >= 28) {
platformPlugin.mPlatformMessageHandler.setSystemUiOverlayStyle(style);

assertEquals(0XFF000000, fakeActivity.getWindow().getStatusBarColor());
assertEquals(0XFFC70039, fakeActivity.getWindow().getNavigationBarColor());
assertEquals(0XFF006DB3, fakeActivity.getWindow().getNavigationBarDividerColor());

// Regression test for https://github.com/flutter/flutter/issues/88431
// A null brightness should not affect changing color settings.
style =
new SystemChromeStyle(
0XFF006DB3, // statusBarColor
null, // statusBarIconBrightness
true, // systemStatusBarContrastEnforced
0XFF000000, // systemNavigationBarColor
null, // systemNavigationBarIconBrightness
0XFF006DB3, // systemNavigationBarDividerColor
true); // systemNavigationBarContrastEnforced

platformPlugin.mPlatformMessageHandler.setSystemUiOverlayStyle(style);

assertEquals(0XFFC70039, fakeActivity.getWindow().getStatusBarColor());
assertEquals(0XFF000000, fakeActivity.getWindow().getNavigationBarColor());
assertEquals(0XFF006DB3, fakeActivity.getWindow().getNavigationBarDividerColor());
}
}

Expand Down
8 changes: 6 additions & 2 deletions shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ CGPoint ConvertPointToGlobal(SemanticsObject* reference, CGPoint local_point) {
// `rect` is in the physical pixel coordinate system. iOS expects the accessibility frame in
// the logical pixel coordinate system. Therefore, we divide by the `scale` (pixel ratio) to
// convert.
CGFloat scale = [[[reference bridge]->view() window] screen].scale;
UIScreen* screen = [[[reference bridge]->view() window] screen];
// Screen can be nil if the FlutterView is covered by another native view.
CGFloat scale = screen == nil ? [UIScreen mainScreen].scale : screen.scale;
auto result = CGPointMake(point.x() / scale, point.y() / scale);
return [[reference bridge]->view() convertPoint:result toView:nil];
}
Expand All @@ -80,7 +82,9 @@ CGRect ConvertRectToGlobal(SemanticsObject* reference, CGRect local_rect) {
// `rect` is in the physical pixel coordinate system. iOS expects the accessibility frame in
// the logical pixel coordinate system. Therefore, we divide by the `scale` (pixel ratio) to
// convert.
CGFloat scale = [[[reference bridge]->view() window] screen].scale;
UIScreen* screen = [[[reference bridge]->view() window] screen];
// Screen can be nil if the FlutterView is covered by another native view.
CGFloat scale = screen == nil ? [UIScreen mainScreen].scale : screen.scale;
auto result =
CGRectMake(rect.x() / scale, rect.y() / scale, rect.width() / scale, rect.height() / scale);
return UIAccessibilityConvertFrameToScreenCoordinates(result, [reference bridge]->view());
Expand Down
Loading