blob: 5b214199f63a8cd55de07b4e240505d056d5a037 [file] [log] [blame] [view]
Erik Chenf652e612024-07-17 21:38:561These design principles make it easier to write, debug, and maintain desktop
2code in //chrome/browser. Most, but not all code in //chrome/browser is desktop
3code. Some code is used on Android.
Erik Chen598410c2024-04-11 21:05:514
5## Caveats:
6* These are recommendations, not requirements.
7* These are not intended to be static. If you think a
8 principle doesn't make sense, reach out to //chrome/OWNERS.
9* These are intended to apply to new code and major refactors. We do not expect
10 existing features to be refactored, except as need arises.
11
12## Structure, modularity:
Erik Chen9c0281b2024-05-29 23:41:2013* Features should be modular.
Erik Chen598410c2024-04-11 21:05:5114 * For most features, all business logic should live in some combination of
15 //chrome/browser/<feature>, //chrome/browser/ui/<feature> or
16 //component/<feature>.
Erik Chenc8b83792024-08-21 23:32:4517 * This includes views code. The historical rule disallowing views in
18 //chrome/browser/<feature> and //chrome/browser/ui/<feature> has been
19 removed.
Erik Chen598410c2024-04-11 21:05:5120 * WebUI resources are the only exception. They will continue to live in
21 //chrome/browser/resources/<feature> alongside standalone BUILD.gn files.
22 * This directory should have a standalone BUILD.gn and OWNERs file.
Erik Chen9c0281b2024-05-29 23:41:2023 * All files in the directory should belong to targets in the BUILD.gn.
Erik Chen288c96772024-08-12 21:14:4624 * Do NOT add to `//chrome/browser/BUILD.gn:browser`,
25 `//chrome/test/BUILD.gn` or `//chrome/browser/ui/BUILD.gn:ui`.
26 * gn circular dependencies are disallowed. Logical
27 circular dependencies are allowed (for legacy reasons) but discouraged.
28 * [Lens
29 overlay](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/lens/BUILD.gn;drc=8e2c1c747f15a93c55ab2f10ebc8b32801ba129e)
30 is an example of a feature with no circular dependencies.
31 * The BUILD.gn should use public/sources separation.
32 * The main reason for this is to guard against future, unexpected usage
33 of parts of the code that were intended to be private. This makes it
34 difficult to change implementation details in the future.
35 * This directory may have a public/ subdirectory to enforce further
36 encapsulation, though this example does not use it.
37 * [cookie
38 controls](https://chromium-review.googlesource.com/c/chromium/src/+/5771416/5/chrome/browser/ui/cookie_controls/BUILD.gn)
39 is an example of a feature with logical circular dependencies.
40 * The header files are moved into a "cookie_controls" target with no
41 circular dependencies.
42 * The cc files are moved into a "impl" target, with circular
43 dependencies allowed with `//chrome/browser:browser` and
44 `//chrome/browser/ui:ui`. These circular dependencies will
45 disappear when all sources are removed from `//chrome/browser:browser` and `//chrome/browser/ui:ui`.
46 * The separation between header and cc files is functionally
47 equivalent to creating abstract base classes in one target, with
48 h/cc files in a separate target. This just skips the boilerplate
49 of creating the abstract base classes.
50 * Even though there are no build circular dependencies, there are
51 still logical circular dependencies from the cc files. This
52 discrepancy is because C++ allows headers to forward declare
53 dependencies, which do not need to be reflected in gn.
Erik Chen598410c2024-04-11 21:05:5154 * This directory may have its own namespace.
Erik Chen598410c2024-04-11 21:05:5155 * Corollary: There are several global functions that facilitate dependency
56 inversion. It will not be possible to call them from modularized features
57 (no dependency cycles), and their usage in non-modularized features is
58 considered a red flag:
59 * `chrome::FindBrowserWithTab` (and everything in browser_finder.h)
60 * `GetBrowserViewForNativeWindow` (via browser_view.h)
61 * `FindBrowserWindowWithWebContents` (via browser_window.h)
Erik Chen63f6f1a2024-08-21 01:33:0462 * Corollary: Don't use `Browser*`. This is functionally a container of
63 hundreds of other pointers. It is impossible to specify dependencies,
64 since `Browser*` functionally depends on everything. Instead, pass in the
65 relevant pointers, e.g. `Profile*`, `FooFeatureController`, etc.
66 * Code that uses `Browser*` is also impossible to properly unit test.
Erik Chen9c0281b2024-05-29 23:41:2067 * Rationale: Modularity enforces the creation of API surfaces and explicit
68 dependencies. This has several positive externalities:
69 * Separation of interface from implementation prevents unnecessarly
70 tight coupling between features. This in turn reduces spooky action at
71 a distance, where seemingly innocuous changes break a distant,
72 supposedly unrelated feature.
73 * Explicit listing of circular dependencies exposes the likely fragile
74 areas of code.
75 * Alongside the later guidance of global functions must be pure,
76 modularity adds the requirement that test-code perform dependency
77 injection. This eliminates a common bug where test behavior diverges
78 from production behavior, and logic is added to production code to
79 work around test-only behaviors.
Erik Chen598410c2024-04-11 21:05:5180
Erik Chen2e1346e2024-08-22 07:21:3181```cpp
82// Do not do this:
83FooFeature(Browser* browser) : browser_(browser) {}
84FooFeature::DoStuff() { DoStuffWith(browser_->profile()->GetPrefs()); }
85
86// Do this:
87FooFeature(PrefService* prefs) : prefs_(prefs) {}
88FooFeature::DoStuff() { DoStuffWith(prefs_); }
89```
90
Erik Chen598410c2024-04-11 21:05:5191* Features should have a core controller with precise lifetime semantics. The
92 core controller for most desktop features should be owned and instantiated by
93 one of the following classes:
94 * `TabFeatures` (member of `TabModel`)
95 * This class should own all tab-centric features. e.g. print preview,
96 lens overlay, compose, find-in-page, etc.
97 * If the feature requires instantiation of
98 `WebContents::SupportsUserData`, it should be done in this class.
99 * For desktop chrome, `TabHelpers::AttachTabHelpers` will become a
100 remove-only method. Clank/WebView may continue to use section 2 of
101 `TabHelpers::AttachTabHelpers` (Clank/WebView only).
102 * More complex features that also target mobile platforms or other
103 supported embedders (e.g. android webview) will continue to use the
104 layered components architecture.
105 * We defer to //components/OWNERS for expertise and feedback on the
106 architecture of these features, and encourage feature-owners to
107 proactively reach out to them.
108 * Lazy instantiation of `WebContents::SupportsUserData` is an
109 anti-pattern.
110 * `BrowserWindowFeatures` (member of `Browser`)
111 * example: omnibox, security chip, bookmarks bar, side panel
112 * `BrowserContextKeyedServiceFactory` (functionally a member of `Profile`)
Erik Chend5431602024-05-23 20:20:58113 * Override `ServiceIsCreatedWithBrowserContext` to return `true`. This
114 guarantees precise lifetime semantics.
115 * Lazy instantiation is an anti-pattern.
116 * Production code is started via `content::ContentMain()`. Test
117 harnesses use a test-suite dependent start-point, e.g.
118 `base::LaunchUnitTests`. Tests typically instantiate a subset of
119 the lazily-instantiated factories instantiated by production code,
120 at a different time, with a different order. This results in
121 different, sometimes broken behavior in tests. This is typically
122 papered over by modifying the production logic to include
123 otherwise unnecessary conditionals, typically early-exits.
124 Overriding `ServiceIsCreatedWithBrowserContext` guarantees
125 identical behavior between test and production code.
126 * Use `TestingProfile::Builder::AddTestingFactory` to stub or fake
127 services.
Erik Chen16208722024-06-06 00:09:08128 * Separating the .h and .cc file into different build targets is
129 allowed.
130 * BrowserContextKeyedServiceFactory combines 3 pieces of
131 functionality:
132 * A getter to receive a service based on an instance of
133 `Profile`.
134 * A mechanism to establishing dependencies.
135 * A way to glue together //chrome layer logic with //components
136 layer logic.
137 * The latter two pieces of functionality are implemented in the
138 .cc file and typically result in dependencies on other parts
139 of //chrome. The first piece of functionality is implemented
140 in the .h file and does not necessarily introduce a dependency
141 on //chrome, since the returned service can be defined in
142 //components.
Erik Chenea810ac2024-07-29 23:44:32143 * GlobalFeatures.
144 * Features which are scoped to the entire process and span multiple
145 Profiles should be members of GlobalFeatures.
146 * GlobalFeatures is a member of BrowserProcess and they have similar
147 lifetime semantics. The main difference is that historically
148 BrowserProcess used the antipattern of lazy instantiation, and the
149 setup of TestingBrowserProcess encourages divergence between test
150 logic and production logic. On the other hand, GlobalFeatures is
151 always instantiated.
152 * This is not making any statements about initialization (e.g.
153 performing non-trivial setup).
Erik Chen598410c2024-04-11 21:05:51154 * The core controller should not be a `NoDestructor` singleton.
Erik Chen2e1346e2024-08-22 07:21:31155
156```cpp
157// Properly scoped state avoids categories of bugs and subtle issues. For
158// example: one common mistake is to store window-scoped state on a tab-scoped
159// object. This results in subtle bugs (or crashes) when tab is dragged into a
160// new window.
161
162// Do not do this:
163FooTabFeature {
164 // As per (1) above, we shouldn't be passing or storing Browser* anyways.
165 // Another common anti-pattern is to dynamically look up Browser* via
166 // browser_finder methods. These often result in the wrong Browser*
167 Browser* browser_;
168
169 // This will point to the wrong BrowserView if the tab is dragged into a new
170 // window. Furthermore, BrowserView* itself is a container of hundreds of
171 // other views. The tab-scoped feature typically wants something much more
172 // specific.
173 BrowserView* browser_view_;
174
175 // Extensions are profile-scoped, and thus any state/logic should be in a
176 // ProfileKeyedService. If the user has multiple tabs (possibly across
177 // multiple windows) simultaneously interacting with FooTabFeature, then it's
178 // likely that the extension will uninstall while it's still in use.
179 void InstallExtension();
180 void UninstallExtension();
181};
182
183// Instead do this:
184FooTabFeature {
185 // Guaranteed to remain valid for the lifetime of this class. This can be used
186 // to dynamically access relevant window state via
187 // tab_->GetBrowserWindowInterface()->GetFeatures().GetFooWindowFeature()
188 TabInterface* tab_;
189};
190
191FooService : public KeyedService {
192 void InstallExtension();
193 void UninstallExtension();
194};
195```
196
Erik Chen598410c2024-04-11 21:05:51197* Global functions should not access non-global state.
198 * Pure functions do not access global state and are allowed. e.g. `base::UTF8ToWide()`
199 * Global functions that wrap global state are allowed, e.g.
200 `IsFooFeatureEnabled()` wraps the global variable
201 `BASE_FEATURE(kFooFeature,...)`
202 * Global functions that access non-global state are disallowed. e.g.
203 static methods on `BrowserList`.
Erik Chen08053b12024-05-25 01:05:41204* No distinction between `//chrome/browser/BUILD.gn` and
205 `//chrome/browser/ui/BUILD.gn`
206 * There is plenty of UI code outside of the `ui` subdirectory, and plenty of
207 non-UI code inside of the `ui` subdirectory. Currently the two BUILD files
208 allow circular includes. We will continue to treat these directories and
209 BUILD files as interchangeable.
Erik Chen598410c2024-04-11 21:05:51210
211## UI
212* Features should use WebUI and Views toolkit, which are x-platform.
213 * Usage of underlying primitives is discouraged (aura, Cocoa, gtk, win32,
214 etc.). This is usually a sign that either the feature is misusing the UI
215 toolkits, or that the UI toolkits are missing important functionality.
216* Features should use "MVC"
217 * Clear separation between controller (control flow), view (presentation of
218 UI) and model (storage of data).
219 * For simple features that do not require data persistence, we only require
220 separation of controller from view.
221 * TODO: work with UI/toolkit team to come up with appropriate examples.
Erik Chen08053b12024-05-25 01:05:41222* Views:
223 * For simple configuration changes, prefer to use existing setter methods
224 and builder syntax.
225 * Feel free to create custom view subclasses to encapsulate logic or
226 override methods where doing so helps implement layout as the composition
227 of smaller standard layouts, or similar. Don't try to jam the kitchen sink
228 into a single giant view.
229 * However, avoid subclassing existing concrete view subclasses, e.g. to add
230 or tweak existing behavior. This risks violating the Google style guidance
231 on multiple inheritance and makes maintenance challenging. In particular
232 do not do this with core controls, as the behaviors of common controls
233 should not vary across the product.
234* Avoid subclassing Widgets.
Erik Chen598410c2024-04-11 21:05:51235* Avoid self-owned objects/classes for views or controllers.
236
237## General
238* Code unrelated to building a web-browser should not live in //chrome.
239 * See [chromeos/code.md](chromeos/code.md) for details on ChromeOS (non-browser) code.
240 * We may move some modularized x-platform code into //components. The main
241 distinction is that ChromeOS can depend on //components, but not on
242 //chrome. This will be evaluated on a case-by-case basis.
243* Avoid nested message loops.
244* Threaded code should have DCHECKs asserting correct sequence.
245 * Provides documentation and correctness checks.
246 * See base/sequence_checker.h.
247* Most features should be gated by base::Feature, API keys and/or gn build
248 configuration, not C preprocessor conditionals e.g. `#if
249 BUILDFLAG(FEATURE_FOO)`.
250 * We ship a single product (Chrome) to multiple platforms. The purpose of
Solomon Kinard8bdf8562024-05-29 21:25:27251 preprocessor conditionals is:
Erik Chen598410c2024-04-11 21:05:51252 * (1) to allow platform-agnostic code to reference platform-specific
253 code.
254 * e.g. `#if BUILDFLAG(OS_WIN)`
255 * (2) to glue src-internal into public //chromium/src.
256 * e.g. `#if BUILDFLAG(GOOGLE_CHROME_BRANDING)`
257 * (3) To make behavior changes to non-production binaries
258 * e.g. `#if !defined(NDEBUG)`
259 * e.g. `#if defined(ADDRESS_SANITIZER)`
260 * (1) primarily serves as glue.
261 * (2) turns Chromium into Chrome. We want the two to be as similar as
262 possible. This preprocessor conditional should be used very sparingly.
263 Almost all our tests are run against Chromium, so any logic behind this
264 conditional will be mostly untested.
265 * (3) is a last resort. The point of DEBUG/ASAN/... builds is to provide
266 insight into problems that affect the production binaries we ship. By
267 changing the production logic to do something different, we are no longer
268 accomplishing this goal.
269 * In all cases, large segments of code should not be gated behind
270 preprocessor conditionals. Instead, they should be pulled into separate
271 files via gn.
272 * In the event that a feature does have large swathes of code in separate
273 build files/translation units (e.g. extensions), using a custom feature
274 flag (e.g. `BUILDFLAG(ENABLE_EXTENSIONS)`) to glue this into the main source
275 is allowed. The glue code should be kept to a minimum.
276* Avoid run-time channel checking.
277* Avoid test only conditionals
278 * This was historically common in unit_tests, because it was not possible to
279 stub out dependencies due to lack of a clear API surface. By requiring
280 modular features with clear API surfaces, it also becomes easy to perform
281 dependency injection for tests, thereby removing the need for conditionals
282 that can be nullptr in tests.
283 * In the event that they are necessary, document and enforce via
284 `CHECK_IS_TEST()`.
Erik Chend086ae02024-08-20 22:53:33285 * As a corollary: do not use BrowserWithTestWindowTest. In production code,
286 there is a 1:1 correspondence between "class Browser" and "class
287 BrowserView". Features that span the two classes (which is most UI
288 features) should be able to unconditionally reference "class BrowserView".
289 The existence of this test suite forces features tested by these tests to
290 have "if (!browser_view)" test-only checks in production code. Either
291 write a browser test (where both classes are provided by the test fixture) or a unit test
292 that requires neither.
293 * This also comes from a corollary of don't use "class Browser".
294 Historically, features were written that take a "class Browser" as an
295 input parameter. "class Browser" cannot be stubbed/faked/mocked, and
296 BrowserWithTestWindowTest was written as a workaround as a way to
297 provide a "class Browser" without a "class BrowserView". New features
298 should not be taking "class Browser" as input, and should instead
299 perform dependency injection.
Erik Chen598410c2024-04-11 21:05:51300* Avoid unreachable branches.
301 * We should have a semantic understanding of each path of control flow. How
302 is this reached? If we don't know, then we should not have a conditional.
303* For a given `base::Callback`, execution should either be always synchronous, or
304always asynchronous. Mixing the two means callers have to deal with two distinct
305control flows, which often leads to incorrect handling of one.
306Avoid the following:
307```cpp
308if (result.cached) {
309 RunCallbackSync())
310} else {
311 GetResultAndRunCallbackAsync()
312}
313```
314* Avoid re-entrancy. Control flow should remain as localized as possible.
315Bad (unnecessary delegation, re-entrancy)
316```cpp
Solomon Kinardc164d5a2024-05-28 21:38:37317class CarFactory {
318 std::unique_ptr<Car> CreateCar() {
319 if (!CanCreateCar()) {
320 return nullptr;
321 }
322 if (FactoryIsBusy() && !delegate->ShouldShowCarIfFactoryIsBusy()) {
323 return nullptr;
324 }
325 return std::make_unique<Car>();
326 }
327
328 bool CanCreateCar();
329 bool FactoryIsBusy();
330
331 Delegate* delegate_ = nullptr;
332};
333
Erik Chen598410c2024-04-11 21:05:51334class CarSalesPerson : public Delegate {
Solomon Kinardc164d5a2024-05-28 21:38:37335 // Can return nullptr, in which case no car is shown.
Erik Chen598410c2024-04-11 21:05:51336 std::unique_ptr<Car> ShowCar() {
337 return car_factory_->CreateCar();
338 }
339
340 // Delegate overrides:
341 // Whether the car should be shown, even if the factory is busy.
342 bool ShouldShowCarIfFactoryIsBusy() override;
343
Solomon Kinardc164d5a2024-05-28 21:38:37344 CarFactory* car_factory_ = nullptr;
Erik Chen598410c2024-04-11 21:05:51345};
346```
347
348Good, version 1: Remove delegation. Pass all relevant state to CarFactory so that CreateCar() does not depend on non-local state.
349```cpp
Solomon Kinardc164d5a2024-05-28 21:38:37350class CarFactory {
351 std::unique_ptr<Car> CreateCar(bool show_even_if_factory_is_busy) {
352 if (!CanCreateCar()) {
353 return nullptr;
354 }
355 if (FactoryIsBusy() && !show_even_if_factory_is_busy) {
356 return nullptr;
357 }
358 return std::make_unique<Car>();
359 }
360 bool CanCreateCar();
361 bool FactoryIsBusy();
362};
363
Erik Chen598410c2024-04-11 21:05:51364class CarSalesPerson {
Solomon Kinardc164d5a2024-05-28 21:38:37365 // Can return nullptr, in which case no car is shown.
Erik Chen598410c2024-04-11 21:05:51366 std::unique_ptr<Car> ShowCar() {
367 return car_factory_->CreateCar(ShouldShowCarIfFactoryIsBusy());
368 }
369
370 // Whether the car should be shown, even if the factory is busy.
371 bool ShouldShowCarIfFactoryIsBusy();
372
Solomon Kinardc164d5a2024-05-28 21:38:37373 CarFactory* car_factory_ = nullptr;
Erik Chen598410c2024-04-11 21:05:51374};
375```
376
Solomon Kinarddc9f9b022024-05-28 21:37:29377Good, version 2: Remove delegation. CreateCar always creates a car (fewer conditionals). State only flows from CarFactory to CarSalesPerson (and never backwards).
Erik Chen598410c2024-04-11 21:05:51378```cpp
Solomon Kinardc164d5a2024-05-28 21:38:37379class CarFactory {
380 bool CanCreateCar();
381 bool FactoryIsBusy();
382 // Never returns nullptr.
383 std::unique_ptr<Car> CreateCar();
384};
385
Erik Chen598410c2024-04-11 21:05:51386class CarSalesPerson {
387 // Can return nullptr, in which case no car is shown
388 std::unique_ptr<Car> ShowCar() {
Solomon Kinardc164d5a2024-05-28 21:38:37389 if (!car_factory_->CanCreateCar()) {
Erik Chen598410c2024-04-11 21:05:51390 return nullptr;
Solomon Kinardc164d5a2024-05-28 21:38:37391 }
392 if (car_factory_->FactoryIsBusy() && !ShouldShowCarIfFactoryIsBusy()) {
Erik Chen598410c2024-04-11 21:05:51393 return nullptr;
Solomon Kinardc164d5a2024-05-28 21:38:37394 }
Erik Chen598410c2024-04-11 21:05:51395 return car_factory_->CreateCar();
396 }
397
398 // Whether the car should be shown, even if the factory is busy.
399 bool ShouldShowCarIfFactoryIsBusy();
Solomon Kinardc164d5a2024-05-28 21:38:37400 CarFactory* car_factory_ = nullptr;
Erik Chen598410c2024-04-11 21:05:51401};
402```
403
Erik Chenf652e612024-07-17 21:38:56404* Circular dependencies are a symptom of problematic design.
Erik Chen598410c2024-04-11 21:05:51405
Erik Chenf652e612024-07-17 21:38:56406Bad. FeatureShowcase depends on FeatureA. FeatureA depends on FeatureB.
407FeatureB depends on FeatureShowcase. The root problem is that the design for
408FeatureShowcase uses both a pull and a push model for control flow.
409```cpp
410// Shows an icon per feature. Needs to know whether each icon is visible.
411class FeatureShowcase {
412 FeatureShowcase() {
413 // Checks whether A should be visible, and if so, shows A.
414 if (FeatureA::IsVisible())
415 ShowFeatureA();
416 }
417
418 // Called to make B visible.
419 void ShowFeatureB();
420
421};
422
423class FeatureA {
424 // Feature A depends on feature B.
425 FeatureA(FeatureB* b);
426
427 static bool IsVisible();
428};
429
430class FeatureB {
431 FeatureB(FeatureShowcase* showcase) {
432 if (IsVisible())
433 showcase->ShowFeatureB();
434 }
435 static bool IsVisible();
436};
437```
438
439Good, version 1. FeatureShowcase uses a pull model for control flow.
440FeatureShowcase depends on FeatureA and FeatureB. FeatureA depends on FeatureB.
441There is no circular dependency.
442```cpp
443// Shows an icon per feature. Needs to know whether each icon is visible.
444class FeatureShowcase {
445 FeatureShowcase() {
446 if (FeatureA::IsVisible())
447 ShowFeatureA();
448 if (FeatureB::IsVisible())
449 ShowFeatureB();
450 }
451};
452
453class FeatureA {
454 // Feature A depends on feature B.
455 FeatureA(FeatureB* b);
456
457 static bool IsVisible();
458};
459
460class FeatureB {
461 FeatureB();
462 static bool IsVisible();
463};
464```
465
466Good, version 2. FeatureShowcase uses a push model for control flow. FeatureA
467and FeatureB both depend on FeatureShowcase. There is no circular dependency.
468```cpp
469// Shows an icon per feature. Needs to know whether each icon is visible.
470class FeatureShowcase {
471 FeatureShowcase();
472
473 // Called to make A visible.
474 void ShowFeatureA();
475
476 // Called to make B visible.
477 void ShowFeatureB();
478};
479
480class FeatureA {
481 // Feature A depends on feature B.
482 FeatureA(FeatureB* b, FeatureShowcase* showcase) {
483 if (IsVisible())
484 showcase->ShowFeatureA();
485 }
486
487 static bool IsVisible();
488};
489
490class FeatureB {
491 FeatureB(FeatureShowcase* showcase) {
492 if (IsVisible())
493 showcase->ShowFeatureB();
494 }
495
496 static bool IsVisible();
497};
498```