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
45 changes: 43 additions & 2 deletions lib/web_ui/lib/src/engine/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:html' as html;
import 'dart:js_util' as js_util;
import 'dart:typed_data';

import 'package:meta/meta.dart';
Expand Down Expand Up @@ -53,8 +54,10 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
/// The current platform configuration.
@override
ui.PlatformConfiguration get configuration => _configuration;
ui.PlatformConfiguration _configuration =
ui.PlatformConfiguration(locales: parseBrowserLanguages());
ui.PlatformConfiguration _configuration = ui.PlatformConfiguration(
locales: parseBrowserLanguages(),
textScaleFactor: findBrowserTextScaleFactor(),
);

/// Receives all events related to platform configuration changes.
@override
Expand Down Expand Up @@ -1076,3 +1079,41 @@ void invoke3<A1, A2, A3>(void Function(A1 a1, A2 a2, A3 a3)? callback,
});
}
}

const double _defaultRootFontSize = 16.0;

/// Finds the text scale factor of the browser by looking at the computed style
/// of the browser's <html> element.
double findBrowserTextScaleFactor() {
final num fontSize = _parseFontSize(html.document.documentElement!) ?? _defaultRootFontSize;
return fontSize / _defaultRootFontSize;
}

/// Parses the font size of [element] and returns the value without a unit.
num? _parseFontSize(html.Element element) {
num? fontSize;

if (js_util.hasProperty(element, 'computedStyleMap')) {
// Use the newer `computedStyleMap` API available on some browsers.
final dynamic computedStyleMap =
// ignore: implicit_dynamic_function
js_util.callMethod(element, 'computedStyleMap', <Object?>[]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tried it. It throws an exception saying that window.getComputedStyleMap is undefined. I also looked online and couldn't find this property on window. The only one that exists is window.getComputedStyle which I'm already using below through dart:html.

if (computedStyleMap is Object) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is is Object a null check? If so, why not != null?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a null check PLUS a type check/inference so when I pass it to js_util.callMethod I don't need to do as Object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange that != null doesn't promote to Object automatically. Generics allow you to declare non-nullness by doing <T extends Object>, so I'd expect != null to also be equivalent to is Object. But oh well.

final dynamic fontSizeObject =
// ignore: implicit_dynamic_function
js_util.callMethod(computedStyleMap, 'get', <Object?>['font-size']);
if (fontSizeObject is Object) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

// ignore: implicit_dynamic_function
fontSize = js_util.getProperty(fontSizeObject, 'value') as num;
}
}
}

if (fontSize == null) {
// Fallback to `getComputedStyle`.
final String fontSizeString = element.getComputedStyle().fontSize;
fontSize = parseFloat(fontSizeString);
}

return fontSize;
}
26 changes: 26 additions & 0 deletions lib/web_ui/lib/src/engine/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@JS()
library util;

import 'dart:async';
import 'dart:html' as html;
import 'dart:js_util' as js_util;
import 'dart:math' as math;
import 'dart:typed_data';

import 'package:js/js.dart';
import 'package:ui/ui.dart' as ui;

import 'browser_detection.dart';
Expand Down Expand Up @@ -653,3 +657,25 @@ extension JsonExtensions on Map<dynamic, dynamic> {
return this[propertyName] as double?;
}
}

typedef JsParseFloat = num? Function(String source);

@JS('parseFloat')
external JsParseFloat get _jsParseFloat;

/// Parses a string [source] into a double.
///
/// Uses the JavaScript `parseFloat` function instead of Dart's [double.parse]
/// because the latter can't parse strings like "20px".
///
/// Returns null if it fails to parse.
num? parseFloat(String source) {
// Using JavaScript's `parseFloat` here because it can parse values
// like "20px", while Dart's `double.tryParse` fails.
final num? result = _jsParseFloat(source);

if (result == null || result.isNaN) {
return null;
}
return result;
}
29 changes: 29 additions & 0 deletions lib/web_ui/test/engine/platform_dispatcher_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,34 @@ void testMain() {
js_util.setProperty(
html.window.navigator, 'clipboard', originalClipboard);
});

test('can find text scale factor', () async {
const double deltaTolerance = 1e-5;

final html.Element root = html.document.documentElement!;
final String oldFontSize = root.style.fontSize;

addTearDown(() {
root.style.fontSize = oldFontSize;
});

root.style.fontSize = '16px';
expect(findBrowserTextScaleFactor(), 1.0);

root.style.fontSize = '20px';
expect(findBrowserTextScaleFactor(), 1.25);

root.style.fontSize = '24px';
expect(findBrowserTextScaleFactor(), 1.5);

root.style.fontSize = '14.4px';
expect(findBrowserTextScaleFactor(), closeTo(0.9, deltaTolerance));

root.style.fontSize = '12.8px';
expect(findBrowserTextScaleFactor(), closeTo(0.8, deltaTolerance));

root.style.fontSize = null;
expect(findBrowserTextScaleFactor(), 1.0);
});
});
}
22 changes: 22 additions & 0 deletions lib/web_ui/test/engine/util_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,26 @@ void testMain() {
debugOperatingSystemOverride = null;
debugIsIOS15 = null;
});

test('parseFloat basic tests', () {
// Simple integers and doubles.
expect(parseFloat('108'), 108.0);
expect(parseFloat('.34'), 0.34);
expect(parseFloat('108.34'), 108.34);

// Number followed by text.
expect(parseFloat('108.34px'), 108.34);
expect(parseFloat('108.34px29'), 108.34);
expect(parseFloat('108.34px 29'), 108.34);

// Number followed by space and text.
expect(parseFloat('108.34 px29'), 108.34);
expect(parseFloat('108.34 px 29'), 108.34);

// Invalid numbers.
expect(parseFloat('text'), isNull);
expect(parseFloat('text108'), isNull);
expect(parseFloat('text 108'), isNull);
expect(parseFloat('another text 108'), isNull);
});
}