Commit 35047f4e authored by David Bienvenu's avatar David Bienvenu Committed by Commit Bot

Reland "Fix session restore after launching with PWA."

This is a reland of f7713274

Original change's description:
> Fix session restore after launching with PWA.
>
> The underlying issue is that the kProfilesLastActive pref gets
> cleared when Chrome is launched with a specific profile, which
> prevents Chrome from iterating over the last active profiles
> and calling session restore on each of them. This CL stops
> clearing the pref when launched with --app-id, and avoids
> restoring sessions for the last active profiles
> if launched with --app-id. Also, ignore browser opens and
> closes if the browser window is for a web app, in order to prevent
> kProfilesLastActive pref from getting overwritten.
>
> Bug: 1022795
> Change-Id: I350a54eaaf167dabb297b9bda9061fe965f0750e
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1984710
> Reviewed-by: Greg Thompson <grt@chromium.org>
> Reviewed-by: David Roger <droger@chromium.org>
> Commit-Queue: David Bienvenu <davidbienvenu@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#827876}

Bug: 1022795
Change-Id: Ia09ddd04dcd853f8bc7322ecf7767580f3076ccf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2546841
Commit-Queue: David Bienvenu <davidbienvenu@chromium.org>
Reviewed-by: default avatarGreg Thompson <grt@chromium.org>
Reviewed-by: default avatarDavid Roger <droger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#831041}
parent 3ab7cdd1
...@@ -343,10 +343,9 @@ Profile* CreatePrimaryProfile(const content::MainFunctionParams& parameters, ...@@ -343,10 +343,9 @@ Profile* CreatePrimaryProfile(const content::MainFunctionParams& parameters,
const base::FilePath& user_data_dir, const base::FilePath& user_data_dir,
const base::CommandLine& parsed_command_line) { const base::CommandLine& parsed_command_line) {
TRACE_EVENT0("startup", "ChromeBrowserMainParts::CreateProfile") TRACE_EVENT0("startup", "ChromeBrowserMainParts::CreateProfile")
base::Time start = base::Time::Now(); base::Time start = base::Time::Now();
bool set_last_used_profile = false; bool last_used_profile_set = false;
// If the browser is launched due to activation on Windows native // If the browser is launched due to activation on Windows native
// notification, the profile id encoded in the notification launch id should // notification, the profile id encoded in the notification launch id should
...@@ -356,22 +355,24 @@ Profile* CreatePrimaryProfile(const content::MainFunctionParams& parameters, ...@@ -356,22 +355,24 @@ Profile* CreatePrimaryProfile(const content::MainFunctionParams& parameters,
NotificationLaunchId::GetNotificationLaunchProfileId(parsed_command_line); NotificationLaunchId::GetNotificationLaunchProfileId(parsed_command_line);
if (!last_used_profile_id.empty()) { if (!last_used_profile_id.empty()) {
profiles::SetLastUsedProfile(last_used_profile_id); profiles::SetLastUsedProfile(last_used_profile_id);
set_last_used_profile = true; last_used_profile_set = true;
} }
#endif // defined(OS_WIN) #endif // defined(OS_WIN)
bool profile_dir_specified = bool profile_dir_specified =
profiles::IsMultipleProfilesEnabled() && profiles::IsMultipleProfilesEnabled() &&
parsed_command_line.HasSwitch(switches::kProfileDirectory); parsed_command_line.HasSwitch(switches::kProfileDirectory);
if (!set_last_used_profile && profile_dir_specified) { if (!last_used_profile_set && profile_dir_specified) {
profiles::SetLastUsedProfile( profiles::SetLastUsedProfile(
parsed_command_line.GetSwitchValueASCII(switches::kProfileDirectory)); parsed_command_line.GetSwitchValueASCII(switches::kProfileDirectory));
set_last_used_profile = true; last_used_profile_set = true;
} }
if (set_last_used_profile) { if (last_used_profile_set &&
!parsed_command_line.HasSwitch(switches::kAppId)) {
// Clear kProfilesLastActive since the user only wants to launch a specific // Clear kProfilesLastActive since the user only wants to launch a specific
// profile. // profile. Don't clear it if the user launched a web app, in order to not
// break any subsequent multi-profile session restore.
ListPrefUpdate update(g_browser_process->local_state(), ListPrefUpdate update(g_browser_process->local_state(),
prefs::kProfilesLastActive); prefs::kProfilesLastActive);
base::ListValue* profile_list = update.Get(); base::ListValue* profile_list = update.Get();
...@@ -395,7 +396,7 @@ Profile* CreatePrimaryProfile(const content::MainFunctionParams& parameters, ...@@ -395,7 +396,7 @@ Profile* CreatePrimaryProfile(const content::MainFunctionParams& parameters,
#else #else
profile = GetStartupProfile(user_data_dir, parsed_command_line); profile = GetStartupProfile(user_data_dir, parsed_command_line);
if (!profile && !set_last_used_profile) if (!profile && !last_used_profile_set)
profile = GetFallbackStartupProfile(); profile = GetFallbackStartupProfile();
if (!profile) { if (!profile) {
...@@ -1612,9 +1613,12 @@ int ChromeBrowserMainParts::PreMainMessageLoopRunImpl() { ...@@ -1612,9 +1613,12 @@ int ChromeBrowserMainParts::PreMainMessageLoopRunImpl() {
#if !defined(OS_CHROMEOS) #if !defined(OS_CHROMEOS)
// On ChromeOS multiple profiles doesn't apply, and will break if we load // On ChromeOS multiple profiles doesn't apply, and will break if we load
// them this early as the cryptohome hasn't yet been mounted (which happens // them this early as the cryptohome hasn't yet been mounted (which happens
// only once we log in). // only once we log in). And if we're launching a web app, we don't want to
last_opened_profiles = // restore the last opened profiles.
g_browser_process->profile_manager()->GetLastOpenedProfiles(); if (!parsed_command_line().HasSwitch(switches::kAppId)) {
last_opened_profiles =
g_browser_process->profile_manager()->GetLastOpenedProfiles();
}
#endif // defined(OS_CHROMEOS) #endif // defined(OS_CHROMEOS)
// This step is costly and is already measured in // This step is costly and is already measured in
......
...@@ -681,7 +681,8 @@ void ChromeBrowserMainPartsWin::PostBrowserStart() { ...@@ -681,7 +681,8 @@ void ChromeBrowserMainPartsWin::PostBrowserStart() {
// complete run of the Chrome Cleanup tool. If post-cleanup settings reset is // complete run of the Chrome Cleanup tool. If post-cleanup settings reset is
// enabled, we delay checks for settings reset prompt until the scheduled // enabled, we delay checks for settings reset prompt until the scheduled
// reset is finished. // reset is finished.
if (safe_browsing::PostCleanupSettingsResetter::IsEnabled()) { if (safe_browsing::PostCleanupSettingsResetter::IsEnabled() &&
!parsed_command_line().HasSwitch(switches::kAppId)) {
// Using last opened profiles, because we want to find reset the profile // Using last opened profiles, because we want to find reset the profile
// that was open in the last Chrome run, which may not be open yet in // that was open in the last Chrome run, which may not be open yet in
// the current run. // the current run.
......
...@@ -1949,7 +1949,7 @@ void ProfileManager::OnBrowserOpened(Browser* browser) { ...@@ -1949,7 +1949,7 @@ void ProfileManager::OnBrowserOpened(Browser* browser) {
DCHECK(profile); DCHECK(profile);
bool is_ephemeral = bool is_ephemeral =
profile->GetPrefs()->GetBoolean(prefs::kForceEphemeralProfiles); profile->GetPrefs()->GetBoolean(prefs::kForceEphemeralProfiles);
if (!profile->IsOffTheRecord() && !is_ephemeral && if (!profile->IsOffTheRecord() && !is_ephemeral && !browser->is_type_app() &&
++browser_counts_[profile] == 1) { ++browser_counts_[profile] == 1) {
active_profiles_.push_back(profile); active_profiles_.push_back(profile);
SaveActiveProfiles(); SaveActiveProfiles();
...@@ -1964,7 +1964,8 @@ void ProfileManager::OnBrowserOpened(Browser* browser) { ...@@ -1964,7 +1964,8 @@ void ProfileManager::OnBrowserOpened(Browser* browser) {
void ProfileManager::OnBrowserClosed(Browser* browser) { void ProfileManager::OnBrowserClosed(Browser* browser) {
Profile* profile = browser->profile(); Profile* profile = browser->profile();
DCHECK(profile); DCHECK(profile);
if (!profile->IsOffTheRecord() && --browser_counts_[profile] == 0) { if (!profile->IsOffTheRecord() && !browser->is_type_app() &&
--browser_counts_[profile] == 0) {
active_profiles_.erase( active_profiles_.erase(
std::find(active_profiles_.begin(), active_profiles_.end(), profile)); std::find(active_profiles_.begin(), active_profiles_.end(), profile));
if (!closing_all_browsers_) if (!closing_all_browsers_)
......
...@@ -133,6 +133,8 @@ class StartupBrowserCreator { ...@@ -133,6 +133,8 @@ class StartupBrowserCreator {
CommandLineTab); CommandLineTab);
FRIEND_TEST_ALL_PREFIXES(web_app::WebAppEngagementBrowserTest, FRIEND_TEST_ALL_PREFIXES(web_app::WebAppEngagementBrowserTest,
CommandLineWindow); CommandLineWindow);
FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest,
LastUsedProfilesWithWebApp);
bool ProcessCmdLineImpl(const base::CommandLine& command_line, bool ProcessCmdLineImpl(const base::CommandLine& command_line,
const base::FilePath& cur_dir, const base::FilePath& cur_dir,
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "base/base_switches.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
...@@ -15,6 +16,7 @@ ...@@ -15,6 +16,7 @@
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h" #include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread_restrictions.h" #include "base/threading/thread_restrictions.h"
#include "build/branding_buildflags.h" #include "build/branding_buildflags.h"
...@@ -27,7 +29,9 @@ ...@@ -27,7 +29,9 @@
#include "chrome/browser/extensions/launch_util.h" #include "chrome/browser/extensions/launch_util.h"
#include "chrome/browser/first_run/first_run.h" #include "chrome/browser/first_run/first_run.h"
#include "chrome/browser/infobars/infobar_service.h" #include "chrome/browser/infobars/infobar_service.h"
#include "chrome/browser/installable/installable_metrics.h"
#include "chrome/browser/lifetime/application_lifetime.h" #include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/prefs/chrome_pref_service_factory.h"
#include "chrome/browser/prefs/session_startup_pref.h" #include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_attributes_entry.h" #include "chrome/browser/profiles/profile_attributes_entry.h"
...@@ -50,8 +54,13 @@ ...@@ -50,8 +54,13 @@
#include "chrome/browser/ui/startup/startup_tab_provider.h" #include "chrome/browser/ui/startup/startup_tab_provider.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h" #include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/web_applications/components/install_finalizer.h"
#include "chrome/browser/web_applications/components/install_manager.h"
#include "chrome/browser/web_applications/components/web_application_info.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/common/buildflags.h" #include "chrome/common/buildflags.h"
#include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h" #include "chrome/common/pref_names.h"
...@@ -71,6 +80,7 @@ ...@@ -71,6 +80,7 @@
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h" #include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"
#include "content/public/test/test_navigation_observer.h" #include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h" #include "content/public/test/test_utils.h"
#include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_registry.h"
...@@ -113,6 +123,11 @@ using extensions::Extension; ...@@ -113,6 +123,11 @@ using extensions::Extension;
namespace { namespace {
#if !defined(OS_CHROMEOS) #if !defined(OS_CHROMEOS)
const char kAppId[] = "dofnemchnjfeendjmdhaldenaiabpiad";
const char kAppName[] = "Test App";
const char kStartUrl[] = "https://test.com";
// Check that there are two browsers. Find the one that is not |browser|. // Check that there are two browsers. Find the one that is not |browser|.
Browser* FindOneOtherBrowser(Browser* browser) { Browser* FindOneOtherBrowser(Browser* browser) {
// There should only be one other browser. // There should only be one other browser.
...@@ -1174,6 +1189,184 @@ IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorTest, ...@@ -1174,6 +1189,184 @@ IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorTest,
ASSERT_EQ(1u, chrome::GetBrowserCount(profile2)); ASSERT_EQ(1u, chrome::GetBrowserCount(profile2));
} }
// An observer that returns back to test code after a new browser is added to
// the BrowserList.
class BrowserAddedObserver : public BrowserListObserver {
public:
BrowserAddedObserver() { BrowserList::AddObserver(this); }
~BrowserAddedObserver() override { BrowserList::RemoveObserver(this); }
Browser* Wait() {
run_loop_.Run();
return browser_;
}
protected:
// BrowserListObserver:
void OnBrowserAdded(Browser* browser) override {
browser_ = browser;
run_loop_.Quit();
}
private:
Browser* browser_ = nullptr;
base::RunLoop run_loop_;
};
class StartupBrowserWithWebAppTest : public StartupBrowserCreatorTest {
protected:
StartupBrowserWithWebAppTest() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
StartupBrowserCreatorTest::SetUpCommandLine(command_line);
if (GetTestPreCount() == 1) {
// Load an app with launch.container = 'window'.
command_line->AppendSwitchASCII(switches::kAppId, kAppId);
command_line->AppendSwitchASCII(switches::kProfileDirectory, "Default");
}
}
web_app::WebAppProvider& provider() {
return *web_app::WebAppProvider::Get(profile());
}
};
IN_PROC_BROWSER_TEST_F(StartupBrowserWithWebAppTest,
PRE_PRE_LastUsedProfilesWithWebApp) {
// Simulate a browser restart by creating the profiles in the PRE_PRE part.
ProfileManager* profile_manager = g_browser_process->profile_manager();
ASSERT_TRUE(embedded_test_server()->Start());
// Create two profiles.
base::FilePath dest_path = profile_manager->user_data_dir();
Profile* profile1 = nullptr;
Profile* profile2 = nullptr;
{
base::ScopedAllowBlockingForTesting allow_blocking;
profile1 = profile_manager->GetProfile(
dest_path.Append(FILE_PATH_LITERAL("New Profile 1")));
ASSERT_TRUE(profile1);
profile2 = profile_manager->GetProfile(
dest_path.Append(FILE_PATH_LITERAL("New Profile 2")));
ASSERT_TRUE(profile2);
}
DisableWelcomePages({profile1, profile2});
// Open some urls with the browsers, and close them.
Browser* browser1 = Browser::Create({Browser::TYPE_NORMAL, profile1, true});
chrome::NewTab(browser1);
ui_test_utils::NavigateToURL(browser1,
embedded_test_server()->GetURL("/title1.html"));
Browser* browser2 = Browser::Create({Browser::TYPE_NORMAL, profile2, true});
chrome::NewTab(browser2);
ui_test_utils::NavigateToURL(browser2,
embedded_test_server()->GetURL("/title2.html"));
// Set startup preferences for the 2 profiles to restore last session.
SessionStartupPref pref1(SessionStartupPref::LAST);
SessionStartupPref::SetStartupPref(profile1, pref1);
SessionStartupPref pref2(SessionStartupPref::LAST);
SessionStartupPref::SetStartupPref(profile2, pref2);
profile1->GetPrefs()->CommitPendingWrite();
profile2->GetPrefs()->CommitPendingWrite();
// Install a web app that we will launch from the command line in
// the PRE test.
web_app::WebAppProviderBase* const provider =
web_app::WebAppProviderBase::GetProviderBase(browser()->profile());
web_app::InstallFinalizer& web_app_finalizer = provider->install_finalizer();
web_app::InstallFinalizer::FinalizeOptions options;
options.install_source = WebappInstallSource::OMNIBOX_INSTALL_ICON;
// Install web app set to open as a tab.
{
web_app_finalizer.RemoveLegacyInstallFinalizerForTesting();
base::RunLoop run_loop;
WebApplicationInfo info;
info.start_url = GURL(kStartUrl);
info.title = base::UTF8ToUTF16(kAppName);
info.open_as_window = true;
web_app_finalizer.FinalizeInstall(
info, options,
base::BindLambdaForTesting(
[&](const web_app::AppId& app_id, web_app::InstallResultCode code) {
EXPECT_EQ(app_id, kAppId);
EXPECT_EQ(code, web_app::InstallResultCode::kSuccessNewInstall);
run_loop.Quit();
}));
run_loop.Run();
EXPECT_EQ(provider->registrar().GetAppUserDisplayMode(kAppId),
blink::mojom::DisplayMode::kStandalone);
}
}
IN_PROC_BROWSER_TEST_F(StartupBrowserWithWebAppTest,
PRE_LastUsedProfilesWithWebApp) {
BrowserAddedObserver added_observer;
content::RunAllTasksUntilIdle();
// Launching with an app opens the app window via a task, so the test
// might start before SelectFirstBrowser is called.
if (!browser()) {
added_observer.Wait();
SelectFirstBrowser();
}
ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
// An app window should have been launched.
EXPECT_TRUE(browser()->is_type_app());
CloseBrowserAsynchronously(browser());
}
IN_PROC_BROWSER_TEST_F(StartupBrowserWithWebAppTest,
LastUsedProfilesWithWebApp) {
ProfileManager* profile_manager = g_browser_process->profile_manager();
base::FilePath dest_path = profile_manager->user_data_dir();
Profile* profile1 = nullptr;
Profile* profile2 = nullptr;
{
base::ScopedAllowBlockingForTesting allow_blocking;
profile1 = profile_manager->GetProfile(
dest_path.Append(FILE_PATH_LITERAL("New Profile 1")));
ASSERT_TRUE(profile1);
profile2 = profile_manager->GetProfile(
dest_path.Append(FILE_PATH_LITERAL("New Profile 2")));
ASSERT_TRUE(profile2);
}
while (SessionRestore::IsRestoring(profile1) ||
SessionRestore::IsRestoring(profile2)) {
base::RunLoop().RunUntilIdle();
}
// The last open sessions should be restored.
EXPECT_TRUE(profile1->restored_last_session());
EXPECT_TRUE(profile2->restored_last_session());
Browser* new_browser = nullptr;
ASSERT_EQ(1u, chrome::GetBrowserCount(profile1));
new_browser = FindOneOtherBrowserForProfile(profile1, nullptr);
ASSERT_TRUE(new_browser);
TabStripModel* tab_strip = new_browser->tab_strip_model();
EXPECT_EQ("/title1.html", tab_strip->GetWebContentsAt(0)->GetURL().path());
ASSERT_EQ(1u, chrome::GetBrowserCount(profile2));
new_browser = FindOneOtherBrowserForProfile(profile2, nullptr);
ASSERT_TRUE(new_browser);
tab_strip = new_browser->tab_strip_model();
EXPECT_EQ("/title2.html", tab_strip->GetWebContentsAt(0)->GetURL().path());
}
#endif // !defined(OS_CHROMEOS) #endif // !defined(OS_CHROMEOS)
class StartupBrowserCreatorExtensionsCheckupExperimentTest class StartupBrowserCreatorExtensionsCheckupExperimentTest
......
...@@ -27,18 +27,6 @@ const char kAppId[] = "dofnemchnjfeendjmdhaldenaiabpiad"; ...@@ -27,18 +27,6 @@ const char kAppId[] = "dofnemchnjfeendjmdhaldenaiabpiad";
const char kAppName[] = "Test App"; const char kAppName[] = "Test App";
const char kStartUrl[] = "https://test.com"; const char kStartUrl[] = "https://test.com";
size_t GetTestPreCount() {
constexpr base::StringPiece kPreTestPrefix = "PRE_";
base::StringPiece test_name =
testing::UnitTest::GetInstance()->current_test_info()->name();
size_t count = 0;
while (test_name.find(kPreTestPrefix, kPreTestPrefix.size() * count) ==
kPreTestPrefix.size() * count) {
++count;
}
return count;
}
} // namespace } // namespace
namespace web_app { namespace web_app {
......
...@@ -1605,7 +1605,13 @@ const char kNotificationPermissionActions[] = ...@@ -1605,7 +1605,13 @@ const char kNotificationPermissionActions[] =
// Directory of the last profile used. // Directory of the last profile used.
const char kProfileLastUsed[] = "profile.last_used"; const char kProfileLastUsed[] = "profile.last_used";
// List of directories of the profiles last active. // List of directories of the profiles last active in browser windows. It does
// not include profiles active in app windows. When a browser window is opened,
// if it's the only browser window open in the profile, its profile is added to
// this list. When a browser window is closed, and there are no other browser
// windows open in the profile, its profile is removed from this list. When
// Chrome is launched with --session-restore, each of the profiles in this list
// have their sessions restored.
const char kProfilesLastActive[] = "profile.last_active_profiles"; const char kProfilesLastActive[] = "profile.last_active_profiles";
// Total number of profiles created for this Chrome build. Used to tag profile // Total number of profiles created for this Chrome build. Used to tag profile
......
...@@ -377,6 +377,19 @@ void InProcessBrowserTest::TearDown() { ...@@ -377,6 +377,19 @@ void InProcessBrowserTest::TearDown() {
#endif #endif
} }
// static
size_t InProcessBrowserTest::GetTestPreCount() {
constexpr base::StringPiece kPreTestPrefix = "PRE_";
base::StringPiece test_name =
testing::UnitTest::GetInstance()->current_test_info()->name();
size_t count = 0;
while (base::StartsWith(test_name, kPreTestPrefix)) {
++count;
test_name = test_name.substr(kPreTestPrefix.size());
}
return count;
}
void InProcessBrowserTest::SelectFirstBrowser() { void InProcessBrowserTest::SelectFirstBrowser() {
const BrowserList* browser_list = BrowserList::GetInstance(); const BrowserList* browser_list = BrowserList::GetInstance();
if (!browser_list->empty()) if (!browser_list->empty())
......
...@@ -150,6 +150,11 @@ class InProcessBrowserTest : public content::BrowserTestBase { ...@@ -150,6 +150,11 @@ class InProcessBrowserTest : public content::BrowserTestBase {
global_browser_set_up_function_ = set_up_function; global_browser_set_up_function_ = set_up_function;
} }
// Counts the number of "PRE_" prefixes in the test name. This is used to
// differentiate between different PRE tests in browser test constructors
// and setup functions.
static size_t GetTestPreCount();
// Returns the browser created by BrowserMain(). // Returns the browser created by BrowserMain().
// If no browser is created in BrowserMain(), this will return nullptr unless // If no browser is created in BrowserMain(), this will return nullptr unless
// another browser instance is created at a later time and // another browser instance is created at a later time and
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment