//chrome/browser
design principlesThese design principles make it easier to write, debug, and maintain desktop code in //chrome/browser
. Most, but not all code in //chrome/browser
is desktop code. Some code is used on Android.
//chrome/OWNERS
.//component/<feature>
and //chrome/browser/<feature>
(or //chrome/browser/ui/<feature>
), and not in //chrome/browser/ui/views
.//chrome/browser
and //chrome/browser/ui
has been removed.//chrome/browser
has been removed.//chrome/browser/resources/<feature>
alongside standalone BUILD.gn
files.BUILD.gn
and OWNERS
file.BUILD.gn
.//chrome/browser/BUILD.gn:browser
, //chrome/test/BUILD.gn
or //chrome/browser/ui/BUILD.gn:ui
.//chrome/browser:browser
and //chrome/browser/ui:ui
. These circular dependencies will disappear when all sources are removed from //chrome/browser:browser
and //chrome/browser/ui:ui
.//chrome/browser/browser/ui:ui
, which will no longer be necessary once NTP is also modularized (crbug.com/382237520).chrome::FindBrowserWithTab
(and everything in browser_finder.h)GetBrowserViewForNativeWindow
(via browser_view.h)FindBrowserWindowWithWebContents
(via browser_window.h)Browser*
. This is functionally a container of hundreds of other pointers. It is impossible to specify dependencies, since Browser*
functionally depends on everything. Instead, pass in the relevant pointers, e.g. Profile*
, FooFeatureController
, etc.Browser*
is also impossible to properly unit test.// Do not do this: FooFeature(Browser* browser) : browser_(browser) {} FooFeature::DoStuff() { DoStuffWith(browser_->profile()->GetPrefs()); } // Do this: FooFeature(PrefService* prefs) : prefs_(prefs) {} FooFeature::DoStuff() { DoStuffWith(prefs_); }
TabFeatures
(member of TabModel
)content::WebContentsUserData
, it should be done in this class.TabHelpers::AttachTabHelpers
will become a remove-only method. Clank/WebView may continue to use section 2 of TabHelpers::AttachTabHelpers
(Clank/WebView only).content::WebContentsUserData
is an anti-pattern.BrowserWindowFeatures
(member of Browser
)BrowserContextKeyedServiceFactory
(functionally a member of Profile
)ServiceIsCreatedWithBrowserContext
to return true
. This guarantees precise lifetime semantics.content::ContentMain()
. Test harnesses use a test-suite dependent start-point, e.g. base::LaunchUnitTests
. Tests typically instantiate a subset of the lazily-instantiated factories instantiated by production code, at a different time, with a different order. This results in different, sometimes broken behavior in tests. This is typically papered over by modifying the production logic to include otherwise unnecessary conditionals, typically early-exits. Overriding ServiceIsCreatedWithBrowserContext
guarantees identical behavior between test and production code.TestingProfile::Builder::AddTestingFactory
to stub or fake services.Profile
.NoDestructor
singleton.// Properly scoped state avoids categories of bugs and subtle issues. For // example: one common mistake is to store window-scoped state on a tab-scoped // object. This results in subtle bugs (or crashes) when tab is dragged into a // new window. // Do not do this: FooTabFeature { // As per (1) above, we shouldn't be passing or storing Browser* anyways. // Another common anti-pattern is to dynamically look up Browser* via // browser_finder methods. These often result in the wrong Browser* Browser* browser_; // This will point to the wrong BrowserView if the tab is dragged into a new // window. Furthermore, BrowserView* itself is a container of hundreds of // other views. The tab-scoped feature typically wants something much more // specific. BrowserView* browser_view_; // Extensions are profile-scoped, and thus any state/logic should be in a // ProfileKeyedService. If the user has multiple tabs (possibly across // multiple windows) simultaneously interacting with FooTabFeature, then it's // likely that the extension will uninstall while it's still in use. void InstallExtension(); void UninstallExtension(); }; // Instead do this: FooTabFeature { // Guaranteed to remain valid for the lifetime of this class. This can be used // to dynamically access relevant window state via // tab_->GetBrowserWindowInterface()->GetFeatures().GetFooWindowFeature() TabInterface* tab_; }; FooService : public KeyedService { void InstallExtension(); void UninstallExtension(); };
base::UTF8ToWide()
IsFooFeatureEnabled()
wraps the global variable BASE_FEATURE(kFooFeature,...)
BrowserList
.//chrome/browser/BUILD.gn
and //chrome/browser/ui/BUILD.gn
ui
subdirectory, and plenty of non-UI code inside of the ui
subdirectory. Currently the two BUILD files allow circular includes. We will continue to treat these directories and BUILD files as interchangeable.// Good, complexity is O(N) and no tight coupling using a well-understood and // common design pattern: modality. Similar logic will be needed in other modal // UIs. The logic in this class does not change regardless of how many other new // modal features are created. class Sparkles { // Shows sparkles over the entire tab. Should not be shown over other Chrome // contents (e.g. print preview) void Show() { if (tab_->CanShowModalUI()) { MakeSparkles(); // Prevents other modals from showing until the member is reset. modal_ui_ = tab_->ShowModalUI(); } } std::unique_ptr<ScopedTabModalUI> modal_ui_; raw_ptr<TabInterface> tab_; }; // Bad. Introduces tight coupling between unrelated features. Similar logic is // needed in PrintPreview and DevTools. Complexity will scale with O(N^2). When // a new modal feature is created, every modal feature will need to be updated. class Sparkles { void Show() { if (PrintPreview::Showing()) { return; } if (DevTools::Showing()) { return; } MakeSparkles(); } };
#if BUILDFLAG(FEATURE_FOO)
.#if BUILDFLAG(OS_WIN)
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
#if !defined(NDEBUG)
#if defined(ADDRESS_SANITIZER)
BUILDFLAG(ENABLE_EXTENSIONS)
) to glue this into the main source is allowed. The glue code should be kept to a minimum.CHECK_IS_TEST()
.// Good. Depending on context, this can be broken into separate tests. bool IsPrime(int input); TEST(Math, CheckIsPrime) { EXPECT_TRUE(IsPrime(2)); EXPECT_TRUE(IsPrime(3)); EXPECT_FALSE(IsPrime(99)); EXPECT_FALSE(IsPrime(-2)); EXPECT_FALSE(IsPrime(0)); EXPECT_FALSE(IsPrime(1)); } // Bad. This is a change detector test. Change detector tests are easy to spot // because the test logic looks the same as the production logic. class ShowButtonOnBrowserActivation : public BrowserActivationListener { void ShowButton(); bool DidShowButton(); // BrowserActivationListener overrides: void BrowserDidActivate() override { ShowButton(); } }; Test(ShowButtonOnBrowserActivationTest, ShowButtonOnActivate) { ShowButtonOnBrowserActivation listener; listener.BrowserDidActivate(); EXPECT_TRUE(listener.DidShowButton()); }
base::Callback
, execution should either be always synchronous, or always asynchronous. Mixing the two means callers have to deal with two distinct control flows, which often leads to incorrect handling of one. Avoid the following:if (result.cached) { RunCallbackSync()) } else { GetResultAndRunCallbackAsync() }
class CarFactory { std::unique_ptr<Car> CreateCar() { if (!CanCreateCar()) { return nullptr; } if (FactoryIsBusy() && !delegate->ShouldShowCarIfFactoryIsBusy()) { return nullptr; } return std::make_unique<Car>(); } bool CanCreateCar(); bool FactoryIsBusy(); Delegate* delegate_ = nullptr; }; class CarSalesPerson : public Delegate { // Can return nullptr, in which case no car is shown. std::unique_ptr<Car> ShowCar() { return car_factory_->CreateCar(); } // Delegate overrides: // Whether the car should be shown, even if the factory is busy. bool ShouldShowCarIfFactoryIsBusy() override; CarFactory* car_factory_ = nullptr; };
Good, version 1: Remove delegation. Pass all relevant state to CarFactory so that CreateCar() does not depend on non-local state.
class CarFactory { std::unique_ptr<Car> CreateCar(bool show_even_if_factory_is_busy) { if (!CanCreateCar()) { return nullptr; } if (FactoryIsBusy() && !show_even_if_factory_is_busy) { return nullptr; } return std::make_unique<Car>(); } bool CanCreateCar(); bool FactoryIsBusy(); }; class CarSalesPerson { // Can return nullptr, in which case no car is shown. std::unique_ptr<Car> ShowCar() { return car_factory_->CreateCar(ShouldShowCarIfFactoryIsBusy()); } // Whether the car should be shown, even if the factory is busy. bool ShouldShowCarIfFactoryIsBusy(); CarFactory* car_factory_ = nullptr; };
Good, version 2: Remove delegation. CreateCar always creates a car (fewer conditionals). State only flows from CarFactory to CarSalesPerson (and never backwards).
class CarFactory { bool CanCreateCar(); bool FactoryIsBusy(); // Never returns nullptr. std::unique_ptr<Car> CreateCar(); }; class CarSalesPerson { // Can return nullptr, in which case no car is shown std::unique_ptr<Car> ShowCar() { if (!car_factory_->CanCreateCar()) { return nullptr; } if (car_factory_->FactoryIsBusy() && !ShouldShowCarIfFactoryIsBusy()) { return nullptr; } return car_factory_->CreateCar(); } // Whether the car should be shown, even if the factory is busy. bool ShouldShowCarIfFactoryIsBusy(); CarFactory* car_factory_ = nullptr; };
Bad. FeatureShowcase depends on FeatureA. FeatureA depends on FeatureB. FeatureB depends on FeatureShowcase. The root problem is that the design for FeatureShowcase uses both a pull and a push model for control flow.
// Shows an icon per feature. Needs to know whether each icon is visible. class FeatureShowcase { FeatureShowcase() { // Checks whether A should be visible, and if so, shows A. if (FeatureA::IsVisible()) ShowFeatureA(); } // Called to make B visible. void ShowFeatureB(); }; class FeatureA { // Feature A depends on feature B. FeatureA(FeatureB* b); static bool IsVisible(); }; class FeatureB { FeatureB(FeatureShowcase* showcase) { if (IsVisible()) showcase->ShowFeatureB(); } static bool IsVisible(); };
Good, version 1. FeatureShowcase uses a pull model for control flow. FeatureShowcase depends on FeatureA and FeatureB. FeatureA depends on FeatureB. There is no circular dependency.
// Shows an icon per feature. Needs to know whether each icon is visible. class FeatureShowcase { FeatureShowcase() { if (FeatureA::IsVisible()) ShowFeatureA(); if (FeatureB::IsVisible()) ShowFeatureB(); } }; class FeatureA { // Feature A depends on feature B. FeatureA(FeatureB* b); static bool IsVisible(); }; class FeatureB { FeatureB(); static bool IsVisible(); };
Good, version 2. FeatureShowcase uses a push model for control flow. FeatureA and FeatureB both depend on FeatureShowcase. There is no circular dependency.
// Shows an icon per feature. Needs to know whether each icon is visible. class FeatureShowcase { FeatureShowcase(); // Called to make A visible. void ShowFeatureA(); // Called to make B visible. void ShowFeatureB(); }; class FeatureA { // Feature A depends on feature B. FeatureA(FeatureB* b, FeatureShowcase* showcase) { if (IsVisible()) showcase->ShowFeatureA(); } static bool IsVisible(); }; class FeatureB { FeatureB(FeatureShowcase* showcase) { if (IsVisible()) showcase->ShowFeatureB(); } static bool IsVisible(); };