未验证 提交 4c41b140 编写于 作者: M Mouad Debbar 提交者: GitHub

[web] Introduce js interop to enable experimental flags on web (#17099)

上级 21f5d7f3
......@@ -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;
}
......
// 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;
......
// 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', () {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册