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.

<!-- Links -->
[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
This commit is contained in:
Reynaldo 2025-07-25 12:43:06 -03:00 committed by GitHub
parent c1c38adec6
commit aea660d5cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 51 additions and 3 deletions

View File

@ -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<String> 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.

View File

@ -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: <String>[
'example_chrome',
'--user-data-dir=$customUserDataDir',
'--remote-debugging-port=12345',
...kChromeArgs,
'example_url',
],
stderr: kDevtoolsStderr,
),
);
await expectReturnsNormallyLater(
chromeLauncher.launch(
'example_url',
skipCheck: true,
webBrowserFlags: <String>['--user-data-dir=$customUserDataDir'],
),
);
});
}
/// Fake chrome connection that fails to get tabs a few times.