diff --git a/engine/src/flutter/lib/web_ui/dev/browser_lock.yaml b/engine/src/flutter/lib/web_ui/dev/browser_lock.yaml index 1fc22247b97..38e3e65b734 100644 --- a/engine/src/flutter/lib/web_ui/dev/browser_lock.yaml +++ b/engine/src/flutter/lib/web_ui/dev/browser_lock.yaml @@ -13,11 +13,11 @@ chrome: # `self.m.platform.name.capitalize()` evaluates to. See: # # recipe_modules/web_util/api.py - Linux: 1027016 - Mac: 1027007 - Mac_Arm: 1026994 - Win: 1026943 - version: '105.0' # CIPD tag for the above Build IDs. Normally "ChromeMajorVersion.UploadAttempt". ;) + Linux: 1047731 + Mac: 1047732 + Mac_Arm: 1047734 + Win: 1047731 + version: '107.0' # CIPD tag for the above Build IDs. Normally "ChromeMajorVersion.UploadAttempt". ;) ## Firefox does not use CIPD. To update the version, simply update it in this ## file. diff --git a/engine/src/flutter/lib/web_ui/dev/chrome.dart b/engine/src/flutter/lib/web_ui/dev/chrome.dart index 18d8a74a85e..d195413f42d 100644 --- a/engine/src/flutter/lib/web_ui/dev/chrome.dart +++ b/engine/src/flutter/lib/web_ui/dev/chrome.dart @@ -21,11 +21,15 @@ import 'environment.dart'; /// Provides an environment for desktop Chrome. class ChromeEnvironment implements BrowserEnvironment { + ChromeEnvironment(this._enableWasmGC); + late final BrowserInstallation _installation; + final bool _enableWasmGC; + @override Future launchBrowserInstance(Uri url, {bool debug = false}) async { - return Chrome(url, _installation, debug: debug); + return Chrome(url, _installation, debug: debug, enableWasmGC: _enableWasmGC); } @override @@ -60,7 +64,7 @@ class ChromeEnvironment implements BrowserEnvironment { class Chrome extends Browser { /// Starts a new instance of Chrome open to the given [url], which may be a /// [Uri] or a [String]. - factory Chrome(Uri url, BrowserInstallation installation, {bool debug = false}) { + factory Chrome(Uri url, BrowserInstallation installation, {required bool debug, required bool enableWasmGC}) { final Completer remoteDebuggerCompleter = Completer.sync(); return Chrome._(BrowserProcess(() async { // A good source of various Chrome CLI options: @@ -76,7 +80,15 @@ class Chrome extends Browser { final bool isChromeNoSandbox = Platform.environment['CHROME_NO_SANDBOX'] == 'true'; final String dir = environment.webUiDartToolDir.createTempSync('test_chrome_user_data_').resolveSymbolicLinksSync(); + final String jsFlags = enableWasmGC ? [ + '--experimental-wasm-gc', + '--wasm-gc-js-interop', + '--experimental-wasm-stack-switching', + '--experimental-wasm-type-reflection', + '--wasm-gc-js-interop', + ].join(' ') : ''; final List args = [ + if (jsFlags.isNotEmpty) '--js-flags=$jsFlags', '--user-data-dir=$dir', url.toString(), if (!debug) diff --git a/engine/src/flutter/lib/web_ui/dev/common.dart b/engine/src/flutter/lib/web_ui/dev/common.dart index b951c362de9..eab1360ac87 100644 --- a/engine/src/flutter/lib/web_ui/dev/common.dart +++ b/engine/src/flutter/lib/web_ui/dev/common.dart @@ -261,10 +261,10 @@ const List kAllBrowserNames = [ /// Creates an environment for a browser. /// /// The [browserName] matches the browser name passed as the `--browser` option. -BrowserEnvironment getBrowserEnvironment(String browserName) { +BrowserEnvironment getBrowserEnvironment(String browserName, { required bool enableWasmGC }) { switch (browserName) { case kChrome: - return ChromeEnvironment(); + return ChromeEnvironment(enableWasmGC); case kEdge: return EdgeEnvironment(); case kFirefox: diff --git a/engine/src/flutter/lib/web_ui/dev/environment.dart b/engine/src/flutter/lib/web_ui/dev/environment.dart index 08822a39dac..436d8cf47dc 100644 --- a/engine/src/flutter/lib/web_ui/dev/environment.dart +++ b/engine/src/flutter/lib/web_ui/dev/environment.dart @@ -105,9 +105,26 @@ class Environment { /// The "dart" executable file. String get dartExecutable => pathlib.join(dartSdkDir.path, 'bin', 'dart'); + /// Path to dartaotruntime for running aot snapshots + String get dartAotRuntimePath => pathlib.join(dartSdkDir.path, 'bin', 'dartaotruntime'); + /// The "pub" executable file. String get pubExecutable => pathlib.join(dartSdkDir.path, 'bin', 'pub'); + /// The path to dart2wasm pre-compiled snapshot + String get dart2wasmSnapshotPath => pathlib.join(dartSdkDir.path, 'bin', 'snapshots', 'dart2wasm_product.snapshot'); + + /// The path to dart2wasm.dart file + String get dart2wasmScriptPath => pathlib.join( + engineSrcDir.path, + 'third_party', + 'dart', + 'pkg', + 'dart2wasm', + 'bin', + 'dart2wasm.dart' + ); + /// Path to where github.com/flutter/engine is checked out inside the engine workspace. io.Directory get flutterDirectory => io.Directory(pathlib.join(engineSrcDir.path, 'flutter')); diff --git a/engine/src/flutter/lib/web_ui/dev/run.dart b/engine/src/flutter/lib/web_ui/dev/run.dart index f83121f3b1c..eaed34bfbf8 100644 --- a/engine/src/flutter/lib/web_ui/dev/run.dart +++ b/engine/src/flutter/lib/web_ui/dev/run.dart @@ -33,11 +33,17 @@ class RunCommand extends Command with ArgUtils { help: 'Whether we require Skia Gold to be available or not. When this ' 'flag is true, the tests will fail if Skia Gold is not available.', ); + argParser.addFlag( + 'wasm', + help: 'Whether the test we are running are compiled to webassembly.' + ); } @override String get name => 'run'; + bool get isWasm => boolArg('wasm'); + bool get isListSteps => boolArg('list'); /// When running screenshot tests, require Skia Gold to be available and @@ -59,6 +65,7 @@ class RunCommand extends Command with ArgUtils { 'run_tests_$browserName': RunTestsStep( browserName: browserName, isDebug: false, + isWasm: isWasm, doUpdateScreenshotGoldens: false, requireSkiaGold: requireSkiaGold, overridePathToCanvasKit: null, diff --git a/engine/src/flutter/lib/web_ui/dev/steps/compile_tests_step.dart b/engine/src/flutter/lib/web_ui/dev/steps/compile_tests_step.dart index aa5f30a538b..cfef289f207 100644 --- a/engine/src/flutter/lib/web_ui/dev/steps/compile_tests_step.dart +++ b/engine/src/flutter/lib/web_ui/dev/steps/compile_tests_step.dart @@ -23,9 +23,10 @@ import '../utils.dart'; /// * test/ - compiled test code /// * test_images/ - test images copied from Skis sources. class CompileTestsStep implements PipelineStep { - CompileTestsStep({this.testFiles, this.useLocalCanvasKit = false}); + CompileTestsStep({this.testFiles, this.useLocalCanvasKit = false, this.isWasm = false}); final List? testFiles; + final bool isWasm; final bool useLocalCanvasKit; @@ -43,11 +44,15 @@ class CompileTestsStep implements PipelineStep { @override Future run() async { await environment.webUiBuildDir.create(); + if (isWasm) { + await copyDart2WasmTestScript(); + await copyDart2WasmRuntime(); + } await copyCanvasKitFiles(useLocalCanvasKit: useLocalCanvasKit); await buildHostPage(); await copyTestFonts(); await copySkiaTestImages(); - await compileTests(testFiles ?? findAllTests()); + await compileTests(testFiles ?? findAllTests(), isWasm); } } @@ -124,6 +129,32 @@ Future copySkiaTestImages() async { } } +Future copyDart2WasmRuntime() async { + final io.File sourceFile = io.File(pathlib.join( + environment.dartSdkDir.path, + 'bin', + 'dart2wasm_runtime.mjs', + )); + final io.Directory targetDir = io.Directory(pathlib.join( + environment.webUiBuildDir.path, + 'dart2wasm_runtime.mjs', + )); + + await sourceFile.copy(targetDir.path); +} + +Future copyDart2WasmTestScript() async { + final io.File sourceFile = io.File(pathlib.join( + environment.webUiDevDir.path, + 'test_dart2wasm.js', + )); + final io.Directory targetDir = io.Directory(pathlib.join( + environment.webUiBuildDir.path, + 'test_dart2wasm.js', + )); + await sourceFile.copy(targetDir.path); +} + Future copyCanvasKitFiles({bool useLocalCanvasKit = false}) async { // If CanvasKit has been built locally, use that instead of the CIPD version. final io.File localCanvasKitWasm = io.File(pathlib.join( @@ -191,18 +222,18 @@ Future copyCanvasKitFiles({bool useLocalCanvasKit = false}) async { } /// Compiles the specified unit tests. -Future compileTests(List testFiles) async { +Future compileTests(List testFiles, bool isWasm) async { final Stopwatch stopwatch = Stopwatch()..start(); final TestsByRenderer sortedTests = sortTestsByRenderer(testFiles); await Future.wait(>[ if (sortedTests.htmlTests.isNotEmpty) - _compileTestsInParallel(targets: sortedTests.htmlTests), + _compileTestsInParallel(targets: sortedTests.htmlTests, isWasm: isWasm), if (sortedTests.canvasKitTests.isNotEmpty) - _compileTestsInParallel(targets: sortedTests.canvasKitTests, renderer: Renderer.canvasKit), + _compileTestsInParallel(targets: sortedTests.canvasKitTests, renderer: Renderer.canvasKit, isWasm: isWasm), if (sortedTests.skwasmTests.isNotEmpty) - _compileTestsInParallel(targets: sortedTests.skwasmTests, renderer: Renderer.skwasm), + _compileTestsInParallel(targets: sortedTests.skwasmTests, renderer: Renderer.skwasm, isWasm: isWasm), ]); stopwatch.stop(); @@ -210,7 +241,7 @@ Future compileTests(List testFiles) async { final int targetCount = sortedTests.numTargetsToCompile; print( 'Built $targetCount tests in ${stopwatch.elapsedMilliseconds ~/ 1000} ' - 'seconds using $_dart2jsConcurrency concurrent dart2js processes.', + 'seconds using $_dart2jsConcurrency concurrent compile processes.', ); } @@ -223,10 +254,11 @@ final Pool _dart2jsPool = Pool(_dart2jsConcurrency); Future _compileTestsInParallel({ required List targets, Renderer renderer = Renderer.html, + bool isWasm = false, }) async { final Stream results = _dart2jsPool.forEach( targets, - (FilePath file) => compileUnitTest(file, renderer: renderer), + (FilePath file) => compileUnitTest(file, renderer: renderer, isWasm: isWasm), ); await for (final bool isSuccess in results) { if (!isSuccess) { @@ -235,6 +267,11 @@ Future _compileTestsInParallel({ } } +Future compileUnitTest(FilePath input, {required Renderer renderer, required bool isWasm}) async { + return isWasm ? compileUnitTestToWasm(input, renderer: renderer) + : compileUnitTestToJS(input, renderer: renderer); +} + /// Compiles one unit test using `dart2js`. /// /// When building for CanvasKit we have to use extra argument @@ -252,7 +289,7 @@ Future _compileTestsInParallel({ /// directory before test are build. See [_copyFilesFromTestToBuild]. /// /// Later the extra files will be deleted in [_cleanupExtraFilesUnderTestDir]. -Future compileUnitTest(FilePath input, {required Renderer renderer}) async { +Future compileUnitTestToJS(FilePath input, {required Renderer renderer}) async { // Compile to different directories for different renderers. This allows us // to run the same test in multiple renderers. final String targetFileName = pathlib.join( @@ -305,6 +342,50 @@ Future compileUnitTest(FilePath input, {required Renderer renderer}) async } } +Future compileUnitTestToWasm(FilePath input, {required Renderer renderer}) async { + final String targetFileName = pathlib.join( + environment.webUiBuildDir.path, + '${input.relativeToWebUi}.browser_test.dart.wasm', + ); + + final io.Directory directoryToTarget = io.Directory(pathlib.join( + environment.webUiBuildDir.path, + pathlib.dirname(input.relativeToWebUi))); + + if (!directoryToTarget.existsSync()) { + directoryToTarget.createSync(recursive: true); + } + + final List arguments = [ + environment.dart2wasmSnapshotPath, + + '--dart-sdk=${environment.dartSdkDir.path}', + + // We do not want to auto-select a renderer in tests. As of today, tests + // are designed to run in one specific mode. So instead, we specify the + // renderer explicitly. + '-DFLUTTER_WEB_AUTO_DETECT=false', + '-DFLUTTER_WEB_USE_SKIA=${renderer == Renderer.canvasKit}', + '-DFLUTTER_WEB_USE_SKWASM=${renderer == Renderer.skwasm}', + input.relativeToWebUi, // current path. + targetFileName, // target path. + ]; + + final int exitCode = await runProcess( + environment.dartAotRuntimePath, + arguments, + workingDirectory: environment.webUiRootDir.path, + ); + + if (exitCode != 0) { + io.stderr.writeln('ERROR: Failed to compile test $input. ' + 'dart2wasm exited with exit code $exitCode'); + return false; + } else { + return true; + } +} + Future buildHostPage() async { final String hostDartPath = pathlib.join('lib', 'static', 'host.dart'); final io.File hostDartFile = io.File(pathlib.join( diff --git a/engine/src/flutter/lib/web_ui/dev/steps/run_tests_step.dart b/engine/src/flutter/lib/web_ui/dev/steps/run_tests_step.dart index 50464d206c9..fbb19a4b482 100644 --- a/engine/src/flutter/lib/web_ui/dev/steps/run_tests_step.dart +++ b/engine/src/flutter/lib/web_ui/dev/steps/run_tests_step.dart @@ -38,11 +38,13 @@ class RunTestsStep implements PipelineStep { required this.requireSkiaGold, this.testFiles, required this.overridePathToCanvasKit, + required this.isWasm }); final String browserName; final List? testFiles; final bool isDebug; + final bool isWasm; final bool doUpdateScreenshotGoldens; final String? overridePathToCanvasKit; @@ -62,7 +64,7 @@ class RunTestsStep implements PipelineStep { Future run() async { await _prepareTestResultsDirectory(); - final BrowserEnvironment browserEnvironment = getBrowserEnvironment(browserName); + final BrowserEnvironment browserEnvironment = getBrowserEnvironment(browserName, enableWasmGC: isWasm); await browserEnvironment.prepare(); final SkiaGoldClient? skiaClient = await _createSkiaClient(); @@ -77,6 +79,7 @@ class RunTestsStep implements PipelineStep { browserEnvironment: browserEnvironment, expectFailure: false, isDebug: isDebug, + isWasm: isWasm, doUpdateScreenshotGoldens: doUpdateScreenshotGoldens, skiaClient: skiaClient, overridePathToCanvasKit: overridePathToCanvasKit, @@ -90,6 +93,7 @@ class RunTestsStep implements PipelineStep { browserEnvironment: browserEnvironment, expectFailure: false, isDebug: isDebug, + isWasm: isWasm, doUpdateScreenshotGoldens: doUpdateScreenshotGoldens, skiaClient: skiaClient, overridePathToCanvasKit: overridePathToCanvasKit, @@ -103,6 +107,7 @@ class RunTestsStep implements PipelineStep { browserEnvironment: browserEnvironment, expectFailure: false, isDebug: isDebug, + isWasm: isWasm, doUpdateScreenshotGoldens: doUpdateScreenshotGoldens, skiaClient: skiaClient, overridePathToCanvasKit: overridePathToCanvasKit, @@ -176,6 +181,7 @@ Future _runTestBatch({ required List testFiles, required Renderer renderer, required bool isDebug, + required bool isWasm, required BrowserEnvironment browserEnvironment, required bool doUpdateScreenshotGoldens, required bool expectFailure, @@ -225,6 +231,7 @@ Future _runTestBatch({ doUpdateScreenshotGoldens: !expectFailure && doUpdateScreenshotGoldens, skiaClient: skiaClient, overridePathToCanvasKit: overridePathToCanvasKit, + isWasm: isWasm, ); }); diff --git a/engine/src/flutter/lib/web_ui/dev/test_dart2wasm.js b/engine/src/flutter/lib/web_ui/dev/test_dart2wasm.js new file mode 100644 index 00000000000..0ce4cfee510 --- /dev/null +++ b/engine/src/flutter/lib/web_ui/dev/test_dart2wasm.js @@ -0,0 +1,73 @@ +// 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. + +// This script runs in HTML files and loads and instantiates dart unit tests +// that are compiled to WebAssembly. It is based off of the `test/dart.js` +// script from the `test` dart package. + +window.onload = async function () { + // Sends an error message to the server indicating that the script failed to + // load. + // + // This mimics a MultiChannel-formatted message. + var sendLoadException = function (message) { + window.parent.postMessage({ + "href": window.location.href, + "data": [0, { "type": "loadException", "message": message }], + "exception": true, + }, window.location.origin); + } + + // Listen for dartLoadException events and forward to the server. + window.addEventListener('dartLoadException', function (e) { + sendLoadException(e.detail); + }); + + // The basename of the current page. + var name = window.location.href.replace(/.*\//, '').replace(/#.*/, ''); + + // Find . + var links = document.getElementsByTagName("link"); + var testLinks = []; + var length = links.length; + for (var i = 0; i < length; ++i) { + if (links[i].rel == "x-dart-test") testLinks.push(links[i]); + } + + if (testLinks.length != 1) { + sendLoadException( + 'Expected exactly 1 in ' + name + ', found ' + + testLinks.length + '.'); + return; + } + + var link = testLinks[0]; + + if (link.href == '') { + sendLoadException( + 'Expected in ' + name + ' to have an "href" ' + + 'attribute.'); + return; + } + + let dart2wasm_runtime; + let moduleInstance; + try { + dart2wasm_runtime = await import('./dart2wasm_runtime.mjs'); + const dartModulePromise = WebAssembly.compileStreaming(fetch(link.href + ".browser_test.dart.wasm")); + moduleInstance = await dart2wasm_runtime.instantiate(dartModulePromise, {}); + } catch (exception) { + const message = `Failed to fetch and instantiate wasm module: ${exception}`; + sendLoadException(message); + } + + if (moduleInstance) { + try { + await dart2wasm_runtime.invoke(moduleInstance); + } catch (exception) { + const message = `Exception while invoking test: ${exception}`; + sendLoadException(message); + } + } +}; diff --git a/engine/src/flutter/lib/web_ui/dev/test_platform.dart b/engine/src/flutter/lib/web_ui/dev/test_platform.dart index 44de9fed942..dbb29c81e15 100644 --- a/engine/src/flutter/lib/web_ui/dev/test_platform.dart +++ b/engine/src/flutter/lib/web_ui/dev/test_platform.dart @@ -47,6 +47,7 @@ class BrowserPlatform extends PlatformPlugin { required this.server, required this.renderer, required this.isDebug, + required this.isWasm, required this.doUpdateScreenshotGoldens, required this.packageConfig, required this.skiaClient, @@ -106,6 +107,7 @@ class BrowserPlatform extends PlatformPlugin { required bool doUpdateScreenshotGoldens, required SkiaGoldClient? skiaClient, required String? overridePathToCanvasKit, + required bool isWasm, }) async { final shelf_io.IOServer server = shelf_io.IOServer(await HttpMultiServer.loopback(0)); @@ -114,6 +116,7 @@ class BrowserPlatform extends PlatformPlugin { renderer: renderer, server: server, isDebug: Configuration.current.pauseAfterLoad, + isWasm: isWasm, doUpdateScreenshotGoldens: doUpdateScreenshotGoldens, packageConfig: await loadPackageConfigUri((await Isolate.packageConfig)!), skiaClient: skiaClient, @@ -126,6 +129,8 @@ class BrowserPlatform extends PlatformPlugin { /// breakpoints in the code. final bool isDebug; + final bool isWasm; + /// The underlying server. final shelf.Server server; @@ -368,6 +373,7 @@ class BrowserPlatform extends PlatformPlugin { static const Map contentTypes = { '.js': 'text/javascript', + '.mjs': 'text/javascript', '.wasm': 'application/wasm', '.html': 'text/html', '.htm': 'text/html', @@ -439,6 +445,8 @@ class BrowserPlatform extends PlatformPlugin { final String scriptBase = htmlEscape.convert(p.basename(test)); final String link = ''; + final String testRunner = isWasm ? '/test_dart2wasm.js' : 'packages/test/dart.js'; + return shelf.Response.ok(''' @@ -451,7 +459,7 @@ class BrowserPlatform extends PlatformPlugin { }; $link - + ''', headers: {'Content-Type': 'text/html'}); @@ -502,10 +510,6 @@ class BrowserPlatform extends PlatformPlugin { return suite; } - @override - StreamChannel loadChannel(String path, SuitePlatform platform) => - throw UnimplementedError(); - Future? _browserManager; Future get browserManager async => (await _browserManager!)!; @@ -533,6 +537,7 @@ class BrowserPlatform extends PlatformPlugin { url: hostUrl, future: completer.future, packageConfig: packageConfig, + isWasm: isWasm, debug: isDebug, renderer: renderer, ); @@ -630,7 +635,7 @@ class BrowserManager { /// Creates a new BrowserManager that communicates with the browser over /// [webSocket]. BrowserManager._(this.packageConfig, this._browser, this._browserEnvironment, - this._renderer, WebSocketChannel webSocket) { + this._renderer, this._isWasm, WebSocketChannel webSocket) { // The duration should be short enough that the debugging console is open as // soon as the user is done setting breakpoints, but long enough that a test // doing a lot of synchronous work doesn't trigger a false positive. @@ -702,6 +707,9 @@ class BrowserManager { /// Whether the channel to the browser has closed. bool _closed = false; + /// Whether we are running tests that have been compiled to WebAssembly. + final bool _isWasm; + /// The completer for [_BrowserEnvironment.displayPause]. /// /// This will be `null` as long as the browser isn't displaying a pause @@ -744,6 +752,7 @@ class BrowserManager { required Future future, required PackageConfig packageConfig, required Renderer renderer, + required bool isWasm, bool debug = false, }) async { final Browser browser = @@ -755,6 +764,7 @@ class BrowserManager { packageConfig: packageConfig, browser: browser, renderer: renderer, + isWasm: isWasm, debug: debug); } @@ -765,6 +775,7 @@ class BrowserManager { required PackageConfig packageConfig, required Browser browser, required Renderer renderer, + required bool isWasm, bool debug = false, }) { final Completer completer = Completer(); @@ -786,7 +797,7 @@ class BrowserManager { return; } completer.complete(BrowserManager._( - packageConfig, browser, browserEnvironment, renderer, webSocket)); + packageConfig, browser, browserEnvironment, renderer, isWasm, webSocket)); }).catchError((Object error, StackTrace stackTrace) { browser.close(); if (completer.isCompleted) { @@ -848,6 +859,11 @@ class BrowserManager { sink.close(); })); + if (Configuration.current.pauseAfterLoad) { + print('Browser loaded. Press enter to start tests...'); + stdin.readLineSync(); + } + return _pool.withResource(() async { _channel.sink.add({ 'command': 'loadSuite', @@ -865,24 +881,30 @@ class BrowserManager { suiteChannel, message); - final String sourceMapFileName = - '${p.basename(path)}.browser_test.dart.js.map'; - final String pathToTest = p.dirname(path); + if (_isWasm) { + // We don't have mapping for wasm yet. But we should send a message + // to let the host page move forward. + controller!.channel('test.browser.mapper').sink.add(null); + } else { + final String sourceMapFileName = + '${p.basename(path)}.browser_test.dart.js.map'; + final String pathToTest = p.dirname(path); - final String mapPath = p.join(env.environment.webUiRootDir.path, - 'build', getBuildDirForRenderer(_renderer), pathToTest, sourceMapFileName); + final String mapPath = p.join(env.environment.webUiRootDir.path, + 'build', getBuildDirForRenderer(_renderer), pathToTest, sourceMapFileName); - final Map packageMap = { - for (Package p in packageConfig.packages) p.name: p.packageUriRoot - }; - final JSStackTraceMapper mapper = JSStackTraceMapper( - await File(mapPath).readAsString(), - mapUrl: p.toUri(mapPath), - packageMap: packageMap, - sdkRoot: p.toUri(sdkDir), - ); + final Map packageMap = { + for (Package p in packageConfig.packages) p.name: p.packageUriRoot + }; + final JSStackTraceMapper mapper = JSStackTraceMapper( + await File(mapPath).readAsString(), + mapUrl: p.toUri(mapPath), + packageMap: packageMap, + sdkRoot: p.toUri(sdkDir), + ); - controller!.channel('test.browser.mapper').sink.add(mapper.serialize()); + controller!.channel('test.browser.mapper').sink.add(mapper.serialize()); + } _controllers.add(controller!); return await controller!.suite; @@ -944,6 +966,10 @@ class BrowserManager { /// Closes the manager and releases any resources it owns, including closing /// the browser. Future close() => _closeMemoizer.runOnce(() { + if (Configuration.current.pauseAfterLoad) { + print('Test run finished. Press enter to close browser...'); + stdin.readLineSync(); + } _closed = true; _timer.cancel(); _pauseCompleter?.complete(); diff --git a/engine/src/flutter/lib/web_ui/dev/test_runner.dart b/engine/src/flutter/lib/web_ui/dev/test_runner.dart index 1a695c31025..6d552fd7581 100644 --- a/engine/src/flutter/lib/web_ui/dev/test_runner.dart +++ b/engine/src/flutter/lib/web_ui/dev/test_runner.dart @@ -31,17 +31,17 @@ class TestCommand extends Command with ArgUtils { help: 'Run in watch mode so the tests re-run whenever a change is ' 'made.', ) - ..addFlag('use-system-flutter', - help: - 'integration tests are using flutter repository for various tasks' - ', such as flutter drive, flutter pub get. If this flag is set, felt ' - 'will use flutter command without cloning the repository. This flag ' - 'can save internet bandwidth. However use with caution. Note that ' - 'since flutter repo is always synced to youngest commit older than ' - 'the engine commit for the tests running in CI, the tests results ' - "won't be consistent with CIs when this flag is set. flutter " - 'command should be set in the PATH for this flag to be useful.' - 'This flag can also be used to test local Flutter changes.') + ..addFlag( + 'use-system-flutter', + help: 'integration tests are using flutter repository for various tasks' + ', such as flutter drive, flutter pub get. If this flag is set, felt ' + 'will use flutter command without cloning the repository. This flag ' + 'can save internet bandwidth. However use with caution. Note that ' + 'since flutter repo is always synced to youngest commit older than ' + 'the engine commit for the tests running in CI, the tests results ' + "won't be consistent with CIs when this flag is set. flutter " + 'command should be set in the PATH for this flag to be useful.' + 'This flag can also be used to test local Flutter changes.') ..addFlag( 'require-skia-gold', help: @@ -74,6 +74,10 @@ class TestCommand extends Command with ArgUtils { 'tests. If omitted, the test runner uses the default CanvasKit ' 'build.', ) + ..addFlag( + 'wasm', + help: 'Whether the test we are running are compiled to webassembly.' + ) ..addFlag( 'use-local-canvaskit', help: 'Optional. Whether or not to use the locally built version of ' @@ -91,6 +95,8 @@ class TestCommand extends Command with ArgUtils { bool get failEarly => boolArg('fail-early'); + bool get isWasm => boolArg('wasm'); + /// Whether to start the browser in debug mode. /// /// In this mode the browser pauses before running the test to allow @@ -131,11 +137,12 @@ class TestCommand extends Command with ArgUtils { final Pipeline testPipeline = Pipeline(steps: [ if (isWatchMode) ClearTerminalScreenStep(), - CompileTestsStep(testFiles: testFiles, useLocalCanvasKit: useLocalCanvasKit), + CompileTestsStep(testFiles: testFiles, useLocalCanvasKit: useLocalCanvasKit, isWasm: isWasm), RunTestsStep( browserName: browserName, testFiles: testFiles, isDebug: isDebug, + isWasm: isWasm, doUpdateScreenshotGoldens: doUpdateScreenshotGoldens, requireSkiaGold: requireSkiaGold, overridePathToCanvasKit: overridePathToCanvasKit, diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart index 3df1bd0a123..d8a9e494801 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart @@ -700,6 +700,14 @@ extension DomCanvasRenderingContext2DExtension on DomCanvasRenderingContext2D { external set globalAlpha(num? value); } +@JS() +@staticInterop +class DomCanvasRenderingContextWebGl {} + +extension DomCanvasRenderingContextWebGlExtension on DomCanvasRenderingContextWebGl { + external bool isContextLost(); +} + @JS() @staticInterop class DomImageData {} @@ -771,7 +779,7 @@ Future domHttpRequest(String url, } })); - xhr.addEventListener('error', allowInterop(completer.completeError)); + xhr.addEventListener('error', allowInterop((DomEvent event) => completer.completeError(event))); xhr.send(sendData); return completer.future; } diff --git a/engine/src/flutter/lib/web_ui/pubspec.yaml b/engine/src/flutter/lib/web_ui/pubspec.yaml index 350abbf1bfd..9307f95436d 100644 --- a/engine/src/flutter/lib/web_ui/pubspec.yaml +++ b/engine/src/flutter/lib/web_ui/pubspec.yaml @@ -7,7 +7,7 @@ environment: dependencies: js: 0.6.4 - meta: 1.3.0 + meta: ^1.7.0 dev_dependencies: archive: 3.1.2 @@ -27,7 +27,7 @@ dev_dependencies: shelf_web_socket: any stack_trace: any stream_channel: any - test: 1.17.7 + test: 1.22.0 test_api: any test_core: any typed_data: any diff --git a/engine/src/flutter/lib/web_ui/test/engine/profiler_test.dart b/engine/src/flutter/lib/web_ui/test/engine/profiler_test.dart index ec53fb9399b..16299147480 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/profiler_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/profiler_test.dart @@ -41,9 +41,9 @@ void _profilerTests() { test('can listen to benchmarks', () { final List data = []; - jsOnBenchmark((String name, num value) { + jsOnBenchmark(allowInterop((String name, num value) { data.add(BenchmarkDatapoint(name, value)); - }); + })); Profiler.instance.benchmark('foo', 123); expect(data, [BenchmarkDatapoint('foo', 123)]); @@ -64,9 +64,9 @@ void _profilerTests() { final List data = []; // Wrong callback signature. - jsOnBenchmark((num value) { + jsOnBenchmark(allowInterop((num value) { data.add(BenchmarkDatapoint('bad', value)); - }); + })); expect( () => Profiler.instance.benchmark('foo', 123), throwsA(isA()), @@ -160,8 +160,6 @@ void jsOnBenchmark(dynamic listener) { js_util.setProperty( domWindow, '_flutter_internal_on_benchmark', - listener is Function - ? allowInterop(listener) - : listener, + listener ); } diff --git a/engine/src/flutter/lib/web_ui/test/engine/window_test.dart b/engine/src/flutter/lib/web_ui/test/engine/window_test.dart index e55ed6d9e86..a696fd6c33b 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/window_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/window_test.dart @@ -437,7 +437,7 @@ Future testMain() async { test('dispatches browser event on flutter/service_worker channel', () async { final Completer completer = Completer(); domWindow.addEventListener('flutter-first-frame', - allowInterop(completer.complete)); + allowInterop((DomEvent e) => completer.complete())); final Zone innerZone = Zone.current.fork(); innerZone.runGuarded(() { diff --git a/engine/src/flutter/lib/web_ui/test/html/shaders/gradient_golden_test.dart b/engine/src/flutter/lib/web_ui/test/html/shaders/gradient_golden_test.dart index 125b517df0c..5aebc78ef00 100644 --- a/engine/src/flutter/lib/web_ui/test/html/shaders/gradient_golden_test.dart +++ b/engine/src/flutter/lib/web_ui/test/html/shaders/gradient_golden_test.dart @@ -4,7 +4,6 @@ import 'dart:math' as math; import 'dart:typed_data'; -import 'dart:web_gl'; import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; @@ -362,8 +361,8 @@ Future testMain() async { () async { final DomCanvasElement sideCanvas = createDomCanvasElement(width: 5, height: 5); - final RenderingContext? context = - sideCanvas.getContext('webgl') as RenderingContext?; + final DomCanvasRenderingContextWebGl? context = + sideCanvas.getContext('webgl') as DomCanvasRenderingContextWebGl?; expect(context, isNotNull); final EngineCanvas engineCanvas = diff --git a/engine/src/flutter/web_sdk/pubspec.yaml b/engine/src/flutter/web_sdk/pubspec.yaml index bd2d22bba6c..7ffa0caef09 100644 --- a/engine/src/flutter/web_sdk/pubspec.yaml +++ b/engine/src/flutter/web_sdk/pubspec.yaml @@ -10,4 +10,4 @@ dependencies: dev_dependencies: analyzer: 4.3.1 - test: 1.21.4 + test: 1.22.0 diff --git a/engine/src/flutter/web_sdk/web_engine_tester/pubspec.yaml b/engine/src/flutter/web_sdk/web_engine_tester/pubspec.yaml index 25219b10713..4e0dbbeae1a 100644 --- a/engine/src/flutter/web_sdk/web_engine_tester/pubspec.yaml +++ b/engine/src/flutter/web_sdk/web_engine_tester/pubspec.yaml @@ -7,7 +7,7 @@ environment: dependencies: js: 0.6.4 stream_channel: 2.1.0 - test: 1.17.7 + test: 1.22.0 webkit_inspection_protocol: 1.0.0 stack_trace: 1.10.0 ui: diff --git a/engine/src/flutter/web_sdk/web_test_utils/pubspec.yaml b/engine/src/flutter/web_sdk/web_test_utils/pubspec.yaml index 592663a1592..03aac1e7e62 100644 --- a/engine/src/flutter/web_sdk/web_test_utils/pubspec.yaml +++ b/engine/src/flutter/web_sdk/web_test_utils/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: crypto: 3.0.1 image: 3.0.1 js: 0.6.4 - meta: 1.3.0 + meta: ^1.7.0 path: 1.8.0 process: 4.2.3 skia_gold_client: