Chromium Startup

This morning, a Microsoft Edge customer contacted support to ask how they could launch a URL in a browser window at a particular size. I responded that they could simply use the --window-size="800,600" command line argument. The customer quickly complained that this only seemed to work if they also specified a non-default path in the --user-data-dir command line argument, such that the URL opened in a different profile.

As I mentioned back in my post about Edge Command Line Arguments, most command-line arguments are ignored if there’s already a running instance of Edge, and this is one of them. Even if you also pass the --new-window argument, Edge simply opens the URL you’ve supplied inside a new window with the same size and location as the original window.

Now, in many cases, this is a reasonable limitation. Many of the command line arguments you can pass into Chromium have a global impact, and having inbound arguments change the behavior of an already-running browser instance would introduce an impractical level of complexity to the code and user experience. Similarly, there’s an assumption in the Chromium code that only one browser instance will interact with a single profile folder at one time, so we could not simply allow multiple browser instances with different behaviors to use a single profile in parallel.

In this particular case, it feels reasonable that if a user passes both --new-window and either --window-size or --window-position (or all three), the resulting window will have the expected dimensions, even if there was already one or more browser windows open in the browsing session. Because the arguments do not impact more than the newly-created window, there’s none of the compexity of trying to change the behavior of any other part of the already-running browser. I filed a bug suggesting that we ought to look at enabling this.

In the course of investigating this bug, I had the opportunity to learn a bit more about how Chromium handles the invocation of a URL when there’s already a running instance. When it first starts, the new browser process searches for an existing hidden message window of Class Chrome_MessageWindow with a Caption matching the user-data-dir of the new process.

If it fails to find one, it creates a new hidden messaging window (using a mutex to combat race conditions) with the correct caption for any future processes to find.

However, if the code did find an existing messaging window, there’s a call to a AttemptToNotifyRunningChrome function that sends a WM_COPYDATA message to pass along the command line from the (soon-to-exit) new process.

In the unlikely event that the existing process fails to accept the message (e.g. because it is hung), the user will be prompted to kill the existing process so that the new process can handle the navigation.

This code is surprisingly simple, and feels very familiar– the startup code inside Fiddler is almost identical except it’s implemented in C#.

In our customer scenario, we see that the existing browser instance correctly gets the command line from the new process:

[11824:4812:0615/] ProcessCommandLineAlreadyRunning "C:\src\c\src\out\default\chrome.exe" --new-window --window-size=400,510 --window-position=123,34 --flag-switches-begin --flag-switches-end

And shortly after that, there’s the expected call to the UpdateWindowBoundsAndShowStateFromCommandLine function that sets the window size and position from any arguments passed on the command line.

The stack trace of that call looks like


Unfortunately, when we look at the GetSavedWindowBoundsAndShowState function we see the problem:

void GetSavedWindowBoundsAndShowState(const Browser* browser,
                                      gfx::Rect* bounds,
                                      ui::WindowShowState* show_state) {
  const base::CommandLine& parsed_command_line =

internal::UpdateWindowBoundsAndShowStateFromCommandLine(parsed_command_line, bounds, show_state);

As you can see, the call passes the command line string representing the current (preexisting) process, rather than the command line that was passed from the newly started process. So, the new window ends up with the same size and position information from the original window.

To fix this, we’ll need to restructure the calls such that when we’re handling a command line passed to us from another process through ProcessCommandLineAlreadyRunning, we use the WM_COPYDATA-passed command line when setting the window size and position.


Published by ericlaw

Impatient optimist. Dad. Author/speaker. Created Fiddler & SlickRun. PM @ Microsoft 2001-2012, and 2018-2022, working on Office, IE, and Edge. Now a SWE on Microsoft Defender Web Protection. My words are my own, I do not speak for any other entity.

5 thoughts on “Chromium Startup

  1. With Visual Studio Code I can launch a new instance of Edge that has the –allow-file-access-from-files command line argument, without first having to close any existing instances of Edge. I need this setting to debug projects that use ES6 JavaScript modules, in order to bypass CORS.

    When I use any other way to start Edge I do indeed have to close all running Edge processes first.

    So far I have been unable to figure out how vscode manages to do what everyone says is impossible.

    1. How, precisely, are you launching Edge from VS Code? If you watch in Process Explorer for Process Creation events, what’s the exact command line supplied to Edge?

  2. I launch Edge from vscode by clicking the “start debugging” button (or pressing F5). This is the launch.json file:
    “version”: “0.2.0”,
    “configurations”: [
    “type”: “msedge”,
    “request”: “launch”,
    “name”: “leetcode for mjsModel”,
    “file”: “${workspaceFolder}/index.html”,
    “runtimeArgs”: [
    // without the extra argument F5 fails on CORS
    // “–disable-web-security”, // overkill, and risky

    Once the new instance is launched, I get the Edge command line in edge://version/
    “C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe” –disable-background-networking –disable-background-timer-throttling –disable-backgrounding-occluded-windows –disable-breakpad –disable-client-side-phishing-detection –disable-default-apps –disable-dev-shm-usage –disable-renderer-backgrounding –disable-sync –metrics-recording-only –no-first-run –no-default-browser-check –user-data-dir=“c:\Users\Admin\AppData\Roaming\Code\User\workspaceStorage\5fc965073e6c487f7d349c3cdf26ef58\ms-vscode.js-debug\.profile” –allow-file-access-from-files –remote-debugging-pipe –flag-switches-begin –flag-switches-end about:blank

    1. Chromium merges process instances based on the User Data Directory (where the profiles are loaded from). When you specify a |–user-data-dir| parameter in the command line as VSCode does, that new invocation will not merge with your already running browser instances that use the default User Data Directory.

  3. Thank you very much for the explanation; I had not realised the importance of the User Data Directory.
    The link to the list of Chromium Command Line Switches in your earlier post was also much appreciated.
    With hindsight it would have been useful if in this list the entry for –allow-file-access-from-files had mentioned that the switch has no effect unless –user-data-dir is also present.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: