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