mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] Introduce js interop to enable experimental flags on web (flutter/engine#17099)
This commit is contained in:
parent
a3a8b2d61d
commit
0201dfd46b
@ -495,6 +495,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/util.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/validators.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/vector_math.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/web_experiments.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/window.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/ui/annotations.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/ui/canvas.dart
|
||||
|
||||
@ -117,6 +117,7 @@ part 'engine/text_editing/text_editing.dart';
|
||||
part 'engine/util.dart';
|
||||
part 'engine/validators.dart';
|
||||
part 'engine/vector_math.dart';
|
||||
part 'engine/web_experiments.dart';
|
||||
part 'engine/window.dart';
|
||||
|
||||
bool _engineInitialized = false;
|
||||
@ -161,6 +162,8 @@ void webOnlyInitializeEngine() {
|
||||
// initialize framework bindings.
|
||||
domRenderer;
|
||||
|
||||
WebExperiments.ensureInitialized();
|
||||
|
||||
bool waitingForAnimation = false;
|
||||
ui.webOnlyScheduleFrameCallback = () {
|
||||
// We're asked to schedule a frame and call `frameHandler` when the frame
|
||||
|
||||
@ -187,13 +187,6 @@ abstract class TextMeasurementService {
|
||||
static TextMeasurementService get canvasInstance =>
|
||||
CanvasTextMeasurementService.instance;
|
||||
|
||||
/// Whether the new experimental implementation of canvas-based text
|
||||
/// measurement is enabled or not.
|
||||
///
|
||||
/// This is only used for testing at the moment. Once the implementation is
|
||||
/// complete and production-ready, we'll get rid of this flag.
|
||||
static bool enableExperimentalCanvasImplementation = const bool.fromEnvironment('FLUTTER_WEB_USE_EXPERIMENTAL_CANVAS_TEXT', defaultValue: false);
|
||||
|
||||
/// Gets the appropriate [TextMeasurementService] instance for the given
|
||||
/// [paragraph].
|
||||
static TextMeasurementService forParagraph(ui.Paragraph paragraph) {
|
||||
@ -206,7 +199,7 @@ abstract class TextMeasurementService {
|
||||
// Skip using canvas measurements until the iframe becomes visible.
|
||||
// see: https://github.com/flutter/flutter/issues/36341
|
||||
if (!window.physicalSize.isEmpty &&
|
||||
enableExperimentalCanvasImplementation &&
|
||||
WebExperiments.instance.useCanvasText &&
|
||||
_canUseCanvasMeasurement(paragraph)) {
|
||||
return canvasInstance;
|
||||
}
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
// @dart = 2.6
|
||||
part of engine;
|
||||
|
||||
/// A bag of all experiment flags in the web engine.
|
||||
///
|
||||
/// This class also handles platform messages that can be sent to enable/disable
|
||||
/// certain experiments at runtime without the need to access engine internals.
|
||||
class WebExperiments {
|
||||
WebExperiments._() {
|
||||
js.context['_flutter_internal_update_experiment'] = updateExperiment;
|
||||
registerHotRestartListener(() {
|
||||
js.context['_flutter_internal_update_experiment'] = null;
|
||||
});
|
||||
}
|
||||
|
||||
static WebExperiments ensureInitialized() {
|
||||
if (WebExperiments.instance == null) {
|
||||
WebExperiments.instance = WebExperiments._();
|
||||
}
|
||||
return WebExperiments.instance;
|
||||
}
|
||||
|
||||
static WebExperiments instance;
|
||||
|
||||
/// Experiment flag for using canvas-based text measurement.
|
||||
bool get useCanvasText => _useCanvasText ?? false;
|
||||
set useCanvasText(bool enabled) {
|
||||
_useCanvasText = enabled;
|
||||
}
|
||||
|
||||
bool _useCanvasText = const bool.fromEnvironment(
|
||||
'FLUTTER_WEB_USE_EXPERIMENTAL_CANVAS_TEXT',
|
||||
defaultValue: null,
|
||||
);
|
||||
|
||||
/// Reset all experimental flags to their default values.
|
||||
void reset() {
|
||||
_useCanvasText = null;
|
||||
}
|
||||
|
||||
/// Used to enable/disable experimental flags in the web engine.
|
||||
void updateExperiment(String name, bool enabled) {
|
||||
switch (name) {
|
||||
case 'useCanvasText':
|
||||
_useCanvasText = enabled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,10 @@ import 'package:test/test.dart';
|
||||
import 'mock_engine_canvas.dart';
|
||||
|
||||
void main() {
|
||||
setUpAll(() {
|
||||
WebExperiments.ensureInitialized();
|
||||
});
|
||||
|
||||
group('EngineCanvas', () {
|
||||
MockEngineCanvas mockCanvas;
|
||||
ui.Paragraph paragraph;
|
||||
|
||||
@ -0,0 +1,80 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
// @dart = 2.6
|
||||
import 'dart:html' as html;
|
||||
import 'dart:js_util' as js_util;
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
WebExperiments.ensureInitialized();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
WebExperiments.instance.reset();
|
||||
});
|
||||
|
||||
test('default web experiment values', () {
|
||||
expect(WebExperiments.instance.useCanvasText, false);
|
||||
});
|
||||
|
||||
test('can turn on/off web experiments', () {
|
||||
WebExperiments.instance.updateExperiment('useCanvasText', true);
|
||||
expect(WebExperiments.instance.useCanvasText, true);
|
||||
|
||||
WebExperiments.instance.updateExperiment('useCanvasText', false);
|
||||
expect(WebExperiments.instance.useCanvasText, false);
|
||||
|
||||
WebExperiments.instance.updateExperiment('useCanvasText', null);
|
||||
// Goes back to default value.
|
||||
expect(WebExperiments.instance.useCanvasText, false);
|
||||
});
|
||||
|
||||
test('ignores unknown experiments', () {
|
||||
expect(WebExperiments.instance.useCanvasText, false);
|
||||
WebExperiments.instance.updateExperiment('foobarbazqux', true);
|
||||
expect(WebExperiments.instance.useCanvasText, false);
|
||||
WebExperiments.instance.updateExperiment('foobarbazqux', false);
|
||||
expect(WebExperiments.instance.useCanvasText, false);
|
||||
});
|
||||
|
||||
test('can reset web experiments', () {
|
||||
WebExperiments.instance.updateExperiment('useCanvasText', true);
|
||||
WebExperiments.instance.reset();
|
||||
expect(WebExperiments.instance.useCanvasText, false);
|
||||
|
||||
WebExperiments.instance.updateExperiment('useCanvasText', true);
|
||||
WebExperiments.instance.updateExperiment('foobarbazqux', true);
|
||||
WebExperiments.instance.reset();
|
||||
expect(WebExperiments.instance.useCanvasText, false);
|
||||
});
|
||||
|
||||
test('js interop also works', () {
|
||||
expect(WebExperiments.instance.useCanvasText, false);
|
||||
|
||||
expect(() => jsUpdateExperiment('useCanvasText', true), returnsNormally);
|
||||
expect(WebExperiments.instance.useCanvasText, true);
|
||||
|
||||
expect(() => jsUpdateExperiment('useCanvasText', null), returnsNormally);
|
||||
expect(WebExperiments.instance.useCanvasText, false);
|
||||
});
|
||||
|
||||
test('js interop throws on wrong type', () {
|
||||
expect(() => jsUpdateExperiment(123, true), throwsA(anything));
|
||||
expect(() => jsUpdateExperiment('foo', 123), throwsA(anything));
|
||||
expect(() => jsUpdateExperiment('foo', 'bar'), throwsA(anything));
|
||||
expect(() => jsUpdateExperiment(false, 'foo'), throwsA(anything));
|
||||
});
|
||||
}
|
||||
|
||||
void jsUpdateExperiment(dynamic name, dynamic enabled) {
|
||||
js_util.callMethod(
|
||||
html.window,
|
||||
'_flutter_internal_update_experiment',
|
||||
<dynamic>[name, enabled],
|
||||
);
|
||||
}
|
||||
@ -71,7 +71,7 @@ class EngineScubaTester {
|
||||
sceneElement.append(canvas.rootElement);
|
||||
html.document.body.append(sceneElement);
|
||||
String screenshotName = '${fileName}_${canvas.runtimeType}';
|
||||
if (TextMeasurementService.enableExperimentalCanvasImplementation) {
|
||||
if (WebExperiments.instance.useCanvasText) {
|
||||
screenshotName += '+canvas_measurement';
|
||||
}
|
||||
await diffScreenshot(
|
||||
@ -96,18 +96,20 @@ void testEachCanvas(String description, CanvasTest body,
|
||||
test('$description (bitmap)', () {
|
||||
try {
|
||||
TextMeasurementService.initialize(rulerCacheCapacity: 2);
|
||||
WebExperiments.instance.useCanvasText = false;
|
||||
return body(BitmapCanvas(bounds));
|
||||
} finally {
|
||||
WebExperiments.instance.useCanvasText = null;
|
||||
TextMeasurementService.clearCache();
|
||||
}
|
||||
});
|
||||
test('$description (bitmap + canvas measurement)', () async {
|
||||
try {
|
||||
TextMeasurementService.initialize(rulerCacheCapacity: 2);
|
||||
TextMeasurementService.enableExperimentalCanvasImplementation = true;
|
||||
WebExperiments.instance.useCanvasText = true;
|
||||
await body(BitmapCanvas(bounds));
|
||||
} finally {
|
||||
TextMeasurementService.enableExperimentalCanvasImplementation = false;
|
||||
WebExperiments.instance.useCanvasText = null;
|
||||
TextMeasurementService.clearCache();
|
||||
}
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
// @dart = 2.6
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:ui/ui.dart';
|
||||
import 'package:ui/ui.dart' hide window;
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
import 'scuba.dart';
|
||||
@ -89,7 +89,7 @@ void main() async {
|
||||
offset = offset.translate(0, p.height + 10);
|
||||
|
||||
// Only the first line is rendered with an ellipsis.
|
||||
if (!TextMeasurementService.enableExperimentalCanvasImplementation) {
|
||||
if (!WebExperiments.instance.useCanvasText) {
|
||||
// This is now correct with the canvas-based measurement, so we shouldn't
|
||||
// print the "(wrong)" warning.
|
||||
p = warning('(wrong)');
|
||||
@ -106,7 +106,7 @@ void main() async {
|
||||
|
||||
// Only the first two lines are rendered and the ellipsis appears on the 2nd
|
||||
// line.
|
||||
if (!TextMeasurementService.enableExperimentalCanvasImplementation) {
|
||||
if (!WebExperiments.instance.useCanvasText) {
|
||||
// This is now correct with the canvas-based measurement, so we shouldn't
|
||||
// print the "(wrong)" warning.
|
||||
p = warning('(wrong)');
|
||||
|
||||
@ -3,11 +3,16 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.6
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
setUpAll(() {
|
||||
WebExperiments.ensureInitialized();
|
||||
});
|
||||
|
||||
test('Should be able to build and layout a paragraph', () {
|
||||
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle());
|
||||
builder.addText('Hello');
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
// @dart = 2.6
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
import 'package:ui/ui.dart' hide window;
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@ -12,18 +12,20 @@ void testEachMeasurement(String description, VoidCallback body, {bool skip}) {
|
||||
test('$description (dom measurement)', () async {
|
||||
try {
|
||||
TextMeasurementService.initialize(rulerCacheCapacity: 2);
|
||||
WebExperiments.instance.useCanvasText = false;
|
||||
return body();
|
||||
} finally {
|
||||
WebExperiments.instance.useCanvasText = null;
|
||||
TextMeasurementService.clearCache();
|
||||
}
|
||||
}, skip: skip);
|
||||
test('$description (canvas measurement)', () async {
|
||||
try {
|
||||
TextMeasurementService.initialize(rulerCacheCapacity: 2);
|
||||
TextMeasurementService.enableExperimentalCanvasImplementation = true;
|
||||
WebExperiments.instance.useCanvasText = true;
|
||||
return body();
|
||||
} finally {
|
||||
TextMeasurementService.enableExperimentalCanvasImplementation = false;
|
||||
WebExperiments.instance.useCanvasText = null;
|
||||
TextMeasurementService.clearCache();
|
||||
}
|
||||
}, skip: skip);
|
||||
@ -184,7 +186,7 @@ void main() async {
|
||||
test('getPositionForOffset multi-line', () {
|
||||
// [Paragraph.getPositionForOffset] for multi-line text doesn't work well
|
||||
// with dom-based measurement.
|
||||
TextMeasurementService.enableExperimentalCanvasImplementation = true;
|
||||
WebExperiments.instance.useCanvasText = true;
|
||||
TextMeasurementService.initialize(rulerCacheCapacity: 2);
|
||||
|
||||
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
||||
@ -280,11 +282,11 @@ void main() async {
|
||||
);
|
||||
|
||||
TextMeasurementService.clearCache();
|
||||
TextMeasurementService.enableExperimentalCanvasImplementation = false;
|
||||
WebExperiments.instance.useCanvasText = null;
|
||||
});
|
||||
|
||||
test('getPositionForOffset multi-line centered', () {
|
||||
TextMeasurementService.enableExperimentalCanvasImplementation = true;
|
||||
WebExperiments.instance.useCanvasText = true;
|
||||
TextMeasurementService.initialize(rulerCacheCapacity: 2);
|
||||
|
||||
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
||||
@ -387,7 +389,7 @@ void main() async {
|
||||
);
|
||||
|
||||
TextMeasurementService.clearCache();
|
||||
TextMeasurementService.enableExperimentalCanvasImplementation = false;
|
||||
WebExperiments.instance.useCanvasText = null;
|
||||
});
|
||||
|
||||
testEachMeasurement('getBoxesForRange returns a box', () {
|
||||
@ -782,7 +784,7 @@ void main() async {
|
||||
|
||||
test('longestLine', () {
|
||||
// [Paragraph.longestLine] is only supported by canvas-based measurement.
|
||||
TextMeasurementService.enableExperimentalCanvasImplementation = true;
|
||||
WebExperiments.instance.useCanvasText = true;
|
||||
TextMeasurementService.initialize(rulerCacheCapacity: 2);
|
||||
|
||||
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
||||
@ -797,7 +799,7 @@ void main() async {
|
||||
expect(paragraph.longestLine, 50.0);
|
||||
|
||||
TextMeasurementService.clearCache();
|
||||
TextMeasurementService.enableExperimentalCanvasImplementation = false;
|
||||
WebExperiments.instance.useCanvasText = null;
|
||||
});
|
||||
|
||||
testEachMeasurement('getLineBoundary (single-line)', () {
|
||||
@ -824,7 +826,7 @@ void main() async {
|
||||
test('getLineBoundary (multi-line)', () {
|
||||
// [Paragraph.getLineBoundary] for multi-line paragraphs is only supported
|
||||
// by canvas-based measurement.
|
||||
TextMeasurementService.enableExperimentalCanvasImplementation = true;
|
||||
WebExperiments.instance.useCanvasText = true;
|
||||
TextMeasurementService.initialize(rulerCacheCapacity: 2);
|
||||
|
||||
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
|
||||
@ -867,7 +869,7 @@ void main() async {
|
||||
}
|
||||
|
||||
TextMeasurementService.clearCache();
|
||||
TextMeasurementService.enableExperimentalCanvasImplementation = false;
|
||||
WebExperiments.instance.useCanvasText = null;
|
||||
});
|
||||
|
||||
testEachMeasurement('width should be a whole integer', () {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user