From aea660d5cd592bdb05280da87acd7738d27dceca Mon Sep 17 00:00:00 2001 From: Reynaldo Date: Fri, 25 Jul 2025 12:43:06 -0300 Subject: [PATCH] feat(tool): Respect user-data-dir flag from web-browser-flag (#169445) ### Respect user-data-dir flag from web-browser-flag Currently, it's already possible to pass `web-browser-flag` when launching Chrome, but the `user-data-dir` flag doesn't work as expected, and there are some reasons for that. In the implementation made in [PR #104935](https://github.com/flutter/flutter/pull/104935), the `web-browser-flag` is appended at the end of the Chrome launch arguments. For most scenarios, this works fine, as demonstrated in the Chrome unit test below: https://source.chromium.org/chromium/chromium/src/+/main:base/command_line_unittest.cc ``` TEST(CommandLineTest, MultipleSameSwitch) { const CommandLine::CharType* argv[] = { FILE_PATH_LITERAL("program"), FILE_PATH_LITERAL("--foo=one"), // --foo first time FILE_PATH_LITERAL("-baz"), FILE_PATH_LITERAL("--foo=two") // --foo second time }; CommandLine cl(std::size(argv), argv); EXPECT_TRUE(cl.HasSwitch("foo")); EXPECT_TRUE(cl.HasSwitch("baz")); EXPECT_EQ("two", cl.GetSwitchValueASCII("foo")); } ``` In this scenario, the parser will consider the last occurrence of a flag. However, this behavior does not apply to certain flags, because Chrome processes some of them based on the first occurrence, not the last. This is the case for `--user-data-dir`, which is parsed very early during Chrome startup. The proposed code checks whether `--user-data-dir` was provided via `web-browser-flag`, and if so, it uses that value instead of the default `%temp%\flutter_tools_chrome_device.xpto` temporary directory. This also resolve this comment: https://github.com/flutter/flutter/pull/104935#issuecomment-1694486157 Example: `launch.json` ``` { "version": "0.2.0", "configurations": [ { "name": "flutter", "request": "launch", "type": "dart" }, { "name": "flutter (profile mode)", "request": "launch", "type": "dart", "flutterMode": "profile" }, { "name": "flutter (release mode)", "request": "launch", "type": "dart", "flutterMode": "release" }, { "name": "Flutter for web (hot reloadable)", "type": "dart", "request": "launch", "program": "lib/main.dart", "args": [ "-d", "chrome", "--web-port=5000", "--web-experimental-hot-reload", "--web-browser-flag=--user-data-dir=D:\\_Desenv\\flutter_tests\\chrome_profile" ] } ] } ``` `chrome://version` | Before | After | |--------|--------| | ![before](https://github.com/user-attachments/assets/b3f33419-fbf1-446e-a471-745df2e8c4f6) | ![after](https://github.com/user-attachments/assets/ea74a203-c269-4ce1-8a9b-f97e9a4cdc78) | Folder | Before | After | |--------|--------| | ![before_files](https://github.com/user-attachments/assets/8522e60b-d0eb-4b66-a4b6-c2d6ed34c15b) | ![after_files](https://github.com/user-attachments/assets/ff94b498-b319-474c-89e5-c55f6e1c4383) | ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../flutter_tools/lib/src/web/chrome.dart | 30 +++++++++++++++++-- .../test/web.shard/chrome_test.dart | 24 +++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/flutter_tools/lib/src/web/chrome.dart b/packages/flutter_tools/lib/src/web/chrome.dart index 710f5048b57..3314a803fcc 100644 --- a/packages/flutter_tools/lib/src/web/chrome.dart +++ b/packages/flutter_tools/lib/src/web/chrome.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:process/process.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart' hide StackTrace; @@ -145,6 +146,31 @@ class ChromiumLauncher { /// The executable this launcher will use. String findExecutable() => _browserFinder(_platform, _fileSystem); + /// Creates a user data directory for Chrome based on provided flags or creates a temporary one. + /// + /// This method handles the creation of Chrome's user data directory in two ways: + /// 1. If webBrowserFlags contains a --user-data-dir flag, it uses that directory + /// 2. Otherwise, it creates a temporary directory in the system's temp location + /// + /// The user data directory is where Chrome stores user preferences, cookies, + /// and other session data. Using a temporary directory ensures a clean state + /// for each launch, while allowing custom directories through flags for + /// persistent configurations. + Directory _createUserDataDirectory(List webBrowserFlags) { + if (webBrowserFlags.isNotEmpty) { + final String? userDataDirFlag = webBrowserFlags.firstWhereOrNull( + (String flag) => flag.startsWith('--user-data-dir='), + ); + + if (userDataDirFlag != null) { + final Directory userDataDir = _fileSystem.directory(userDataDirFlag.split('=')[1]); + webBrowserFlags.remove(userDataDirFlag); + return userDataDir; + } + } + return _fileSystem.systemTempDirectory.createTempSync('flutter_tools_chrome_device.'); + } + /// Launch a Chromium browser to a particular `host` page. /// /// [headless] defaults to false, and controls whether we open a headless or @@ -189,9 +215,7 @@ class ChromiumLauncher { } } - final Directory userDataDir = _fileSystem.systemTempDirectory.createTempSync( - 'flutter_tools_chrome_device.', - ); + final Directory userDataDir = _createUserDataDirectory(webBrowserFlags); if (cacheDir != null) { // Seed data dir with previous state. diff --git a/packages/flutter_tools/test/web.shard/chrome_test.dart b/packages/flutter_tools/test/web.shard/chrome_test.dart index c1a5a871be9..12ed6bed7c1 100644 --- a/packages/flutter_tools/test/web.shard/chrome_test.dart +++ b/packages/flutter_tools/test/web.shard/chrome_test.dart @@ -931,6 +931,30 @@ void main() { await chrome.close(); }, ); + + testWithoutContext('respects custom user data directory flag', () async { + const String customUserDataDir = '/custom/chrome/data/dir'; + processManager.addCommand( + const FakeCommand( + command: [ + 'example_chrome', + '--user-data-dir=$customUserDataDir', + '--remote-debugging-port=12345', + ...kChromeArgs, + 'example_url', + ], + stderr: kDevtoolsStderr, + ), + ); + + await expectReturnsNormallyLater( + chromeLauncher.launch( + 'example_url', + skipCheck: true, + webBrowserFlags: ['--user-data-dir=$customUserDataDir'], + ), + ); + }); } /// Fake chrome connection that fails to get tabs a few times.