Wire up BraveOrigin startup dialog to browser launch (#34555)

* Add BraveOrigin startup dialog view

Adds BraveOriginStartupView, a standalone modal window that hosts
the startup WebUI dialog. Manages system profile creation, web
contents lifecycle, and buy window navigation.

* Address review feedback for BraveOrigin startup view

- Use WidgetDelegate instead of deprecated WidgetDelegateView
- Use WebView(profile) + LoadInitialURL instead of manual WebContents
- Remove redundant GetAccessibleWindowTitle/CalculatePreferredSize overrides
- Simplify SetTitle to use message ID directly
- Fix g_startup_view leak with unique_ptr + cleanup in WindowClosing
- Remove chromium_src WidgetDelegateView friend class override

* Wire up BraveOrigin startup dialog to browser launch

Intercepts StartupBrowserCreator::Start to show the purchase
validation dialog before any browser window opens. Blocks Mac
app commands while the dialog is showing.

* Address review feedback and add test coverage for startup view

Refactor BraveOriginStartupView to use a Delegate interface instead of
raw callbacks, making the code more testable and readable. Move the
--test-type guard into ShouldShowDialog() and add
SetShouldShowDialogForTesting() for browser test support. Use
__VA_ARGS__ in the startup_browser_creator.h macros. Add unit tests
covering ShouldShowDialog with various pref/SKU credential states.

* Add browser tests for BraveOrigin startup dialog

* Add integration tests for StartupBrowserCreator::Start override

Exercise the real startup interception logic that shows the
BraveOrigin dialog, covering both validation and close paths.
This commit is contained in:
Brian R. Bondy
2026-03-13 08:25:32 -04:00
committed by GitHub
parent 545538e6d7
commit a8214ba5c9
14 changed files with 761 additions and 76 deletions
+44 -1
View File
@@ -12,6 +12,7 @@
#include "brave/app/brave_command_ids.h"
#include "brave/browser/brave_browser_features.h"
#include "brave/browser/ui/browser_commands.h"
#include "brave/components/brave_origin/buildflags/buildflags.h"
#include "brave/components/tor/buildflags/buildflags.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/browser_process.h"
@@ -23,6 +24,10 @@
#include "components/prefs/pref_member.h"
#include "ui/base/l10n/l10n_util_mac.h"
#if BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
#include "brave/browser/ui/views/brave_origin/brave_origin_startup_view.h"
#endif
#if BUILDFLAG(ENABLE_TOR)
#include "brave/browser/tor/tor_profile_service_factory.h"
#include "brave/components/tor/pref_names.h"
@@ -73,8 +78,9 @@ class TorPrefObserver : public BooleanPrefMember {
} // namespace
@interface AppController (Brave)
// Expose method in chrome/..app_controller_mac.mm
// Expose methods in chrome/..app_controller_mac.mm
- (BOOL)canOpenNewBrowser;
- (void)commandDispatchUsingKeyModifiers:(id)sender;
@end
@interface BraveAppController () {
@@ -91,6 +97,43 @@ class TorPrefObserver : public BooleanPrefMember {
@end
@implementation BraveAppController
#if BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
- (BOOL)canOpenNewBrowser {
if (BraveOriginStartupView::IsShowing()) {
return NO;
}
return [super canOpenNewBrowser];
}
- (void)commandDispatch:(id)sender {
if (BraveOriginStartupView::IsShowing()) {
// Allow Quit through so the user can exit from the startup dialog.
if ([sender tag] == IDC_EXIT) {
BraveOriginStartupView::Hide();
}
return;
}
[super commandDispatch:sender];
}
- (void)commandDispatchUsingKeyModifiers:(id)sender {
if (BraveOriginStartupView::IsShowing()) {
return;
}
[super commandDispatchUsingKeyModifiers:sender];
}
- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
hasVisibleWindows:(BOOL)hasVisibleWindows {
if (BraveOriginStartupView::IsShowing()) {
return NO;
}
return [super applicationShouldHandleReopen:theApplication
hasVisibleWindows:hasVisibleWindows];
}
#endif // BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
- (void)mainMenuCreated {
[super mainMenuCreated];
@@ -2,8 +2,8 @@
<html dir="$i18n{textdirection}" lang="$i18n{language}">
<head>
<meta charset="utf-8">
<title>$i18n{braveOriginStartupTitle}</title>
<link rel="stylesheet" href="chrome://resources/brave/css/nala.css">
<title>$i18n{BRAVE_ORIGIN_STARTUP_TITLE}</title>
<link rel="stylesheet" href="chrome://resources/brave/leo/css/variables.css">
<link rel="stylesheet" href="chrome://resources/brave/css/reset.css">
<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="/components/app.css">
+4 -2
View File
@@ -387,8 +387,10 @@ if (enable_brave_education) {
}
if (is_brave_origin_branded) {
brave_chrome_browser_deps +=
[ "//brave/browser/ui/webui/brave_origin_startup" ]
brave_chrome_browser_deps += [
"//brave/browser/ui/views/brave_origin",
"//brave/browser/ui/webui/brave_origin_startup",
]
}
if (enable_ai_chat) {
+42
View File
@@ -33,3 +33,45 @@ source_set("brave_origin") {
"//url",
]
}
source_set("browser_tests") {
testonly = true
defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
sources = [ "brave_origin_startup_view_browsertest.cc" ]
deps = [
":brave_origin",
"//base",
"//brave/components/brave_origin:pref_names",
"//brave/components/skus/browser",
"//chrome/browser",
"//chrome/browser:browser_process",
"//chrome/browser/profiles:profile",
"//chrome/browser/ui",
"//chrome/browser/ui/startup",
"//chrome/test:test_support_ui",
"//components/prefs",
"//content/public/browser",
"//content/test:test_support",
"//testing/gtest",
"//ui/views",
"//url",
]
}
source_set("unit_tests") {
testonly = true
sources = [ "brave_origin_startup_view_unittest.cc" ]
deps = [
":brave_origin",
"//base",
"//brave/components/brave_origin:pref_names",
"//brave/components/skus/browser",
"//components/prefs:test_support",
"//testing/gtest",
]
}
@@ -7,6 +7,7 @@
#include <utility>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/strings/strcat.h"
@@ -25,6 +26,7 @@
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/common/content_switches.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/widget/widget.h"
@@ -34,9 +36,10 @@
namespace {
constexpr int kDialogWidth = 500;
constexpr int kDialogHeight = 600;
constexpr int kDialogHeight = 450;
BraveOriginStartupView* g_startup_view = nullptr;
std::optional<bool> g_should_show_dialog_override;
bool HasOriginSkuCredentials(PrefService* local_state) {
const auto& skus_state = local_state->GetDict(skus::prefs::kSkusState);
@@ -65,23 +68,33 @@ bool HasOriginSkuCredentials(PrefService* local_state) {
// static
bool BraveOriginStartupView::ShouldShowDialog(PrefService* local_state) {
if (g_should_show_dialog_override.has_value()) {
return *g_should_show_dialog_override;
}
// Skip the dialog when running under test infrastructure.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) {
return false;
}
return !local_state->GetBoolean(brave_origin::kOriginPurchaseValidated) ||
!HasOriginSkuCredentials(local_state);
}
// static
void BraveOriginStartupView::Show(
base::OnceClosure on_complete,
OpenExternalCallback open_external,
base::RepeatingClosure attempt_exit,
CreateProfilesCallback create_system_profile,
CreateProfilesCallback create_default_profile) {
void BraveOriginStartupView::SetShouldShowDialogForTesting( // IN-TEST
std::optional<bool> override) {
g_should_show_dialog_override = override;
}
// static
void BraveOriginStartupView::Show(base::OnceClosure on_complete,
std::unique_ptr<Delegate> delegate) {
if (g_startup_view) {
return;
}
g_startup_view = new BraveOriginStartupView(
std::move(on_complete), std::move(open_external), std::move(attempt_exit),
std::move(create_system_profile), std::move(create_default_profile));
g_startup_view =
new BraveOriginStartupView(std::move(on_complete), std::move(delegate));
g_startup_view->Display();
}
@@ -97,20 +110,21 @@ bool BraveOriginStartupView::IsShowing() {
return g_startup_view != nullptr;
}
// static
void BraveOriginStartupView::ValidateForTesting() { // IN-TEST
if (g_startup_view && g_startup_view->GetWidget()) {
g_startup_view->CloseAndProceed();
}
}
BraveOriginStartupView::BraveOriginStartupView(
base::OnceClosure on_complete,
OpenExternalCallback open_external,
base::RepeatingClosure attempt_exit,
CreateProfilesCallback create_system_profile,
CreateProfilesCallback create_default_profile)
std::unique_ptr<Delegate> delegate)
: keep_alive_(
std::make_unique<ScopedKeepAlive>(KeepAliveOrigin::USER_MANAGER_VIEW,
KeepAliveRestartOption::DISABLED)),
on_complete_(std::move(on_complete)),
open_external_(std::move(open_external)),
attempt_exit_(std::move(attempt_exit)),
create_system_profile_(std::move(create_system_profile)),
create_default_profile_(std::move(create_default_profile)) {
delegate_(std::move(delegate)),
on_complete_(std::move(on_complete)) {
SetHasWindowSizeControls(false);
SetTitle(IDS_BRAVE_ORIGIN_STARTUP_TITLE);
}
@@ -124,10 +138,10 @@ BraveOriginStartupView::~BraveOriginStartupView() {
void BraveOriginStartupView::Display() {
// Load both the system profile (for WebUI) and the default user profile
// (for SKU service, which requires a regular profile).
create_system_profile_.Run(
delegate_->CreateSystemProfile(
base::BindOnce(&BraveOriginStartupView::OnSystemProfileCreated,
weak_ptr_factory_.GetWeakPtr()));
create_default_profile_.Run(
delegate_->CreateDefaultProfile(
base::BindOnce(&BraveOriginStartupView::OnDefaultProfileCreated,
weak_ptr_factory_.GetWeakPtr()));
}
@@ -239,8 +253,8 @@ void BraveOriginStartupView::OpenBuyWindow() {
"account", brave_domains::ServicesEnvironment::STAGING),
"/?intent=checkout&product=origin"});
GURL gurl(url);
if (gurl.is_valid() && open_external_) {
open_external_.Run(gurl);
if (gurl.is_valid() && delegate_) {
delegate_->OpenExternal(gurl);
}
}
@@ -303,8 +317,14 @@ void BraveOriginStartupView::WindowClosing() {
std::unique_ptr<ScopedProfileKeepAlive>) {},
std::move(keep_alive_), std::move(profile_keep_alive_)));
if (!validated_) {
task_runner->PostTask(FROM_HERE, attempt_exit_);
if (!validated_ && delegate_) {
// Move delegate_ into the closure so it outlives `this` (which is deleted
// in WidgetIsZombie right after WindowClosing returns).
task_runner->PostTask(
FROM_HERE,
base::BindOnce(
[](std::unique_ptr<Delegate> delegate) { delegate->AttemptExit(); },
std::move(delegate_)));
}
}
@@ -11,6 +11,7 @@
static_assert(BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED));
#include <memory>
#include <optional>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
@@ -42,39 +43,56 @@ class BraveOriginStartupView : public views::WidgetDelegate,
public content::WebContentsDelegate,
public content::WebContentsObserver {
public:
using ProfileCallback = base::OnceCallback<void(Profile*)>;
// Delegate interface for external dependencies. Implemented by the browser
// startup code and easily mockable in tests.
class Delegate {
public:
virtual ~Delegate() = default;
// Opens |url| in the user's default external browser.
virtual void OpenExternal(const GURL& url) = 0;
// Exits the browser process.
virtual void AttemptExit() = 0;
// Asynchronously creates the system profile and passes it to |callback|.
virtual void CreateSystemProfile(ProfileCallback callback) = 0;
// Asynchronously creates the default user profile and passes it to
// |callback|.
virtual void CreateDefaultProfile(ProfileCallback callback) = 0;
};
// Returns true if the startup dialog should be shown (purchase not yet
// validated or SKU credentials missing).
// validated or SKU credentials missing). Also returns false when running
// under test infrastructure (--test-type flag).
static bool ShouldShowDialog(PrefService* local_state);
// Override ShouldShowDialog() result for testing. Pass std::nullopt to
// remove the override and restore normal behavior.
static void SetShouldShowDialogForTesting(std::optional<bool> override);
// Shows the startup dialog. |on_complete| is called when the user has
// been validated and the dialog closes, to continue the startup flow.
// |open_external| is called to open URLs in the default browser.
// |attempt_exit| is called to terminate the browser when the dialog is
// closed without validation.
using OpenExternalCallback = base::RepeatingCallback<void(const GURL&)>;
using ProfileCallback = base::OnceCallback<void(Profile*)>;
using CreateProfilesCallback = base::RepeatingCallback<void(ProfileCallback)>;
// |on_complete| runs when the user validates; |open_external| opens URLs
// externally; |attempt_exit| exits the browser; |create_system_profile| and
// |create_default_profile| load the required profiles asynchronously.
// |delegate| provides external operations (opening URLs, exiting, creating
// profiles).
static void Show(base::OnceClosure on_complete,
OpenExternalCallback open_external,
base::RepeatingClosure attempt_exit,
CreateProfilesCallback create_system_profile,
CreateProfilesCallback create_default_profile);
std::unique_ptr<Delegate> delegate);
static void Hide();
static bool IsShowing();
// Simulates a successful validation for testing. Calls CloseAndProceed() on
// the current instance, triggering the on_complete callback.
static void ValidateForTesting(); // IN-TEST
BraveOriginStartupView(const BraveOriginStartupView&) = delete;
BraveOriginStartupView& operator=(const BraveOriginStartupView&) = delete;
private:
BraveOriginStartupView(base::OnceClosure on_complete,
OpenExternalCallback open_external,
base::RepeatingClosure attempt_exit,
CreateProfilesCallback create_system_profile,
CreateProfilesCallback create_default_profile);
std::unique_ptr<Delegate> delegate);
~BraveOriginStartupView() override;
void Display();
@@ -120,15 +138,12 @@ class BraveOriginStartupView : public views::WidgetDelegate,
std::unique_ptr<ScopedKeepAlive> keep_alive_;
std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive_;
std::unique_ptr<views::WebView> web_view_;
std::unique_ptr<Delegate> delegate_;
int profiles_loaded_count_ = 0;
bool validated_ = false;
base::OnceClosure on_complete_;
OpenExternalCallback open_external_;
base::RepeatingClosure attempt_exit_;
CreateProfilesCallback create_system_profile_;
CreateProfilesCallback create_default_profile_;
views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
@@ -0,0 +1,279 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/browser/ui/views/brave_origin/brave_origin_startup_view.h"
#include <optional>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/test/bind.h"
#include "base/test/run_until.h"
#include "brave/components/brave_origin/pref_names.h"
#include "brave/components/skus/browser/pref_names.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/startup/startup_browser_creator.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_test.h"
namespace {
// Test delegate that uses real profile infrastructure but records calls to
// OpenExternal and AttemptExit via shared flags (the delegate is moved into
// an async closure during WindowClosing, so raw pointers to it can dangle).
class TestDelegate : public BraveOriginStartupView::Delegate {
public:
explicit TestDelegate(bool* attempt_exit_flag)
: attempt_exit_flag_(attempt_exit_flag) {}
void OpenExternal(const GURL& url) override {}
void AttemptExit() override {
if (attempt_exit_flag_) {
*attempt_exit_flag_ = true;
}
}
void CreateSystemProfile(
BraveOriginStartupView::ProfileCallback callback) override {
g_browser_process->profile_manager()->CreateProfileAsync(
ProfileManager::GetSystemProfilePath(), std::move(callback));
}
void CreateDefaultProfile(
BraveOriginStartupView::ProfileCallback callback) override {
g_browser_process->profile_manager()->CreateProfileAsync(
g_browser_process->profile_manager()->GetLastUsedProfileDir(),
std::move(callback));
}
private:
raw_ptr<bool> attempt_exit_flag_ = nullptr;
};
} // namespace
class BraveOriginStartupViewBrowserTest : public InProcessBrowserTest {
public:
void SetUp() override {
BraveOriginStartupView::SetShouldShowDialogForTesting(std::nullopt);
InProcessBrowserTest::SetUp();
}
void TearDown() override {
InProcessBrowserTest::TearDown();
BraveOriginStartupView::SetShouldShowDialogForTesting(std::nullopt);
}
void TearDownOnMainThread() override {
if (BraveOriginStartupView::IsShowing()) {
HideAndWaitForClose();
}
InProcessBrowserTest::TearDownOnMainThread();
}
// Hide() is a no-op until the widget exists (async profile creation may
// still be in progress). Retry Hide() inside RunUntil so that pending tasks
// are processed between attempts, and the widget is closed as soon as it
// becomes available.
void HideAndWaitForClose() {
ASSERT_TRUE(base::test::RunUntil([] {
BraveOriginStartupView::Hide();
return !BraveOriginStartupView::IsShowing();
}));
}
};
// Verifies the dialog can be shown and IsShowing() reflects its state.
IN_PROC_BROWSER_TEST_F(BraveOriginStartupViewBrowserTest, ShowAndHide) {
EXPECT_FALSE(BraveOriginStartupView::IsShowing());
bool on_complete_called = false;
BraveOriginStartupView::Show(
base::BindLambdaForTesting([&]() { on_complete_called = true; }),
std::make_unique<TestDelegate>(nullptr));
EXPECT_TRUE(BraveOriginStartupView::IsShowing());
HideAndWaitForClose();
EXPECT_FALSE(BraveOriginStartupView::IsShowing());
// Closing without validation should not call on_complete.
EXPECT_FALSE(on_complete_called);
}
// Verifies that calling Show() twice does not create a second dialog.
IN_PROC_BROWSER_TEST_F(BraveOriginStartupViewBrowserTest, ShowTwiceIsNoOp) {
BraveOriginStartupView::Show(base::DoNothing(),
std::make_unique<TestDelegate>(nullptr));
EXPECT_TRUE(BraveOriginStartupView::IsShowing());
// Second Show should be a no-op.
BraveOriginStartupView::Show(base::DoNothing(),
std::make_unique<TestDelegate>(nullptr));
EXPECT_TRUE(BraveOriginStartupView::IsShowing());
HideAndWaitForClose();
EXPECT_FALSE(BraveOriginStartupView::IsShowing());
}
// Verifies that Hide() is safe to call when dialog is not showing.
IN_PROC_BROWSER_TEST_F(BraveOriginStartupViewBrowserTest,
HideWhenNotShowingIsNoOp) {
EXPECT_FALSE(BraveOriginStartupView::IsShowing());
BraveOriginStartupView::Hide(); // Should not crash.
EXPECT_FALSE(BraveOriginStartupView::IsShowing());
}
// Verifies that the testing override controls ShouldShowDialog().
IN_PROC_BROWSER_TEST_F(BraveOriginStartupViewBrowserTest,
ShouldShowDialogTestingOverride) {
PrefService* local_state = g_browser_process->local_state();
// Default in tests: --test-type flag makes it return false.
EXPECT_FALSE(BraveOriginStartupView::ShouldShowDialog(local_state));
// Override to force show.
BraveOriginStartupView::SetShouldShowDialogForTesting(true);
EXPECT_TRUE(BraveOriginStartupView::ShouldShowDialog(local_state));
// Override to force hide.
BraveOriginStartupView::SetShouldShowDialogForTesting(false);
EXPECT_FALSE(BraveOriginStartupView::ShouldShowDialog(local_state));
// Reset override.
BraveOriginStartupView::SetShouldShowDialogForTesting(std::nullopt);
EXPECT_FALSE(BraveOriginStartupView::ShouldShowDialog(local_state));
}
// Verifies closing without validation calls AttemptExit on the delegate.
IN_PROC_BROWSER_TEST_F(BraveOriginStartupViewBrowserTest,
CloseWithoutValidationCallsAttemptExit) {
bool attempt_exit_called = false;
BraveOriginStartupView::Show(
base::DoNothing(), std::make_unique<TestDelegate>(&attempt_exit_called));
EXPECT_FALSE(attempt_exit_called);
HideAndWaitForClose();
EXPECT_FALSE(BraveOriginStartupView::IsShowing());
EXPECT_TRUE(attempt_exit_called);
}
// Verifies that when the purchase is validated and SKU credentials exist,
// the dialog does not appear and the browser window opens normally.
IN_PROC_BROWSER_TEST_F(BraveOriginStartupViewBrowserTest,
PaidUserSkipsDialogAndGetsBrowser) {
PrefService* local_state = g_browser_process->local_state();
// Simulate a paid user: set validated pref and add SKU credentials.
local_state->SetBoolean(brave_origin::kOriginPurchaseValidated, true);
base::DictValue skus_state;
skus_state.Set("production", R"({
"credentials": {
"items": {
"origin": "some-credential-value"
}
}
})");
local_state->SetDict(skus::prefs::kSkusState, std::move(skus_state));
// With valid purchase state, ShouldShowDialog returns false (even without
// the --test-type override).
BraveOriginStartupView::SetShouldShowDialogForTesting(std::nullopt);
EXPECT_FALSE(BraveOriginStartupView::ShouldShowDialog(local_state));
// The dialog is not showing, and we have a browser window (provided by the
// test framework via the normal startup path).
EXPECT_FALSE(BraveOriginStartupView::IsShowing());
EXPECT_TRUE(browser() != nullptr);
}
// --------------------------------------------------------------------------
// Integration tests for the StartupBrowserCreator::Start override.
// These call Start() directly to exercise the real startup interception logic
// in chromium_src/chrome/browser/ui/startup/startup_browser_creator.cc.
// --------------------------------------------------------------------------
// Verifies that StartupBrowserCreator::Start() shows the dialog when
// ShouldShowDialog is true, and that closing without validation does not crash.
IN_PROC_BROWSER_TEST_F(BraveOriginStartupViewBrowserTest,
StartOverrideShowsDialogOnClose) {
EXPECT_FALSE(BraveOriginStartupView::IsShowing());
BraveOriginStartupView::SetShouldShowDialogForTesting(true);
// Call the real Start() override. It should intercept and show the dialog.
StartupBrowserCreator browser_creator;
StartupProfileInfo profile_info{browser()->profile(),
StartupProfileMode::kBrowserWindow};
bool result = browser_creator.Start(*base::CommandLine::ForCurrentProcess(),
base::FilePath(), profile_info,
{browser()->profile()});
EXPECT_TRUE(result);
EXPECT_TRUE(BraveOriginStartupView::IsShowing());
// Close without validation — should not crash.
HideAndWaitForClose();
EXPECT_FALSE(BraveOriginStartupView::IsShowing());
}
// Verifies that StartupBrowserCreator::Start() shows the dialog and that
// simulating validation fires the on_complete callback (which calls
// Start_ChromiumImpl), opening a new browser window without crashing.
IN_PROC_BROWSER_TEST_F(BraveOriginStartupViewBrowserTest,
StartOverrideShowsDialogAndProceeds) {
EXPECT_FALSE(BraveOriginStartupView::IsShowing());
size_t browser_count_before = chrome::GetTotalBrowserCount();
BraveOriginStartupView::SetShouldShowDialogForTesting(true);
StartupBrowserCreator browser_creator;
StartupProfileInfo profile_info{browser()->profile(),
StartupProfileMode::kBrowserWindow};
bool result = browser_creator.Start(*base::CommandLine::ForCurrentProcess(),
base::FilePath(), profile_info,
{browser()->profile()});
EXPECT_TRUE(result);
EXPECT_TRUE(BraveOriginStartupView::IsShowing());
// ValidateForTesting() is a no-op until the widget is ready (async profile
// creation). Retry inside RunUntil so pending tasks are processed between
// attempts.
ASSERT_TRUE(base::test::RunUntil([] {
BraveOriginStartupView::ValidateForTesting();
return !BraveOriginStartupView::IsShowing();
}));
// A new browser window should have been created by Start_ChromiumImpl.
EXPECT_GT(chrome::GetTotalBrowserCount(), browser_count_before);
}
// Verifies that StartupBrowserCreator::Start() proceeds normally (no dialog)
// when ShouldShowDialog is false.
IN_PROC_BROWSER_TEST_F(BraveOriginStartupViewBrowserTest,
StartOverrideSkipsDialogWhenNotNeeded) {
BraveOriginStartupView::SetShouldShowDialogForTesting(false);
size_t browser_count_before = chrome::GetTotalBrowserCount();
StartupBrowserCreator browser_creator;
StartupProfileInfo profile_info{browser()->profile(),
StartupProfileMode::kBrowserWindow};
bool result = browser_creator.Start(*base::CommandLine::ForCurrentProcess(),
base::FilePath(), profile_info,
{browser()->profile()});
EXPECT_TRUE(result);
EXPECT_FALSE(BraveOriginStartupView::IsShowing());
// Start_ChromiumImpl should have run directly, creating a new browser.
EXPECT_GT(chrome::GetTotalBrowserCount(), browser_count_before);
}
@@ -0,0 +1,167 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/browser/ui/views/brave_origin/brave_origin_startup_view.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/values.h"
#include "brave/components/brave_origin/pref_names.h"
#include "brave/components/skus/browser/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
class BraveOriginStartupViewTest : public testing::Test {
public:
void SetUp() override {
local_state_.registry()->RegisterBooleanPref(
brave_origin::kOriginPurchaseValidated, false);
local_state_.registry()->RegisterDictionaryPref(skus::prefs::kSkusState);
BraveOriginStartupView::SetShouldShowDialogForTesting(std::nullopt);
}
void TearDown() override {
BraveOriginStartupView::SetShouldShowDialogForTesting(std::nullopt);
}
// Builds a JSON string representing an SKU state entry with credentials.
// If |has_items| is true, the credentials dict contains a non-empty "items"
// dict; otherwise it is empty.
std::string BuildSkuStateJson(bool has_items) {
if (has_items) {
return R"({
"credentials": {
"items": {
"origin": "some-credential-value"
}
}
})";
}
return R"({
"credentials": {
"items": {}
}
})";
}
void SetSkuCredentials(const std::string& env_key, const std::string& json) {
base::DictValue skus_state;
skus_state.Set(env_key, json);
local_state_.SetDict(skus::prefs::kSkusState, std::move(skus_state));
}
protected:
TestingPrefServiceSimple local_state_;
};
// --- ShouldShowDialog tests ---
TEST_F(BraveOriginStartupViewTest,
ShouldShowDialogWhenPurchaseNotValidatedAndNoCredentials) {
// Default state: pref is false, no SKU credentials -> should show.
EXPECT_TRUE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
TEST_F(BraveOriginStartupViewTest,
ShouldNotShowDialogWhenPurchaseValidatedAndHasCredentials) {
local_state_.SetBoolean(brave_origin::kOriginPurchaseValidated, true);
SetSkuCredentials("production", BuildSkuStateJson(/*has_items=*/true));
EXPECT_FALSE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
TEST_F(BraveOriginStartupViewTest,
ShouldShowDialogWhenPurchaseValidatedButNoCredentials) {
// Pref validated but SKU credentials missing -> should show.
local_state_.SetBoolean(brave_origin::kOriginPurchaseValidated, true);
EXPECT_TRUE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
TEST_F(BraveOriginStartupViewTest,
ShouldShowDialogWhenNotValidatedButHasCredentials) {
// Has credentials but pref not set -> should show.
SetSkuCredentials("production", BuildSkuStateJson(/*has_items=*/true));
EXPECT_TRUE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
TEST_F(BraveOriginStartupViewTest,
ShouldNotShowDialogWhenValidatedAndMultipleEnvCredentials) {
local_state_.SetBoolean(brave_origin::kOriginPurchaseValidated, true);
base::DictValue skus_state;
skus_state.Set("staging", BuildSkuStateJson(/*has_items=*/false));
skus_state.Set("production", BuildSkuStateJson(/*has_items=*/true));
local_state_.SetDict(skus::prefs::kSkusState, std::move(skus_state));
EXPECT_FALSE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
TEST_F(BraveOriginStartupViewTest,
ShouldShowDialogWhenCredentialsHaveEmptyItems) {
local_state_.SetBoolean(brave_origin::kOriginPurchaseValidated, true);
SetSkuCredentials("production", BuildSkuStateJson(/*has_items=*/false));
EXPECT_TRUE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
TEST_F(BraveOriginStartupViewTest, ShouldShowDialogWhenSkuStateHasInvalidJson) {
local_state_.SetBoolean(brave_origin::kOriginPurchaseValidated, true);
SetSkuCredentials("production", "not-valid-json{{{");
EXPECT_TRUE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
TEST_F(BraveOriginStartupViewTest,
ShouldShowDialogWhenSkuStateHasNonStringValue) {
local_state_.SetBoolean(brave_origin::kOriginPurchaseValidated, true);
base::DictValue skus_state;
skus_state.Set("production", 42); // Not a string.
local_state_.SetDict(skus::prefs::kSkusState, std::move(skus_state));
EXPECT_TRUE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
TEST_F(BraveOriginStartupViewTest, ShouldShowDialogWhenCredentialsDictMissing) {
local_state_.SetBoolean(brave_origin::kOriginPurchaseValidated, true);
SetSkuCredentials("production", R"({"some_other_key": "value"})");
EXPECT_TRUE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
TEST_F(BraveOriginStartupViewTest, ShouldShowDialogWhenItemsDictMissing) {
local_state_.SetBoolean(brave_origin::kOriginPurchaseValidated, true);
SetSkuCredentials("production", R"({"credentials": {"not_items": "value"}})");
EXPECT_TRUE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
// --- SetShouldShowDialogForTesting tests ---
TEST_F(BraveOriginStartupViewTest, TestOverrideForceShow) {
// Even with valid state, the override forces show.
local_state_.SetBoolean(brave_origin::kOriginPurchaseValidated, true);
SetSkuCredentials("production", BuildSkuStateJson(/*has_items=*/true));
BraveOriginStartupView::SetShouldShowDialogForTesting(true);
EXPECT_TRUE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
TEST_F(BraveOriginStartupViewTest, TestOverrideForceHide) {
// Even with invalid state, the override forces hide.
BraveOriginStartupView::SetShouldShowDialogForTesting(false);
EXPECT_FALSE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
TEST_F(BraveOriginStartupViewTest, TestOverrideResetsToNormal) {
BraveOriginStartupView::SetShouldShowDialogForTesting(false);
EXPECT_FALSE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
BraveOriginStartupView::SetShouldShowDialogForTesting(std::nullopt);
// Back to normal: default state should show.
EXPECT_TRUE(BraveOriginStartupView::ShouldShowDialog(&local_state_));
}
// --- IsShowing tests (without needing a widget) ---
TEST_F(BraveOriginStartupViewTest, IsShowingReturnsFalseByDefault) {
EXPECT_FALSE(BraveOriginStartupView::IsShowing());
}
@@ -9,11 +9,12 @@
#include "brave/browser/resources/brave_origin_startup/grit/brave_origin_startup_generated_map.h"
#include "brave/browser/resources/brave_origin_startup/grit/brave_origin_startup_static_resources.h"
#include "brave/browser/resources/brave_origin_startup/grit/brave_origin_startup_static_resources_map.h"
#include "brave/browser/skus/skus_service_factory.h"
#include "brave/browser/ui/webui/brave_origin_startup/brave_origin_startup_handler.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "components/grit/brave_components_strings.h"
#include "components/grit/brave_components_webui_strings.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
@@ -28,28 +29,9 @@ void CreateAndAddDataSource(content::WebUI* web_ui, Profile* profile) {
webui::SetupWebUIDataSource(
source, kBraveOriginStartupGenerated,
IDR_BRAVE_ORIGIN_STARTUP_STATIC_BRAVE_ORIGIN_STARTUP_HTML);
source->AddResourcePaths(kBraveOriginStartupStaticResources);
static constexpr webui::LocalizedString kStrings[] = {
{"braveOriginStartupTitle", IDS_BRAVE_ORIGIN_STARTUP_TITLE},
{"braveOriginStartupDescription", IDS_BRAVE_ORIGIN_STARTUP_DESCRIPTION},
{"braveOriginStartupDescription2", IDS_BRAVE_ORIGIN_STARTUP_DESCRIPTION2},
{"braveOriginStartupRestoreButton",
IDS_BRAVE_ORIGIN_STARTUP_RESTORE_BUTTON},
{"braveOriginStartupBuyButton", IDS_BRAVE_ORIGIN_STARTUP_BUY_BUTTON},
{"braveOriginStartupRestoreTitle",
IDS_BRAVE_ORIGIN_STARTUP_RESTORE_TITLE},
{"braveOriginStartupRestoreDescription",
IDS_BRAVE_ORIGIN_STARTUP_RESTORE_DESCRIPTION},
{"braveOriginStartupPurchaseIdLabel",
IDS_BRAVE_ORIGIN_STARTUP_PURCHASE_ID_LABEL},
{"braveOriginStartupPurchaseIdPlaceholder",
IDS_BRAVE_ORIGIN_STARTUP_PURCHASE_ID_PLACEHOLDER},
{"braveOriginStartupVerifyButton",
IDS_BRAVE_ORIGIN_STARTUP_VERIFY_BUTTON},
{"braveOriginStartupVerifyingMessage",
IDS_BRAVE_ORIGIN_STARTUP_VERIFYING_MESSAGE},
};
source->AddLocalizedStrings(kStrings);
source->AddLocalizedStrings(webui::kBraveOriginStartupStrings);
}
} // namespace
+1
View File
@@ -286,6 +286,7 @@ void BraveSettingsUI::AddResources(content::WebUIDataSource* html_source,
brave_origin::IsBraveOriginEnabled());
html_source->AddBoolean("isBraveOriginBrandedBuild",
BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED));
// STAGING for unofficial builds; official builds always resolve to prod.
html_source->AddString(
"braveOriginBuyUrl",
base::StrCat(
@@ -1,3 +1,5 @@
include_rules = [
"+brave/browser/ui/views/brave_origin",
"+brave/components/brave_origin/buildflags",
"+brave/components/tor/buildflags",
]
@@ -7,6 +7,7 @@
#include "base/command_line.h"
#include "base/logging.h"
#include "brave/components/brave_origin/buildflags/buildflags.h"
#include "brave/components/constants/brave_switches.h"
#include "brave/components/tor/buildflags/buildflags.h"
#include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
@@ -15,6 +16,15 @@
#include "brave/browser/tor/tor_profile_manager.h"
#endif
#if BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
#include "brave/browser/ui/views/brave_origin/brave_origin_startup_view.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "components/prefs/pref_service.h"
#endif
#ifdef LaunchModeRecorder
static_assert(false,
"Replace the use of OldLaunchModeRecorder with "
@@ -85,5 +95,95 @@ void BraveStartupBrowserCreatorImpl::Launch(
}
#define StartupBrowserCreatorImpl BraveStartupBrowserCreatorImpl
#define Start(...) Start_ChromiumImpl(__VA_ARGS__)
#define ProcessCommandLineAlreadyRunning(...) \
ProcessCommandLineAlreadyRunning_ChromiumImpl(__VA_ARGS__)
#include <chrome/browser/ui/startup/startup_browser_creator.cc>
#undef ProcessCommandLineAlreadyRunning
#undef Start
#undef StartupBrowserCreatorImpl
#if BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
namespace {
// Concrete delegate that wires up the startup dialog to real browser services.
class StartupDialogDelegate : public BraveOriginStartupView::Delegate {
public:
void OpenExternal(const GURL& url) override {
platform_util::OpenExternal(url);
}
void AttemptExit() override { chrome::AttemptExit(); }
void CreateSystemProfile(
BraveOriginStartupView::ProfileCallback callback) override {
g_browser_process->profile_manager()->CreateProfileAsync(
ProfileManager::GetSystemProfilePath(), std::move(callback));
}
void CreateDefaultProfile(
BraveOriginStartupView::ProfileCallback callback) override {
g_browser_process->profile_manager()->CreateProfileAsync(
g_browser_process->profile_manager()->GetLastUsedProfileDir(),
std::move(callback));
}
};
} // namespace
#endif // BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
// For Brave Origin branded builds, intercept Start() to show a purchase
// validation dialog before any browser window or profile picker opens. Start()
// is only called externally from chrome_browser_main.cc, so the #define above
// only renames the definition (not external callers). When the dialog closes
// with a successful validation, the callback invokes Start_ChromiumImpl() to
// continue the normal startup flow.
bool StartupBrowserCreator::Start(const base::CommandLine& cmd_line,
const base::FilePath& cur_dir,
StartupProfileInfo profile_info,
const Profiles& last_opened_profiles) {
#if BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
if (BraveOriginStartupView::ShouldShowDialog(
g_browser_process->local_state())) {
// Capture first_run_tabs_ by value because `this` (the
// StartupBrowserCreator) is destroyed by chrome_browser_main.cc
// (browser_creator_.reset()) right after Start() returns.
BraveOriginStartupView::Show(
base::BindOnce(
[](std::vector<GURL> first_run_tabs,
const base::CommandLine& cmd_line, const base::FilePath& cur_dir,
StartupProfileInfo profile_info,
const Profiles& last_opened_profiles) {
StartupBrowserCreator browser_creator;
browser_creator.AddFirstRunTabs(first_run_tabs);
browser_creator.Start_ChromiumImpl(cmd_line, cur_dir,
std::move(profile_info),
last_opened_profiles);
},
std::move(first_run_tabs_), cmd_line, cur_dir,
std::move(profile_info), last_opened_profiles),
std::make_unique<StartupDialogDelegate>());
return true;
}
#endif // BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
return Start_ChromiumImpl(cmd_line, cur_dir, std::move(profile_info),
last_opened_profiles);
}
// For Brave Origin branded builds, block second-launch attempts while the
// startup dialog is showing. Without this, a second launch goes through
// ProcessCommandLineAlreadyRunning (not Start), bypassing the dialog guard
// and opening a browser window directly.
// static
void StartupBrowserCreator::ProcessCommandLineAlreadyRunning(
const base::CommandLine& command_line,
const base::FilePath& cur_dir,
const StartupProfilePathInfo& profile_path_info) {
#if BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
if (BraveOriginStartupView::IsShowing()) {
return;
}
#endif // BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
ProcessCommandLineAlreadyRunning_ChromiumImpl(command_line, cur_dir,
profile_path_info);
}
@@ -0,0 +1,25 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef BRAVE_CHROMIUM_SRC_CHROME_BROWSER_UI_STARTUP_STARTUP_BROWSER_CREATOR_H_
#define BRAVE_CHROMIUM_SRC_CHROME_BROWSER_UI_STARTUP_STARTUP_BROWSER_CREATOR_H_
// Adds _ChromiumImpl method declarations alongside the originals, so the Brave
// Origin startup dialog can intercept the startup flow in the .cc override.
// Start() is only called from chrome_browser_main.cc (a different translation
// unit) so the #define in the .cc does not affect its external callers.
#define Start(...) \
Start_ChromiumImpl(__VA_ARGS__); \
bool Start(__VA_ARGS__)
#define ProcessCommandLineAlreadyRunning(...) \
ProcessCommandLineAlreadyRunning_ChromiumImpl(__VA_ARGS__); \
static void ProcessCommandLineAlreadyRunning(__VA_ARGS__)
#include <chrome/browser/ui/startup/startup_browser_creator.h> // IWYU pragma: export
#undef ProcessCommandLineAlreadyRunning
#undef Start
#endif // BRAVE_CHROMIUM_SRC_CHROME_BROWSER_UI_STARTUP_STARTUP_BROWSER_CREATOR_H_
+8 -1
View File
@@ -191,7 +191,10 @@ test("brave_unit_tests") {
]
if (is_brave_origin_branded) {
deps += [ "//brave/browser/ui/webui/brave_origin_startup:unit_tests" ]
deps += [
"//brave/browser/ui/views/brave_origin:unit_tests",
"//brave/browser/ui/webui/brave_origin_startup:unit_tests",
]
}
if (enable_brave_news) {
@@ -1183,6 +1186,10 @@ test("brave_browser_tests") {
sources += [ "//brave/browser/ui/views/crash_report_permission_ask_dialog_browsertest.cc" ]
}
if (is_brave_origin_branded) {
deps += [ "//brave/browser/ui/views/brave_origin:browser_tests" ]
}
if (enable_brave_wayback_machine) {
sources += [ "//brave/components/brave_wayback_machine/brave_wayback_machine_browsertest.cc" ]
deps += [ "//brave/components/brave_wayback_machine" ]