blob: e5ee401fd94c2859aee12bf7abeed6985bd70f9e [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 Chen9c0281b2024-05-29 23:41:2059 * Rationale: Modularity enforces the creation of API surfaces and explicit
60 dependencies. This has several positive externalities:
61 * Separation of interface from implementation prevents unnecessarly
62 tight coupling between features. This in turn reduces spooky action at
63 a distance, where seemingly innocuous changes break a distant,
64 supposedly unrelated feature.
65 * Explicit listing of circular dependencies exposes the likely fragile
66 areas of code.
67 * Alongside the later guidance of global functions must be pure,
68 modularity adds the requirement that test-code perform dependency
69 injection. This eliminates a common bug where test behavior diverges
70 from production behavior, and logic is added to production code to
71 work around test-only behaviors.
Erik Chen598410c2024-04-11 21:05:5172
73* Features should have a core controller with precise lifetime semantics. The
74 core controller for most desktop features should be owned and instantiated by
75 one of the following classes:
76 * `TabFeatures` (member of `TabModel`)
77 * This class should own all tab-centric features. e.g. print preview,
78 lens overlay, compose, find-in-page, etc.
79 * If the feature requires instantiation of
80 `WebContents::SupportsUserData`, it should be done in this class.
81 * For desktop chrome, `TabHelpers::AttachTabHelpers` will become a
82 remove-only method. Clank/WebView may continue to use section 2 of
83 `TabHelpers::AttachTabHelpers` (Clank/WebView only).
84 * More complex features that also target mobile platforms or other
85 supported embedders (e.g. android webview) will continue to use the
86 layered components architecture.
87 * We defer to //components/OWNERS for expertise and feedback on the
88 architecture of these features, and encourage feature-owners to
89 proactively reach out to them.
90 * Lazy instantiation of `WebContents::SupportsUserData` is an
91 anti-pattern.
92 * `BrowserWindowFeatures` (member of `Browser`)
93 * example: omnibox, security chip, bookmarks bar, side panel
94 * `BrowserContextKeyedServiceFactory` (functionally a member of `Profile`)
Erik Chend5431602024-05-23 20:20:5895 * Override `ServiceIsCreatedWithBrowserContext` to return `true`. This
96 guarantees precise lifetime semantics.
97 * Lazy instantiation is an anti-pattern.
98 * Production code is started via `content::ContentMain()`. Test
99 harnesses use a test-suite dependent start-point, e.g.
100 `base::LaunchUnitTests`. Tests typically instantiate a subset of
101 the lazily-instantiated factories instantiated by production code,
102 at a different time, with a different order. This results in
103 different, sometimes broken behavior in tests. This is typically
104 papered over by modifying the production logic to include
105 otherwise unnecessary conditionals, typically early-exits.
106 Overriding `ServiceIsCreatedWithBrowserContext` guarantees
107 identical behavior between test and production code.
108 * Use `TestingProfile::Builder::AddTestingFactory` to stub or fake
109 services.
Erik Chen16208722024-06-06 00:09:08110 * Separating the .h and .cc file into different build targets is
111 allowed.
112 * BrowserContextKeyedServiceFactory combines 3 pieces of
113 functionality:
114 * A getter to receive a service based on an instance of
115 `Profile`.
116 * A mechanism to establishing dependencies.
117 * A way to glue together //chrome layer logic with //components
118 layer logic.
119 * The latter two pieces of functionality are implemented in the
120 .cc file and typically result in dependencies on other parts
121 of //chrome. The first piece of functionality is implemented
122 in the .h file and does not necessarily introduce a dependency
123 on //chrome, since the returned service can be defined in
124 //components.
Erik Chenea810ac2024-07-29 23:44:32125 * GlobalFeatures.
126 * Features which are scoped to the entire process and span multiple
127 Profiles should be members of GlobalFeatures.
128 * GlobalFeatures is a member of BrowserProcess and they have similar
129 lifetime semantics. The main difference is that historically
130 BrowserProcess used the antipattern of lazy instantiation, and the
131 setup of TestingBrowserProcess encourages divergence between test
132 logic and production logic. On the other hand, GlobalFeatures is
133 always instantiated.
134 * This is not making any statements about initialization (e.g.
135 performing non-trivial setup).
Erik Chen598410c2024-04-11 21:05:51136 * The core controller should not be a `NoDestructor` singleton.
137* Global functions should not access non-global state.
138 * Pure functions do not access global state and are allowed. e.g. `base::UTF8ToWide()`
139 * Global functions that wrap global state are allowed, e.g.
140 `IsFooFeatureEnabled()` wraps the global variable
141 `BASE_FEATURE(kFooFeature,...)`
142 * Global functions that access non-global state are disallowed. e.g.
143 static methods on `BrowserList`.
Erik Chen08053b12024-05-25 01:05:41144* No distinction between `//chrome/browser/BUILD.gn` and
145 `//chrome/browser/ui/BUILD.gn`
146 * There is plenty of UI code outside of the `ui` subdirectory, and plenty of
147 non-UI code inside of the `ui` subdirectory. Currently the two BUILD files
148 allow circular includes. We will continue to treat these directories and
149 BUILD files as interchangeable.
Erik Chen598410c2024-04-11 21:05:51150
151## UI
152* Features should use WebUI and Views toolkit, which are x-platform.
153 * Usage of underlying primitives is discouraged (aura, Cocoa, gtk, win32,
154 etc.). This is usually a sign that either the feature is misusing the UI
155 toolkits, or that the UI toolkits are missing important functionality.
156* Features should use "MVC"
157 * Clear separation between controller (control flow), view (presentation of
158 UI) and model (storage of data).
159 * For simple features that do not require data persistence, we only require
160 separation of controller from view.
161 * TODO: work with UI/toolkit team to come up with appropriate examples.
Erik Chen08053b12024-05-25 01:05:41162* Views:
163 * For simple configuration changes, prefer to use existing setter methods
164 and builder syntax.
165 * Feel free to create custom view subclasses to encapsulate logic or
166 override methods where doing so helps implement layout as the composition
167 of smaller standard layouts, or similar. Don't try to jam the kitchen sink
168 into a single giant view.
169 * However, avoid subclassing existing concrete view subclasses, e.g. to add
170 or tweak existing behavior. This risks violating the Google style guidance
171 on multiple inheritance and makes maintenance challenging. In particular
172 do not do this with core controls, as the behaviors of common controls
173 should not vary across the product.
174* Avoid subclassing Widgets.
Erik Chen598410c2024-04-11 21:05:51175* Avoid self-owned objects/classes for views or controllers.
176
177## General
178* Code unrelated to building a web-browser should not live in //chrome.
179 * See [chromeos/code.md](chromeos/code.md) for details on ChromeOS (non-browser) code.
180 * We may move some modularized x-platform code into //components. The main
181 distinction is that ChromeOS can depend on //components, but not on
182 //chrome. This will be evaluated on a case-by-case basis.
183* Avoid nested message loops.
184* Threaded code should have DCHECKs asserting correct sequence.
185 * Provides documentation and correctness checks.
186 * See base/sequence_checker.h.
187* Most features should be gated by base::Feature, API keys and/or gn build
188 configuration, not C preprocessor conditionals e.g. `#if
189 BUILDFLAG(FEATURE_FOO)`.
190 * We ship a single product (Chrome) to multiple platforms. The purpose of
Solomon Kinard8bdf8562024-05-29 21:25:27191 preprocessor conditionals is:
Erik Chen598410c2024-04-11 21:05:51192 * (1) to allow platform-agnostic code to reference platform-specific
193 code.
194 * e.g. `#if BUILDFLAG(OS_WIN)`
195 * (2) to glue src-internal into public //chromium/src.
196 * e.g. `#if BUILDFLAG(GOOGLE_CHROME_BRANDING)`
197 * (3) To make behavior changes to non-production binaries
198 * e.g. `#if !defined(NDEBUG)`
199 * e.g. `#if defined(ADDRESS_SANITIZER)`
200 * (1) primarily serves as glue.
201 * (2) turns Chromium into Chrome. We want the two to be as similar as
202 possible. This preprocessor conditional should be used very sparingly.
203 Almost all our tests are run against Chromium, so any logic behind this
204 conditional will be mostly untested.
205 * (3) is a last resort. The point of DEBUG/ASAN/... builds is to provide
206 insight into problems that affect the production binaries we ship. By
207 changing the production logic to do something different, we are no longer
208 accomplishing this goal.
209 * In all cases, large segments of code should not be gated behind
210 preprocessor conditionals. Instead, they should be pulled into separate
211 files via gn.
212 * In the event that a feature does have large swathes of code in separate
213 build files/translation units (e.g. extensions), using a custom feature
214 flag (e.g. `BUILDFLAG(ENABLE_EXTENSIONS)`) to glue this into the main source
215 is allowed. The glue code should be kept to a minimum.
216* Avoid run-time channel checking.
217* Avoid test only conditionals
218 * This was historically common in unit_tests, because it was not possible to
219 stub out dependencies due to lack of a clear API surface. By requiring
220 modular features with clear API surfaces, it also becomes easy to perform
221 dependency injection for tests, thereby removing the need for conditionals
222 that can be nullptr in tests.
223 * In the event that they are necessary, document and enforce via
224 `CHECK_IS_TEST()`.
Erik Chend086ae02024-08-20 22:53:33225 * As a corollary: do not use BrowserWithTestWindowTest. In production code,
226 there is a 1:1 correspondence between "class Browser" and "class
227 BrowserView". Features that span the two classes (which is most UI
228 features) should be able to unconditionally reference "class BrowserView".
229 The existence of this test suite forces features tested by these tests to
230 have "if (!browser_view)" test-only checks in production code. Either
231 write a browser test (where both classes are provided by the test fixture) or a unit test
232 that requires neither.
233 * This also comes from a corollary of don't use "class Browser".
234 Historically, features were written that take a "class Browser" as an
235 input parameter. "class Browser" cannot be stubbed/faked/mocked, and
236 BrowserWithTestWindowTest was written as a workaround as a way to
237 provide a "class Browser" without a "class BrowserView". New features
238 should not be taking "class Browser" as input, and should instead
239 perform dependency injection.
Erik Chen598410c2024-04-11 21:05:51240* Avoid unreachable branches.
241 * We should have a semantic understanding of each path of control flow. How
242 is this reached? If we don't know, then we should not have a conditional.
243* For a given `base::Callback`, execution should either be always synchronous, or
244always asynchronous. Mixing the two means callers have to deal with two distinct
245control flows, which often leads to incorrect handling of one.
246Avoid the following:
247```cpp
248if (result.cached) {
249 RunCallbackSync())
250} else {
251 GetResultAndRunCallbackAsync()
252}
253```
254* Avoid re-entrancy. Control flow should remain as localized as possible.
255Bad (unnecessary delegation, re-entrancy)
256```cpp
Solomon Kinardc164d5a2024-05-28 21:38:37257class CarFactory {
258 std::unique_ptr<Car> CreateCar() {
259 if (!CanCreateCar()) {
260 return nullptr;
261 }
262 if (FactoryIsBusy() && !delegate->ShouldShowCarIfFactoryIsBusy()) {
263 return nullptr;
264 }
265 return std::make_unique<Car>();
266 }
267
268 bool CanCreateCar();
269 bool FactoryIsBusy();
270
271 Delegate* delegate_ = nullptr;
272};
273
Erik Chen598410c2024-04-11 21:05:51274class CarSalesPerson : public Delegate {
Solomon Kinardc164d5a2024-05-28 21:38:37275 // Can return nullptr, in which case no car is shown.
Erik Chen598410c2024-04-11 21:05:51276 std::unique_ptr<Car> ShowCar() {
277 return car_factory_->CreateCar();
278 }
279
280 // Delegate overrides:
281 // Whether the car should be shown, even if the factory is busy.
282 bool ShouldShowCarIfFactoryIsBusy() override;
283
Solomon Kinardc164d5a2024-05-28 21:38:37284 CarFactory* car_factory_ = nullptr;
Erik Chen598410c2024-04-11 21:05:51285};
286```
287
288Good, version 1: Remove delegation. Pass all relevant state to CarFactory so that CreateCar() does not depend on non-local state.
289```cpp
Solomon Kinardc164d5a2024-05-28 21:38:37290class CarFactory {
291 std::unique_ptr<Car> CreateCar(bool show_even_if_factory_is_busy) {
292 if (!CanCreateCar()) {
293 return nullptr;
294 }
295 if (FactoryIsBusy() && !show_even_if_factory_is_busy) {
296 return nullptr;
297 }
298 return std::make_unique<Car>();
299 }
300 bool CanCreateCar();
301 bool FactoryIsBusy();
302};
303
Erik Chen598410c2024-04-11 21:05:51304class CarSalesPerson {
Solomon Kinardc164d5a2024-05-28 21:38:37305 // Can return nullptr, in which case no car is shown.
Erik Chen598410c2024-04-11 21:05:51306 std::unique_ptr<Car> ShowCar() {
307 return car_factory_->CreateCar(ShouldShowCarIfFactoryIsBusy());
308 }
309
310 // Whether the car should be shown, even if the factory is busy.
311 bool ShouldShowCarIfFactoryIsBusy();
312
Solomon Kinardc164d5a2024-05-28 21:38:37313 CarFactory* car_factory_ = nullptr;
Erik Chen598410c2024-04-11 21:05:51314};
315```
316
Solomon Kinarddc9f9b022024-05-28 21:37:29317Good, 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:51318```cpp
Solomon Kinardc164d5a2024-05-28 21:38:37319class CarFactory {
320 bool CanCreateCar();
321 bool FactoryIsBusy();
322 // Never returns nullptr.
323 std::unique_ptr<Car> CreateCar();
324};
325
Erik Chen598410c2024-04-11 21:05:51326class CarSalesPerson {
327 // Can return nullptr, in which case no car is shown
328 std::unique_ptr<Car> ShowCar() {
Solomon Kinardc164d5a2024-05-28 21:38:37329 if (!car_factory_->CanCreateCar()) {
Erik Chen598410c2024-04-11 21:05:51330 return nullptr;
Solomon Kinardc164d5a2024-05-28 21:38:37331 }
332 if (car_factory_->FactoryIsBusy() && !ShouldShowCarIfFactoryIsBusy()) {
Erik Chen598410c2024-04-11 21:05:51333 return nullptr;
Solomon Kinardc164d5a2024-05-28 21:38:37334 }
Erik Chen598410c2024-04-11 21:05:51335 return car_factory_->CreateCar();
336 }
337
338 // Whether the car should be shown, even if the factory is busy.
339 bool ShouldShowCarIfFactoryIsBusy();
Solomon Kinardc164d5a2024-05-28 21:38:37340 CarFactory* car_factory_ = nullptr;
Erik Chen598410c2024-04-11 21:05:51341};
342```
343
Erik Chenf652e612024-07-17 21:38:56344* Circular dependencies are a symptom of problematic design.
Erik Chen598410c2024-04-11 21:05:51345
Erik Chenf652e612024-07-17 21:38:56346Bad. FeatureShowcase depends on FeatureA. FeatureA depends on FeatureB.
347FeatureB depends on FeatureShowcase. The root problem is that the design for
348FeatureShowcase uses both a pull and a push model for control flow.
349```cpp
350// Shows an icon per feature. Needs to know whether each icon is visible.
351class FeatureShowcase {
352 FeatureShowcase() {
353 // Checks whether A should be visible, and if so, shows A.
354 if (FeatureA::IsVisible())
355 ShowFeatureA();
356 }
357
358 // Called to make B visible.
359 void ShowFeatureB();
360
361};
362
363class FeatureA {
364 // Feature A depends on feature B.
365 FeatureA(FeatureB* b);
366
367 static bool IsVisible();
368};
369
370class FeatureB {
371 FeatureB(FeatureShowcase* showcase) {
372 if (IsVisible())
373 showcase->ShowFeatureB();
374 }
375 static bool IsVisible();
376};
377```
378
379Good, version 1. FeatureShowcase uses a pull model for control flow.
380FeatureShowcase depends on FeatureA and FeatureB. FeatureA depends on FeatureB.
381There is no circular dependency.
382```cpp
383// Shows an icon per feature. Needs to know whether each icon is visible.
384class FeatureShowcase {
385 FeatureShowcase() {
386 if (FeatureA::IsVisible())
387 ShowFeatureA();
388 if (FeatureB::IsVisible())
389 ShowFeatureB();
390 }
391};
392
393class FeatureA {
394 // Feature A depends on feature B.
395 FeatureA(FeatureB* b);
396
397 static bool IsVisible();
398};
399
400class FeatureB {
401 FeatureB();
402 static bool IsVisible();
403};
404```
405
406Good, version 2. FeatureShowcase uses a push model for control flow. FeatureA
407and FeatureB both depend on FeatureShowcase. There is no circular dependency.
408```cpp
409// Shows an icon per feature. Needs to know whether each icon is visible.
410class FeatureShowcase {
411 FeatureShowcase();
412
413 // Called to make A visible.
414 void ShowFeatureA();
415
416 // Called to make B visible.
417 void ShowFeatureB();
418};
419
420class FeatureA {
421 // Feature A depends on feature B.
422 FeatureA(FeatureB* b, FeatureShowcase* showcase) {
423 if (IsVisible())
424 showcase->ShowFeatureA();
425 }
426
427 static bool IsVisible();
428};
429
430class FeatureB {
431 FeatureB(FeatureShowcase* showcase) {
432 if (IsVisible())
433 showcase->ShowFeatureB();
434 }
435
436 static bool IsVisible();
437};
438```