blob: 439067a6dfd8abef66d4e9cc8e7ff275a801de1b [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>.
17 * WebUI resources are the only exception. They will continue to live in
18 //chrome/browser/resources/<feature> alongside standalone BUILD.gn files.
19 * This directory should have a standalone BUILD.gn and OWNERs file.
Erik Chen9c0281b2024-05-29 23:41:2020 * All files in the directory should belong to targets in the BUILD.gn.
Erik Chen288c96772024-08-12 21:14:4621 * Do NOT add to `//chrome/browser/BUILD.gn:browser`,
22 `//chrome/test/BUILD.gn` or `//chrome/browser/ui/BUILD.gn:ui`.
23 * gn circular dependencies are disallowed. Logical
24 circular dependencies are allowed (for legacy reasons) but discouraged.
25 * [Lens
26 overlay](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/lens/BUILD.gn;drc=8e2c1c747f15a93c55ab2f10ebc8b32801ba129e)
27 is an example of a feature with no circular dependencies.
28 * The BUILD.gn should use public/sources separation.
29 * The main reason for this is to guard against future, unexpected usage
30 of parts of the code that were intended to be private. This makes it
31 difficult to change implementation details in the future.
32 * This directory may have a public/ subdirectory to enforce further
33 encapsulation, though this example does not use it.
34 * [cookie
35 controls](https://chromium-review.googlesource.com/c/chromium/src/+/5771416/5/chrome/browser/ui/cookie_controls/BUILD.gn)
36 is an example of a feature with logical circular dependencies.
37 * The header files are moved into a "cookie_controls" target with no
38 circular dependencies.
39 * The cc files are moved into a "impl" target, with circular
40 dependencies allowed with `//chrome/browser:browser` and
41 `//chrome/browser/ui:ui`. These circular dependencies will
42 disappear when all sources are removed from `//chrome/browser:browser` and `//chrome/browser/ui:ui`.
43 * The separation between header and cc files is functionally
44 equivalent to creating abstract base classes in one target, with
45 h/cc files in a separate target. This just skips the boilerplate
46 of creating the abstract base classes.
47 * Even though there are no build circular dependencies, there are
48 still logical circular dependencies from the cc files. This
49 discrepancy is because C++ allows headers to forward declare
50 dependencies, which do not need to be reflected in gn.
Erik Chen598410c2024-04-11 21:05:5151 * This directory may have its own namespace.
Erik Chen598410c2024-04-11 21:05:5152 * Corollary: There are several global functions that facilitate dependency
53 inversion. It will not be possible to call them from modularized features
54 (no dependency cycles), and their usage in non-modularized features is
55 considered a red flag:
56 * `chrome::FindBrowserWithTab` (and everything in browser_finder.h)
57 * `GetBrowserViewForNativeWindow` (via browser_view.h)
58 * `FindBrowserWindowWithWebContents` (via browser_window.h)
Erik Chen63f6f1a2024-08-21 01:33:0459 * Corollary: Don't use `Browser*`. This is functionally a container of
60 hundreds of other pointers. It is impossible to specify dependencies,
61 since `Browser*` functionally depends on everything. Instead, pass in the
62 relevant pointers, e.g. `Profile*`, `FooFeatureController`, etc.
63 * Code that uses `Browser*` is also impossible to properly unit test.
Erik Chen9c0281b2024-05-29 23:41:2064 * Rationale: Modularity enforces the creation of API surfaces and explicit
65 dependencies. This has several positive externalities:
66 * Separation of interface from implementation prevents unnecessarly
67 tight coupling between features. This in turn reduces spooky action at
68 a distance, where seemingly innocuous changes break a distant,
69 supposedly unrelated feature.
70 * Explicit listing of circular dependencies exposes the likely fragile
71 areas of code.
72 * Alongside the later guidance of global functions must be pure,
73 modularity adds the requirement that test-code perform dependency
74 injection. This eliminates a common bug where test behavior diverges
75 from production behavior, and logic is added to production code to
76 work around test-only behaviors.
Erik Chen598410c2024-04-11 21:05:5177
78* Features should have a core controller with precise lifetime semantics. The
79 core controller for most desktop features should be owned and instantiated by
80 one of the following classes:
81 * `TabFeatures` (member of `TabModel`)
82 * This class should own all tab-centric features. e.g. print preview,
83 lens overlay, compose, find-in-page, etc.
84 * If the feature requires instantiation of
85 `WebContents::SupportsUserData`, it should be done in this class.
86 * For desktop chrome, `TabHelpers::AttachTabHelpers` will become a
87 remove-only method. Clank/WebView may continue to use section 2 of
88 `TabHelpers::AttachTabHelpers` (Clank/WebView only).
89 * More complex features that also target mobile platforms or other
90 supported embedders (e.g. android webview) will continue to use the
91 layered components architecture.
92 * We defer to //components/OWNERS for expertise and feedback on the
93 architecture of these features, and encourage feature-owners to
94 proactively reach out to them.
95 * Lazy instantiation of `WebContents::SupportsUserData` is an
96 anti-pattern.
97 * `BrowserWindowFeatures` (member of `Browser`)
98 * example: omnibox, security chip, bookmarks bar, side panel
99 * `BrowserContextKeyedServiceFactory` (functionally a member of `Profile`)
Erik Chend5431602024-05-23 20:20:58100 * Override `ServiceIsCreatedWithBrowserContext` to return `true`. This
101 guarantees precise lifetime semantics.
102 * Lazy instantiation is an anti-pattern.
103 * Production code is started via `content::ContentMain()`. Test
104 harnesses use a test-suite dependent start-point, e.g.
105 `base::LaunchUnitTests`. Tests typically instantiate a subset of
106 the lazily-instantiated factories instantiated by production code,
107 at a different time, with a different order. This results in
108 different, sometimes broken behavior in tests. This is typically
109 papered over by modifying the production logic to include
110 otherwise unnecessary conditionals, typically early-exits.
111 Overriding `ServiceIsCreatedWithBrowserContext` guarantees
112 identical behavior between test and production code.
113 * Use `TestingProfile::Builder::AddTestingFactory` to stub or fake
114 services.
Erik Chen16208722024-06-06 00:09:08115 * Separating the .h and .cc file into different build targets is
116 allowed.
117 * BrowserContextKeyedServiceFactory combines 3 pieces of
118 functionality:
119 * A getter to receive a service based on an instance of
120 `Profile`.
121 * A mechanism to establishing dependencies.
122 * A way to glue together //chrome layer logic with //components
123 layer logic.
124 * The latter two pieces of functionality are implemented in the
125 .cc file and typically result in dependencies on other parts
126 of //chrome. The first piece of functionality is implemented
127 in the .h file and does not necessarily introduce a dependency
128 on //chrome, since the returned service can be defined in
129 //components.
Erik Chenea810ac2024-07-29 23:44:32130 * GlobalFeatures.
131 * Features which are scoped to the entire process and span multiple
132 Profiles should be members of GlobalFeatures.
133 * GlobalFeatures is a member of BrowserProcess and they have similar
134 lifetime semantics. The main difference is that historically
135 BrowserProcess used the antipattern of lazy instantiation, and the
136 setup of TestingBrowserProcess encourages divergence between test
137 logic and production logic. On the other hand, GlobalFeatures is
138 always instantiated.
139 * This is not making any statements about initialization (e.g.
140 performing non-trivial setup).
Erik Chen598410c2024-04-11 21:05:51141 * The core controller should not be a `NoDestructor` singleton.
142* Global functions should not access non-global state.
143 * Pure functions do not access global state and are allowed. e.g. `base::UTF8ToWide()`
144 * Global functions that wrap global state are allowed, e.g.
145 `IsFooFeatureEnabled()` wraps the global variable
146 `BASE_FEATURE(kFooFeature,...)`
147 * Global functions that access non-global state are disallowed. e.g.
148 static methods on `BrowserList`.
Erik Chen08053b12024-05-25 01:05:41149* No distinction between `//chrome/browser/BUILD.gn` and
150 `//chrome/browser/ui/BUILD.gn`
151 * There is plenty of UI code outside of the `ui` subdirectory, and plenty of
152 non-UI code inside of the `ui` subdirectory. Currently the two BUILD files
153 allow circular includes. We will continue to treat these directories and
154 BUILD files as interchangeable.
Erik Chen598410c2024-04-11 21:05:51155
156## UI
157* Features should use WebUI and Views toolkit, which are x-platform.
158 * Usage of underlying primitives is discouraged (aura, Cocoa, gtk, win32,
159 etc.). This is usually a sign that either the feature is misusing the UI
160 toolkits, or that the UI toolkits are missing important functionality.
161* Features should use "MVC"
162 * Clear separation between controller (control flow), view (presentation of
163 UI) and model (storage of data).
164 * For simple features that do not require data persistence, we only require
165 separation of controller from view.
166 * TODO: work with UI/toolkit team to come up with appropriate examples.
Erik Chen08053b12024-05-25 01:05:41167* Views:
168 * For simple configuration changes, prefer to use existing setter methods
169 and builder syntax.
170 * Feel free to create custom view subclasses to encapsulate logic or
171 override methods where doing so helps implement layout as the composition
172 of smaller standard layouts, or similar. Don't try to jam the kitchen sink
173 into a single giant view.
174 * However, avoid subclassing existing concrete view subclasses, e.g. to add
175 or tweak existing behavior. This risks violating the Google style guidance
176 on multiple inheritance and makes maintenance challenging. In particular
177 do not do this with core controls, as the behaviors of common controls
178 should not vary across the product.
179* Avoid subclassing Widgets.
Erik Chen598410c2024-04-11 21:05:51180* Avoid self-owned objects/classes for views or controllers.
181
182## General
183* Code unrelated to building a web-browser should not live in //chrome.
184 * See [chromeos/code.md](chromeos/code.md) for details on ChromeOS (non-browser) code.
185 * We may move some modularized x-platform code into //components. The main
186 distinction is that ChromeOS can depend on //components, but not on
187 //chrome. This will be evaluated on a case-by-case basis.
188* Avoid nested message loops.
189* Threaded code should have DCHECKs asserting correct sequence.
190 * Provides documentation and correctness checks.
191 * See base/sequence_checker.h.
192* Most features should be gated by base::Feature, API keys and/or gn build
193 configuration, not C preprocessor conditionals e.g. `#if
194 BUILDFLAG(FEATURE_FOO)`.
195 * We ship a single product (Chrome) to multiple platforms. The purpose of
Solomon Kinard8bdf8562024-05-29 21:25:27196 preprocessor conditionals is:
Erik Chen598410c2024-04-11 21:05:51197 * (1) to allow platform-agnostic code to reference platform-specific
198 code.
199 * e.g. `#if BUILDFLAG(OS_WIN)`
200 * (2) to glue src-internal into public //chromium/src.
201 * e.g. `#if BUILDFLAG(GOOGLE_CHROME_BRANDING)`
202 * (3) To make behavior changes to non-production binaries
203 * e.g. `#if !defined(NDEBUG)`
204 * e.g. `#if defined(ADDRESS_SANITIZER)`
205 * (1) primarily serves as glue.
206 * (2) turns Chromium into Chrome. We want the two to be as similar as
207 possible. This preprocessor conditional should be used very sparingly.
208 Almost all our tests are run against Chromium, so any logic behind this
209 conditional will be mostly untested.
210 * (3) is a last resort. The point of DEBUG/ASAN/... builds is to provide
211 insight into problems that affect the production binaries we ship. By
212 changing the production logic to do something different, we are no longer
213 accomplishing this goal.
214 * In all cases, large segments of code should not be gated behind
215 preprocessor conditionals. Instead, they should be pulled into separate
216 files via gn.
217 * In the event that a feature does have large swathes of code in separate
218 build files/translation units (e.g. extensions), using a custom feature
219 flag (e.g. `BUILDFLAG(ENABLE_EXTENSIONS)`) to glue this into the main source
220 is allowed. The glue code should be kept to a minimum.
221* Avoid run-time channel checking.
222* Avoid test only conditionals
223 * This was historically common in unit_tests, because it was not possible to
224 stub out dependencies due to lack of a clear API surface. By requiring
225 modular features with clear API surfaces, it also becomes easy to perform
226 dependency injection for tests, thereby removing the need for conditionals
227 that can be nullptr in tests.
228 * In the event that they are necessary, document and enforce via
229 `CHECK_IS_TEST()`.
Erik Chend086ae02024-08-20 22:53:33230 * As a corollary: do not use BrowserWithTestWindowTest. In production code,
231 there is a 1:1 correspondence between "class Browser" and "class
232 BrowserView". Features that span the two classes (which is most UI
233 features) should be able to unconditionally reference "class BrowserView".
234 The existence of this test suite forces features tested by these tests to
235 have "if (!browser_view)" test-only checks in production code. Either
236 write a browser test (where both classes are provided by the test fixture) or a unit test
237 that requires neither.
238 * This also comes from a corollary of don't use "class Browser".
239 Historically, features were written that take a "class Browser" as an
240 input parameter. "class Browser" cannot be stubbed/faked/mocked, and
241 BrowserWithTestWindowTest was written as a workaround as a way to
242 provide a "class Browser" without a "class BrowserView". New features
243 should not be taking "class Browser" as input, and should instead
244 perform dependency injection.
Erik Chen598410c2024-04-11 21:05:51245* Avoid unreachable branches.
246 * We should have a semantic understanding of each path of control flow. How
247 is this reached? If we don't know, then we should not have a conditional.
248* For a given `base::Callback`, execution should either be always synchronous, or
249always asynchronous. Mixing the two means callers have to deal with two distinct
250control flows, which often leads to incorrect handling of one.
251Avoid the following:
252```cpp
253if (result.cached) {
254 RunCallbackSync())
255} else {
256 GetResultAndRunCallbackAsync()
257}
258```
259* Avoid re-entrancy. Control flow should remain as localized as possible.
260Bad (unnecessary delegation, re-entrancy)
261```cpp
Solomon Kinardc164d5a2024-05-28 21:38:37262class CarFactory {
263 std::unique_ptr<Car> CreateCar() {
264 if (!CanCreateCar()) {
265 return nullptr;
266 }
267 if (FactoryIsBusy() && !delegate->ShouldShowCarIfFactoryIsBusy()) {
268 return nullptr;
269 }
270 return std::make_unique<Car>();
271 }
272
273 bool CanCreateCar();
274 bool FactoryIsBusy();
275
276 Delegate* delegate_ = nullptr;
277};
278
Erik Chen598410c2024-04-11 21:05:51279class CarSalesPerson : public Delegate {
Solomon Kinardc164d5a2024-05-28 21:38:37280 // Can return nullptr, in which case no car is shown.
Erik Chen598410c2024-04-11 21:05:51281 std::unique_ptr<Car> ShowCar() {
282 return car_factory_->CreateCar();
283 }
284
285 // Delegate overrides:
286 // Whether the car should be shown, even if the factory is busy.
287 bool ShouldShowCarIfFactoryIsBusy() override;
288
Solomon Kinardc164d5a2024-05-28 21:38:37289 CarFactory* car_factory_ = nullptr;
Erik Chen598410c2024-04-11 21:05:51290};
291```
292
293Good, version 1: Remove delegation. Pass all relevant state to CarFactory so that CreateCar() does not depend on non-local state.
294```cpp
Solomon Kinardc164d5a2024-05-28 21:38:37295class CarFactory {
296 std::unique_ptr<Car> CreateCar(bool show_even_if_factory_is_busy) {
297 if (!CanCreateCar()) {
298 return nullptr;
299 }
300 if (FactoryIsBusy() && !show_even_if_factory_is_busy) {
301 return nullptr;
302 }
303 return std::make_unique<Car>();
304 }
305 bool CanCreateCar();
306 bool FactoryIsBusy();
307};
308
Erik Chen598410c2024-04-11 21:05:51309class CarSalesPerson {
Solomon Kinardc164d5a2024-05-28 21:38:37310 // Can return nullptr, in which case no car is shown.
Erik Chen598410c2024-04-11 21:05:51311 std::unique_ptr<Car> ShowCar() {
312 return car_factory_->CreateCar(ShouldShowCarIfFactoryIsBusy());
313 }
314
315 // Whether the car should be shown, even if the factory is busy.
316 bool ShouldShowCarIfFactoryIsBusy();
317
Solomon Kinardc164d5a2024-05-28 21:38:37318 CarFactory* car_factory_ = nullptr;
Erik Chen598410c2024-04-11 21:05:51319};
320```
321
Solomon Kinarddc9f9b022024-05-28 21:37:29322Good, 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:51323```cpp
Solomon Kinardc164d5a2024-05-28 21:38:37324class CarFactory {
325 bool CanCreateCar();
326 bool FactoryIsBusy();
327 // Never returns nullptr.
328 std::unique_ptr<Car> CreateCar();
329};
330
Erik Chen598410c2024-04-11 21:05:51331class CarSalesPerson {
332 // Can return nullptr, in which case no car is shown
333 std::unique_ptr<Car> ShowCar() {
Solomon Kinardc164d5a2024-05-28 21:38:37334 if (!car_factory_->CanCreateCar()) {
Erik Chen598410c2024-04-11 21:05:51335 return nullptr;
Solomon Kinardc164d5a2024-05-28 21:38:37336 }
337 if (car_factory_->FactoryIsBusy() && !ShouldShowCarIfFactoryIsBusy()) {
Erik Chen598410c2024-04-11 21:05:51338 return nullptr;
Solomon Kinardc164d5a2024-05-28 21:38:37339 }
Erik Chen598410c2024-04-11 21:05:51340 return car_factory_->CreateCar();
341 }
342
343 // Whether the car should be shown, even if the factory is busy.
344 bool ShouldShowCarIfFactoryIsBusy();
Solomon Kinardc164d5a2024-05-28 21:38:37345 CarFactory* car_factory_ = nullptr;
Erik Chen598410c2024-04-11 21:05:51346};
347```
348
Erik Chenf652e612024-07-17 21:38:56349* Circular dependencies are a symptom of problematic design.
Erik Chen598410c2024-04-11 21:05:51350
Erik Chenf652e612024-07-17 21:38:56351Bad. FeatureShowcase depends on FeatureA. FeatureA depends on FeatureB.
352FeatureB depends on FeatureShowcase. The root problem is that the design for
353FeatureShowcase uses both a pull and a push model for control flow.
354```cpp
355// Shows an icon per feature. Needs to know whether each icon is visible.
356class FeatureShowcase {
357 FeatureShowcase() {
358 // Checks whether A should be visible, and if so, shows A.
359 if (FeatureA::IsVisible())
360 ShowFeatureA();
361 }
362
363 // Called to make B visible.
364 void ShowFeatureB();
365
366};
367
368class FeatureA {
369 // Feature A depends on feature B.
370 FeatureA(FeatureB* b);
371
372 static bool IsVisible();
373};
374
375class FeatureB {
376 FeatureB(FeatureShowcase* showcase) {
377 if (IsVisible())
378 showcase->ShowFeatureB();
379 }
380 static bool IsVisible();
381};
382```
383
384Good, version 1. FeatureShowcase uses a pull model for control flow.
385FeatureShowcase depends on FeatureA and FeatureB. FeatureA depends on FeatureB.
386There is no circular dependency.
387```cpp
388// Shows an icon per feature. Needs to know whether each icon is visible.
389class FeatureShowcase {
390 FeatureShowcase() {
391 if (FeatureA::IsVisible())
392 ShowFeatureA();
393 if (FeatureB::IsVisible())
394 ShowFeatureB();
395 }
396};
397
398class FeatureA {
399 // Feature A depends on feature B.
400 FeatureA(FeatureB* b);
401
402 static bool IsVisible();
403};
404
405class FeatureB {
406 FeatureB();
407 static bool IsVisible();
408};
409```
410
411Good, version 2. FeatureShowcase uses a push model for control flow. FeatureA
412and FeatureB both depend on FeatureShowcase. There is no circular dependency.
413```cpp
414// Shows an icon per feature. Needs to know whether each icon is visible.
415class FeatureShowcase {
416 FeatureShowcase();
417
418 // Called to make A visible.
419 void ShowFeatureA();
420
421 // Called to make B visible.
422 void ShowFeatureB();
423};
424
425class FeatureA {
426 // Feature A depends on feature B.
427 FeatureA(FeatureB* b, FeatureShowcase* showcase) {
428 if (IsVisible())
429 showcase->ShowFeatureA();
430 }
431
432 static bool IsVisible();
433};
434
435class FeatureB {
436 FeatureB(FeatureShowcase* showcase) {
437 if (IsVisible())
438 showcase->ShowFeatureB();
439 }
440
441 static bool IsVisible();
442};
443```
close