diff --git a/packages/web_benchmarks/.gitignore b/packages/web_benchmarks/.gitignore new file mode 100644 index 000000000000..333c1e910a3e --- /dev/null +++ b/packages/web_benchmarks/.gitignore @@ -0,0 +1 @@ +logs/ diff --git a/packages/web_benchmarks/CHANGELOG.md b/packages/web_benchmarks/CHANGELOG.md index 9afb5e58ce28..e399fd367ee0 100644 --- a/packages/web_benchmarks/CHANGELOG.md +++ b/packages/web_benchmarks/CHANGELOG.md @@ -1,5 +1,8 @@ -## 2.1.0-wip +## 3.0.0 +* **Breaking change:** removed the `initialPage` parameter from the `serveWebBenchmark` +method and `runBenchmarks` method. Replaced this parameter with `benchmarkPath`, which +allows for passing the combined value of the URL path segments, fragment, and query parameters. * Restructure the `testing/test_app` to make the example benchmarks easier to follow. ## 2.0.2 diff --git a/packages/web_benchmarks/lib/client.dart b/packages/web_benchmarks/lib/client.dart index 9bdd65b04149..357d2bb8f196 100644 --- a/packages/web_benchmarks/lib/client.dart +++ b/packages/web_benchmarks/lib/client.dart @@ -41,9 +41,12 @@ extension on HTMLElement { /// /// When used without a server, prompts the user to select a benchmark to /// run next. +/// +/// [benchmarkPath] specifies the path for the URL that will be loaded in Chrome +/// when reloading the window for subsequent benchmark runs. Future runBenchmarks( Map benchmarks, { - String initialPage = defaultInitialPage, + String benchmarkPath = defaultInitialPath, }) async { // Set local benchmarks. _benchmarks = benchmarks; @@ -60,14 +63,15 @@ Future runBenchmarks( await _runBenchmark(nextBenchmark); final Uri currentUri = Uri.parse(window.location.href); - // Create a new URI with the current 'page' value set to [initialPage] to - // ensure the benchmark app is reloaded at the proper location. - final String newUri = Uri( - scheme: currentUri.scheme, - host: currentUri.host, - port: currentUri.port, - path: initialPage, - ).toString(); + // Create a new URI with the parsed value of [benchmarkPath] to ensure the + // benchmark app is reloaded with the proper configuration. + final String newUri = Uri.parse(benchmarkPath) + .replace( + scheme: currentUri.scheme, + host: currentUri.host, + port: currentUri.port, + ) + .toString(); // Reloading the window will trigger the next benchmark to run. await _client.printToConsole( diff --git a/packages/web_benchmarks/lib/server.dart b/packages/web_benchmarks/lib/server.dart index 80e5f7a4f508..8a52c9717c80 100644 --- a/packages/web_benchmarks/lib/server.dart +++ b/packages/web_benchmarks/lib/server.dart @@ -47,6 +47,9 @@ const int defaultChromeDebugPort = 10000; /// [compilationOptions] specify the compiler and renderer to use for the /// benchmark app. This can either use dart2wasm & skwasm or /// dart2js & canvaskit. +/// +/// [benchmarkPath] specifies the path for the URL that will be loaded upon +/// opening the benchmark app in Chrome. Future serveWebBenchmark({ required io.Directory benchmarkAppDirectory, required String entryPoint, @@ -54,8 +57,8 @@ Future serveWebBenchmark({ int chromeDebugPort = defaultChromeDebugPort, bool headless = true, bool treeShakeIcons = true, - String initialPage = defaultInitialPage, CompilationOptions compilationOptions = const CompilationOptions.js(), + String benchmarkPath = defaultInitialPath, }) async { // Reduce logging level. Otherwise, package:webkit_inspection_protocol is way too spammy. Logger.root.level = Level.INFO; @@ -64,10 +67,10 @@ Future serveWebBenchmark({ benchmarkAppDirectory: benchmarkAppDirectory, entryPoint: entryPoint, benchmarkServerPort: benchmarkServerPort, + benchmarkPath: benchmarkPath, chromeDebugPort: chromeDebugPort, headless: headless, compilationOptions: compilationOptions, treeShakeIcons: treeShakeIcons, - initialPage: initialPage, ).run(); } diff --git a/packages/web_benchmarks/lib/src/common.dart b/packages/web_benchmarks/lib/src/common.dart index 5c7c7259796a..a32f65c77da7 100644 --- a/packages/web_benchmarks/lib/src/common.dart +++ b/packages/web_benchmarks/lib/src/common.dart @@ -15,6 +15,6 @@ const int kMeasuredSampleCount = 100; /// all benchmarks have run and there are no more benchmarks to run. const String kEndOfBenchmarks = '__end_of_benchmarks__'; -/// The default initial page to load upon opening the benchmark app or reloading -/// it in Chrome. -const String defaultInitialPage = 'index.html'; +/// The default initial path for the URL that will be loaded upon opening the +/// benchmark app or reloading it in Chrome. +const String defaultInitialPath = 'index.html'; diff --git a/packages/web_benchmarks/lib/src/runner.dart b/packages/web_benchmarks/lib/src/runner.dart index 0c929104622f..a78ef20bbc49 100644 --- a/packages/web_benchmarks/lib/src/runner.dart +++ b/packages/web_benchmarks/lib/src/runner.dart @@ -53,6 +53,9 @@ class BenchmarkServer { /// [compilationOptions] specify the compiler and renderer to use for the /// benchmark app. This can either use dart2wasm & skwasm or /// dart2js & canvaskit. + /// + /// [benchmarkPath] specifies the path for the URL that will be loaded upon + /// opening the benchmark app in Chrome. BenchmarkServer({ required this.benchmarkAppDirectory, required this.entryPoint, @@ -61,7 +64,7 @@ class BenchmarkServer { required this.headless, required this.treeShakeIcons, this.compilationOptions = const CompilationOptions.js(), - this.initialPage = defaultInitialPage, + this.benchmarkPath = defaultInitialPath, }); final ProcessManager _processManager = const LocalProcessManager(); @@ -97,13 +100,25 @@ class BenchmarkServer { /// When false, '--no-tree-shake-icons' will be passed as a build argument. final bool treeShakeIcons; - /// The initial page to load upon opening the benchmark app in Chrome. + /// The initial path for the URL that will be loaded upon opening the + /// benchmark app in Chrome. + /// + /// This path should contain the path segments, fragment, and/or query + /// parameters that are required for the benchmark. This value will be parsed + /// by `Uri.parse` and combined with the benchmark URI scheme ('http'), host + /// ('localhost'), and port [benchmarkServerPort] to create the URL for + /// loading in Chrome. See [_benchmarkAppUrl]. /// - /// The default value is [defaultInitialPage]. - final String initialPage; + /// The default value is [defaultInitialPath]. + final String benchmarkPath; - String get _benchmarkAppUrl => - 'http://localhost:$benchmarkServerPort/$initialPage'; + String get _benchmarkAppUrl => Uri.parse(benchmarkPath) + .replace( + scheme: 'http', + host: 'localhost', + port: benchmarkServerPort, + ) + .toString(); /// Builds and serves the benchmark app, and collects benchmark results. Future run() async { @@ -169,6 +184,7 @@ class BenchmarkServer { path.join(benchmarkAppDirectory.path, 'build', 'web'), defaultDocument: 'index.html', ); + // We want our page to be crossOriginIsolated. This will allow us to run the // skwasm renderer, which uses a SharedArrayBuffer, which requires the page // to be crossOriginIsolated. But also, even in the non-skwasm case, running @@ -277,9 +293,20 @@ class BenchmarkServer { } }); - // If all previous handlers returned HTTP 404, this is the last handler - // that simply warns about the unrecognized path. - cascade = cascade.add((Request request) { + // If all previous handlers returned HTTP 404, this handler either serves + // the static handler at the default document (for GET requests only) or + // warns about the unrecognized path. + cascade = cascade.add((Request request) async { + if (request.method == 'GET') { + final Uri newRequestUri = request.requestedUri.replace(path: '/'); + final Request newRequest = Request( + request.method, + newRequestUri, + headers: request.headers, + ); + return await buildFolderHandler(newRequest); + } + io.stderr.writeln('Unrecognized URL path: ${request.requestedUri.path}'); return Response.notFound('Not found: ${request.requestedUri.path}'); }); diff --git a/packages/web_benchmarks/pubspec.yaml b/packages/web_benchmarks/pubspec.yaml index fb8f9df26df7..94964f30230c 100644 --- a/packages/web_benchmarks/pubspec.yaml +++ b/packages/web_benchmarks/pubspec.yaml @@ -2,7 +2,7 @@ name: web_benchmarks description: A benchmark harness for performance-testing Flutter apps in Chrome. repository: https://github.com/flutter/packages/tree/main/packages/web_benchmarks issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+web_benchmarks%22 -version: 2.1.0-wip +version: 3.0.0 environment: sdk: ^3.3.0 diff --git a/packages/web_benchmarks/testing/test_app/benchmark/test_infra/automator.dart b/packages/web_benchmarks/testing/test_app/benchmark/test_infra/automator.dart index 4184a8a74305..9484a544144c 100644 --- a/packages/web_benchmarks/testing/test_app/benchmark/test_infra/automator.dart +++ b/packages/web_benchmarks/testing/test_app/benchmark/test_infra/automator.dart @@ -59,8 +59,8 @@ class Automator { await _handleAppTap(); case BenchmarkName.simpleCompilationCheck: _handleSimpleCompilationCheck(); - case BenchmarkName.simpleInitialPageCheck: - _handleSimpleInitialPageCheck(); + case BenchmarkName.simpleBenchmarkPathCheck: + _handleSimpleBenchmarkPathCheck(); } // At the end of the test, mark as finished. @@ -117,12 +117,12 @@ class Automator { profile.extraData['isWasm'] = kIsWasm ? 1 : 0; } - void _handleSimpleInitialPageCheck() { - // Record whether the URL contains the expected initial page so we can - // verify the behavior of setting the `initialPage` on the benchmark server. - final bool containsExpectedPage = - window.location.toString().contains(testBenchmarkInitialPage); - profile.extraData['expectedUrl'] = containsExpectedPage ? 1 : 0; + void _handleSimpleBenchmarkPathCheck() { + // Record whether the URL contains the expected path so we can verify the + // behavior of setting the `benchmarkPath` on the benchmark server. + final bool containsExpectedPath = + window.location.toString().contains(testBenchmarkPath); + profile.extraData['expectedUrl'] = containsExpectedPath ? 1 : 0; } } diff --git a/packages/web_benchmarks/testing/test_app/benchmark/test_infra/client/simple_initial_page_client.dart b/packages/web_benchmarks/testing/test_app/benchmark/test_infra/client/simple_benchmark_path_client.dart similarity index 68% rename from packages/web_benchmarks/testing/test_app/benchmark/test_infra/client/simple_initial_page_client.dart rename to packages/web_benchmarks/testing/test_app/benchmark/test_infra/client/simple_benchmark_path_client.dart index 9bfd9cf3ae2c..f8f54492c3e8 100644 --- a/packages/web_benchmarks/testing/test_app/benchmark/test_infra/client/simple_initial_page_client.dart +++ b/packages/web_benchmarks/testing/test_app/benchmark/test_infra/client/simple_benchmark_path_client.dart @@ -10,9 +10,10 @@ import '../recorder.dart'; Future main() async { await runBenchmarks( { - BenchmarkName.simpleInitialPageCheck.name: () => TestAppRecorder( - benchmark: BenchmarkName.simpleInitialPageCheck, + BenchmarkName.simpleBenchmarkPathCheck.name: () => TestAppRecorder( + benchmark: BenchmarkName.simpleBenchmarkPathCheck, ), }, + benchmarkPath: testBenchmarkPath, ); } diff --git a/packages/web_benchmarks/testing/test_app/benchmark/test_infra/common.dart b/packages/web_benchmarks/testing/test_app/benchmark/test_infra/common.dart index eaba8cd11572..8045852f64d5 100644 --- a/packages/web_benchmarks/testing/test_app/benchmark/test_infra/common.dart +++ b/packages/web_benchmarks/testing/test_app/benchmark/test_infra/common.dart @@ -2,12 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -const String testBenchmarkInitialPage = 'index.html#about'; +/// The benchmark path to load in the URL when loading or reloading the +/// benchmark app in Chrome. +const String testBenchmarkPath = 'about'; enum BenchmarkName { appNavigate, appScroll, appTap, - simpleInitialPageCheck, + simpleBenchmarkPathCheck, simpleCompilationCheck; } diff --git a/packages/web_benchmarks/testing/test_app/benchmark/web_benchmarks_test.dart b/packages/web_benchmarks/testing/test_app/benchmark/web_benchmarks_test.dart index 64b4f6061c89..63b33dc8a14e 100644 --- a/packages/web_benchmarks/testing/test_app/benchmark/web_benchmarks_test.dart +++ b/packages/web_benchmarks/testing/test_app/benchmark/web_benchmarks_test.dart @@ -29,25 +29,25 @@ Future main() async { ); test( - 'Can run a web benchmark with an alternate initial page', + 'Can run a web benchmark with an alternate benchmarkPath', () async { final BenchmarkResults results = await _runBenchmarks( - benchmarkNames: [BenchmarkName.simpleInitialPageCheck.name], + benchmarkNames: [BenchmarkName.simpleBenchmarkPathCheck.name], entryPoint: - 'benchmark/test_infra/client/simple_initial_page_client.dart', - initialPage: testBenchmarkInitialPage, + 'benchmark/test_infra/client/simple_benchmark_path_client.dart', + benchmarkPath: testBenchmarkPath, ); - // The runner puts an `expectedUrl` metric in the results so that we can - // verify the initial page value that should be passed on initial load - // and on reloads. final List? scores = - results.scores[BenchmarkName.simpleInitialPageCheck.name]; + results.scores[BenchmarkName.simpleBenchmarkPathCheck.name]; expect(scores, isNotNull); - final BenchmarkScore isWasmScore = scores! + // The runner puts an `expectedUrl` metric in the results so that we can + // verify the initial page value that should be passed on initial load + // and on reloads. + final BenchmarkScore expectedUrlScore = scores! .firstWhere((BenchmarkScore score) => score.metric == 'expectedUrl'); - expect(isWasmScore.value, 1); + expect(expectedUrlScore.value, 1); }, timeout: Timeout.none, ); @@ -79,14 +79,14 @@ Future main() async { Future _runBenchmarks({ required List benchmarkNames, required String entryPoint, - String initialPage = defaultInitialPage, + String benchmarkPath = defaultInitialPath, CompilationOptions compilationOptions = const CompilationOptions.js(), }) async { final BenchmarkResults taskResult = await serveWebBenchmark( benchmarkAppDirectory: Directory('testing/test_app'), entryPoint: entryPoint, treeShakeIcons: false, - initialPage: initialPage, + benchmarkPath: benchmarkPath, compilationOptions: compilationOptions, ); diff --git a/packages/web_benchmarks/testing/test_app/lib/about_page.dart b/packages/web_benchmarks/testing/test_app/lib/about_page.dart index a3603abeb553..23f85320eaf7 100644 --- a/packages/web_benchmarks/testing/test_app/lib/about_page.dart +++ b/packages/web_benchmarks/testing/test_app/lib/about_page.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; class AboutPage extends StatelessWidget { const AboutPage({super.key}); @@ -12,7 +13,7 @@ class AboutPage extends StatelessWidget { return Scaffold( appBar: AppBar( leading: BackButton( - onPressed: () => Navigator.of(context).pop(), + onPressed: () => context.canPop() ? context.pop() : context.go('/'), ), ), body: Center( diff --git a/packages/web_benchmarks/testing/test_app/lib/home_page.dart b/packages/web_benchmarks/testing/test_app/lib/home_page.dart index 2fecf2514e17..c0f7a131aec3 100644 --- a/packages/web_benchmarks/testing/test_app/lib/home_page.dart +++ b/packages/web_benchmarks/testing/test_app/lib/home_page.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; const ValueKey textKey = ValueKey('textKey'); const ValueKey aboutPageKey = ValueKey('aboutPageKey'); @@ -34,7 +35,7 @@ class _HomePageState extends State { IconButton( key: aboutPageKey, icon: const Icon(Icons.help_outline), - onPressed: () => Navigator.of(context).pushNamed('about'), + onPressed: () => context.go('/about'), ), ], ), diff --git a/packages/web_benchmarks/testing/test_app/lib/main.dart b/packages/web_benchmarks/testing/test_app/lib/main.dart index f615e761acb8..172abba32888 100644 --- a/packages/web_benchmarks/testing/test_app/lib/main.dart +++ b/packages/web_benchmarks/testing/test_app/lib/main.dart @@ -3,32 +3,44 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; +import 'package:flutter_web_plugins/url_strategy.dart'; +import 'package:go_router/go_router.dart'; import 'about_page.dart'; import 'home_page.dart'; void main() { + usePathUrlStrategy(); runApp(const MyApp()); } +final GoRouter _router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (_, __) => const HomePage(title: 'Flutter Demo Home Page'), + ), + GoRoute( + path: '/about', + builder: (_, __) => const AboutPage(), + ), + ], +); + class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { - return MaterialApp( + return MaterialApp.router( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), + routerConfig: _router, // This blocks the About page button. debugShowCheckedModeBanner: false, - initialRoute: 'home', - routes: { - 'home': (_) => const HomePage(title: 'Flutter Demo Home Page'), - 'about': (_) => const AboutPage(), - }, ); } } diff --git a/packages/web_benchmarks/testing/test_app/pubspec.yaml b/packages/web_benchmarks/testing/test_app/pubspec.yaml index 953b188efcea..902f1e0ba9cb 100644 --- a/packages/web_benchmarks/testing/test_app/pubspec.yaml +++ b/packages/web_benchmarks/testing/test_app/pubspec.yaml @@ -11,6 +11,9 @@ environment: dependencies: flutter: sdk: flutter + flutter_web_plugins: + sdk: flutter + go_router: ^14.2.7 dev_dependencies: flutter_lints: ^4.0.0