processes.rst (65206B)
1 Gecko Processes 2 =============== 3 4 Before Creating a New Process 5 ----------------------------- 6 7 Firefox started out as a one process application. Then, one became two as 8 NPAPI plugins like Flash were pushed into their own process (plugin processes) 9 for security and stability reasons. Then, it split again so that the browser 10 could also disentangle itself from web content (content processes). Then, 11 implementations on some platforms developed processes for graphics ("GPU" 12 processes). And for media codecs. And VR. And file URLs. And sockets. And 13 even more content processes. And so on... 14 15 Here is an incomplete list of *good* reasons we've created new processes: 16 17 * Separating HTML and JS from the browser makes it possible to secure the 18 browser and the rest of the system from them, even when those APIs are 19 compromised. 20 * Browser stability was also improved by separating HTML and JS from the 21 browser, since catastrophic failures related to a tab could be limited to the 22 tab instead of crashing the browser. 23 * Site isolation requires additional processes to separate HTML and JS for 24 different sites. The separation of memory spaces undermines many types of 25 exploits. 26 * Sandboxing processes offers great security guarantees but requires making 27 tradeoffs between power and protection. More processes means more options. 28 For example, we heavily sandbox content processes to protect from external 29 code, while the File process, which is a content process that can access 30 ``file://`` URLs, has a sandbox that is similar but allows access to local 31 files. 32 * One of the benefits of the GPU process was that it improved browser 33 stability by separating a system component that had frequent stability 34 issues -- GPU drivers. The same logic inspired the NPAPI (Flash) plugin 35 process. 36 37 Informed by this history, there is some of non-obvious preparation that you 38 should do before starting down this path. This falls under the category of 39 "First, do no harm": 40 41 * **Consult the Platform and IPC teams** (#ipc) to develop the plan for the 42 way your process will integrate with the systems in which it will exist, as 43 well as how it will be handled on any platforms where it will *not* exist. 44 For example, an application's process hierarchy forms a tree where one process 45 spawns another. Currently, all processes in Firefox are spawned by the main 46 process (excepting the `launcher process`_). There is good reason for this, 47 mostly based on our sandboxing restrictions that forbid non-main processes 48 from launching new processes themselves. But it means that the main process 49 will need to know to create your process. If you make the decision to do 50 this from, say, a content process, you will need a safe, performant and 51 stable way to request this of the main process. You will also need a way to 52 efficiently communicate directly with your new process. And you will need to 53 consider limitations of some platforms (think Android) where you may not want 54 to or not be able to spawn the new process. 55 * **Consult the sandboxing team** (#hardening) to discuss what the sandbox for 56 your new process will look like. Anything that compromises security is a 57 non-starter. You may, for instance, want to create a new process to escape 58 the confines of the sandbox in a content process. This can be legitimate, 59 for example you may need access to some device API that is unavailable to a 60 content process, but the security for your new process will then have to come 61 from a different source. "I won't run Javascript" is not sufficient. Keep 62 in mind that your process will have to have some mechanism for communication 63 with other processes to be useful, so it is always a potential target. 64 65 .. note:: 66 Firefox has, to date, undergone exactly one occurrence of the *removal* of 67 a process type. In 2020, the NPAPI plugin process was removed when the 68 last supported plugin, Adobe's FlashPlayer, reached its end-of-life. 69 70 .. _launcher process: https://wiki.mozilla.org/Platform/Integration/InjectEject/Launcher_Process/ 71 72 Firefox Process Hierarchy 73 ------------------------- 74 75 This diagram shows the primary process types in Firefox. 76 77 .. mermaid:: 78 79 graph TD 80 RDD -->|PRemoteMediaManager| Content 81 RDD(Data Decoder) ==>|PRDD| Main 82 83 Launcher --> Main 84 85 Main ==>|PContent| Content 86 Main ==>|PSocketProcess| Socket(Network Socket) 87 Main ==>|PGMP| GMP(Gecko Media Plugins) 88 VR ==>|PVR| Main 89 GPU ==>|PGPU| Main 90 91 Socket -->|PSocketProcessBridge| Content 92 93 GPU -->|PCompositorManager| Main 94 GPU -->|PCompositorManager| Content 95 96 Content -->|PGMPContent| GMP 97 98 VR -->|PVRGPU| GPU 99 100 .. warning:: 101 The main process is sometimes called the UI process, the chrome process, 102 the browser process or the parent process. This is true for documentation, 103 conversation and, most significantly, **code**. Due to the syntactic 104 overlap with IPDL actors, that last name can get pretty confusing. Less 105 commonly, the content process is called the renderer process, which is it's 106 name in Chromium code. Since the content process sandbox won't allow it, 107 Firefox never does (hardware) rendering in the content/rendering process! 108 109 The arrows point from the parent side to the child. Bolded arrows indicate the 110 first top-level actors for the various process types. The other arrows show 111 important actors that are usually the first connections established between the 112 two processes. These relationships difficult to discern from code. Processes 113 should clearly document their top-level connections in their IPDL files. 114 115 Some process types only exist on some platforms and some processes may only be 116 created on demand. For example, Mac builds do not use a GPU process but 117 instead fold the same actor connections into its main process (except ``PGPU``, 118 which it does not use). These exceptions are also very hard to learn from code 119 and should be clearly documented. 120 121 ``about:processes`` shows statistics for the processes in a currently running 122 browser. It is also useful to see the distribution of web pages across content 123 processes. 124 125 .. _Adding a New Type of Process: 126 127 Adding a New Type of Process 128 ---------------------------- 129 130 Adding a new process type doesn't require any especially difficult steps but it 131 does require a lot of steps that are not obvious. This section will focus on 132 the steps as it builds an example. It will be light on the details of the 133 classes and protocols involved. Some implementations may need to seek out a 134 deeper understanding of the components set up here but most should instead 135 strive for simplicity. 136 137 In the spirit of creating a *responsible* process, the sample will connect 138 several components that any deployed Gecko process is likely to need. These 139 include configuring a sandbox, :ref:`registration with the CrashReporter service <Crash Reporter>` 140 and ("minimal") XPCOM initialization. Consult documentation for these 141 components for more information on their integration. 142 143 This example will be loosely based on the old (now defunct) IPDL **Extending a 144 Protocol** example for adding a new actor. We will add a command to the 145 browser's ``navigator`` JS object, ``navigator.getAssistance()``. When the 146 user enters the new command in, say, the browser's console window, it will 147 create a new process of our new **Demo** process type and ask that process for 148 "assistance" in the form of a string that it will then print to the console. 149 Once that is done, the new process will be cleanly destroyed. 150 151 Code for the complete demo can be found `here 152 <https://phabricator.services.mozilla.com/D119038>`_. 153 154 Common Architecture 155 ~~~~~~~~~~~~~~~~~~~ 156 157 Every type of process (besides the launcher and main processes) needs two 158 classes and an actor pair to launch. This sample will be adding a process type 159 we call **Demo**. 160 161 * An actor pair where the parent actor is a top-level actor in the main process 162 and the child is the (first) top-level actor in the new process. It is common 163 for this actor to simply take the name of the process type. The sample uses 164 ``PDemo``, so it creates ``DemoParent`` and ``DemoChild`` actor subclasses 165 as usual (see :ref:`IPDL: Inter-Thread and Inter-Process Message Passing`). 166 * A subclass of `GeckoChildProcessHost 167 <https://searchfox.org/mozilla-central/source/ipc/glue/GeckoChildProcessHost.h>`_ 168 that exists in the main process (where new processes are created) and handles 169 most of the machinery needed for new process creation. It is common for these 170 names to be the process type plus ``ProcessParent`` or ``ProcessHost``. The 171 sample uses ``DemoParent::Host``, a private class, which keeps 172 ``GeckoChildProcessHost`` out of the **Demo** process' *public interface* 173 since it is large, complicated and mostly unimportant externally. This 174 complexity is also why it is a bad idea to add extra responsibilities to the 175 ``Host`` object that inherits it. 176 * A subclass of `ProcessChild 177 <https://searchfox.org/mozilla-central/source/ipc/glue/ProcessChild.h>`_ that 178 exists in the new process. These names are usually generated by affixing 179 ``ProcessChild`` or ``ProcessImpl`` to the type. The sample will use 180 ``DemoChild::Process``, another private class, for the same reasons it did 181 with the ``Host``. 182 183 A fifth class is optional but integration with common services requires 184 something like it: 185 186 * A singleton class that "manages" the collective of processes (usually the 187 Host objects) of the new type in the main process. In many instances, there 188 is at most one instance of a process type, so this becomes a singleton that 189 manages a singleton... that manages a singleton. Object ownership is often 190 hard to establish between manager objects and the hosts they manage. It is 191 wise to limit the power of these classes. This class will often get its name 192 by appending ``ProcessManager`` to the process type. The sample provides a 193 very simple manager in ``DemoParent::Manager``. 194 195 Finally, it is highly probable and usually desirable for the new process to 196 include another new top-level actor that represents the top-level operations 197 and communications of the new process. This actor will use the new process as 198 a child but may have any other process as the parent, unlike ``PDemo`` whose 199 parent is always the main process. This new actor will be created by the main 200 process, which creates a pair of ``Endpoint`` objects specifically for the 201 desired process pairing, and then sends those ``Endpoint`` objects to their 202 respective processes. The **Demo** example is interesting because the user can 203 issue the command from a content process or the main one, by opening the 204 console in a normal or a privileged page (e.g. ``about:sessionrestore``), 205 respectively. Supporting both of these cases will involve very little 206 additional effort. The sample will show this as part of implementing the 207 second top-level actor pair ``PDemoHelpline`` in `Connecting With Other 208 Processes`_, where the parent can be in either the main or a content process. 209 210 The rest of the sections will explain how to compose these classes and 211 integrate them with Gecko. 212 213 Process Bookkeeping 214 ~~~~~~~~~~~~~~~~~~~ 215 .. _process-bookkeeping: 216 217 To begin with, look at the `geckoprocesstypes generator 218 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/xpcom/geckoprocesstypes_generator/geckoprocesstypes/__init__.py>`_ 219 which adds the bones for a new process (by defining enum values and so on). 220 Some further manual intervention is still required, and you need to follow the 221 following checklists depending on your needs. 222 223 Basic requirements 224 ^^^^^^^^^^^^^^^^^^ 225 226 * Add a new entry to the `enum WebIDLProcType 227 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/dom/chrome-webidl/ChromeUtils.webidl#610-638>`_ 228 * Update the `static_assert 229 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/toolkit/xre/nsAppRunner.cpp#988-990>`_ 230 call checking for boundary against ``GeckoProcessType_End`` 231 * Add your process to the correct ``MessageLoop::TYPE_x`` in the first 232 ``switch(XRE_GetProcessType())`` in `XRE_InitChildProcess 233 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/toolkit/xre/nsEmbedFunctions.cpp#572-590>`_. 234 You can get more information about that topic in `this comment 235 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/ipc/chromium/src/base/message_loop.h#159-187>`_ 236 * Instantiate your child within the second ``switch (XRE_GetProcessType())`` in 237 `XRE_InitChildProcess 238 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/toolkit/xre/nsEmbedFunctions.cpp#615-671>`_ 239 * Add a new entry ``PROCESS_TYPE_x`` in `nsIXULRuntime interface 240 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/xpcom/system/nsIXULRuntime.idl#183-196>`_ 241 242 Graphics 243 ######## 244 245 If you need graphics-related interaction, hack into `gfxPlatform 246 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/gfx/thebes/gfxPlatform.cpp>`_ 247 248 - Add a call to your process manager init in ``gfxPlatform::Init()`` in 249 `gfxPlatform 250 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/gfx/thebes/gfxPlatform.cpp#808-810>`_ 251 - Add a call to your process manager shutdown in ``gfxPlatform::Shutdown()`` in 252 `gfxPlatform 253 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/gfx/thebes/gfxPlatform.cpp#1255-1259>`_ 254 255 Android 256 ####### 257 258 You might want to talk with `#geckoview` maintainers to ensure if this is 259 required or applicable to your new process type. 260 261 - Add a new ``<service>`` entry against 262 ``org.mozilla.gecko.process.GeckoChildProcessServices$XXX`` in the 263 `AndroidManifest 264 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/mobile/android/geckoview/src/main/AndroidManifest.xml#45-81>`_ 265 - Add matching class inheritance from `GeckoChildProcessServices 266 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoChildProcessServices.jinja#10-13>`_ 267 - Add new entry in `public enum GeckoProcessType 268 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessType.java#11-23>`_ 269 270 Crash reporting 271 ############### 272 273 - Add ``InitCrashReporter`` message to the parent-side `InitCrashReporter 274 <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/PUtilityProcess.ipdl#30>`_ 275 - Ensure your parent class inherits `public ipc::CrashReporterHelper<GeckoProcessType_Xxx> 276 <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessParent.h#23>`_ 277 - Add new ``Xxx*Status`` `annotations 278 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/toolkit/crashreporter/CrashAnnotations.yaml#968-971>`_ 279 entry for your new process type description. The link here points to 280 `UtilityProcessStatus` so you can see the similar description you have to 281 write, but you might want to respect ordering in that file and put your new 282 code at the appropriate place. 283 - Add entry in `PROCESS_CRASH_SUBMIT_ATTEMPT 284 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/toolkit/components/telemetry/Histograms.json#13403-13422>`_ and 285 `submit_attempt` in `toolkit/components/crashes/metrics.yaml`. 286 287 Memory reporting 288 ################ 289 290 Throughout the linked code, please consider those methods more as boilerplate code that will require some trivial modification to fit your exact usecase. 291 292 - Add definition of memory reporter to your new :ref:`top-level actor <Top Level Actors>` 293 294 + Type inclusion `MemoryReportTypes <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/PUtilityProcess.ipdl#6>`_ 295 + To parent-side `AddMemoryReport <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/PUtilityProcess.ipdl#32>`_ 296 + To child-side `RequestMemoryReport <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/PUtilityProcess.ipdl#44-48>`_ 297 298 - Add handling for your new process within `nsMemoryReporterManager::GetReportsExtended <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/xpcom/base/nsMemoryReporterManager.cpp#1813-1819>`_ 299 - Provide a process manager level abstraction 300 301 + Implement a new class deriving ``MemoryReportingProcess`` such as `UtilityMemoryReporter <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessManager.cpp#253-292>`_ 302 + Write a `GetProcessMemoryReport <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessManager.cpp#294-300>`_ 303 304 - On the child side, provide an implementation for `RequestMemoryReport <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessChild.cpp#153-166>`_ 305 - On the parent side 306 307 + Provide an implementation for `RequestMemoryReport <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessParent.cpp#41-69>`_ 308 + Provide an implementation for `AddMemoryReport <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessParent.cpp#71-77>`_ 309 310 If you want to add a test that ensures proper behavior, you can have a look at the `utility process memory report test <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/test/browser/browser_utility_memoryReport.js>`_ 311 312 Process reporting 313 ################# 314 315 Those elements will be used for exposing processes to users in some `about:` 316 pages. You might want to ping `#fluent-reviewers` to ensure if you need your 317 process there. 318 319 - Add a `user-facing localizable name 320 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/toolkit/locales/en-US/toolkit/global/processTypes.ftl#39-57>`_ 321 for your process, if needed 322 - Hashmap from process type to user-facing string above in `const ProcessType 323 <https://searchfox.org/mozilla-central/rev/c5c002f81f08a73e04868e0c2bf0eb113f200b03/toolkit/modules/ProcessType.sys.mjs#10-16>`_ 324 - For `about:processes` you will probably want to follow the following steps: 325 326 + Add handling for your new process type producing a unique `fluentName <https://searchfox.org/mozilla-central/rev/be4604e4be8c71b3c1dbff2398a5b05f15411673/toolkit/components/aboutprocesses/content/aboutProcesses.js#472-539>`_, i.e., constructing a dynamic name is highly discouraged 327 + Add matching localization strings within `about:processes localization file <https://searchfox.org/mozilla-central/rev/be4604e4be8c71b3c1dbff2398a5b05f15411673/toolkit/locales/en-US/toolkit/about/aboutProcesses.ftl#35-55>`_ 328 + Add matching localization strings within `about:support localization file <https://searchfox.org/mozilla-central/source/toolkit/locales/en-US/toolkit/global/processTypes.ftl#54-60>`_ 329 330 Profiler 331 ######## 332 333 - Add definition of ``PProfiler`` to your new IPDL 334 335 + Type inclusion `protocol PProfiler <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/PUtilityProcess.ipdl#9>`_ 336 + Child-side `InitProfiler <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/PUtilityProcess.ipdl#42>`_ 337 338 - Make sure your initialization path contains a `SendInitProfiler <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessHost.cpp#222-223>`_. You will want to perform the call once a ``OnChannelConnected`` is issued, thus ensuring your new process is connected to IPC. 339 - Provide an implementation for `InitProfiler <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessChild.cpp#147-151>`_ 340 341 - You will probably want to make sure your child process code register within the profiler a proper name, otherwise it will default to ``GeckoMain`` ; this can be done by issuing ``profiler_set_process_name(nsCString("XxX"))`` on the child init side. 342 343 Static Components 344 ################# 345 346 The amount of changes required here are significant, `Bug 1740485: Improve 347 StaticComponents code generation 348 <https://bugzilla.mozilla.org/show_bug.cgi?id=1740485>`_ tracks improving that. 349 350 - Update allowance in those configuration files to match new process selector 351 that includes your new process. When exploring those components definitions, 352 keep in mind that you are looking at updating `processes` field in the 353 `Classes` object. The `ProcessSelector` value will come from what the reader 354 writes based on the instructions below. Some of these also contains several 355 services, so you might have to ensure you have all your bases covered. Some of 356 the components might not need to be updated as well. 357 358 + `libpref <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/modules/libpref/components.conf>`_ 359 + `telemetry <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/toolkit/components/telemetry/core/components.conf>`_ 360 + `android <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/widget/android/components.conf>`_ 361 + `gtk <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/widget/gtk/components.conf>`_ 362 + `windows <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/widget/windows/components.conf>`_ 363 + `base <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/xpcom/base/components.conf>`_ 364 + `components <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/xpcom/components/components.conf>`_ 365 + `ds <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/xpcom/ds/components.conf>`_ 366 + `threads <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/xpcom/threads/components.conf>`_ 367 + `cocoa kWidgetModule <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/widget/cocoa/nsWidgetFactory.mm#194-202>`_ 368 + `build <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/xpcom/build/components.conf>`_ 369 + `XPCOMinit kXPCOMModule <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/xpcom/build/XPCOMInit.cpp#172-180>`_ 370 371 - Within `static components generator 372 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/xpcom/components/gen_static_components.py>`_ 373 374 + Add new definition in ``ProcessSelector`` for your new process 375 ``ALLOW_IN_x_PROCESS = 0x..`` 376 + Add new process selector masks including your new process definition 377 + Also add those into the ``PROCESSES`` structure 378 379 - Within `module definition <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/xpcom/components/Module.h>`_ 380 381 + Add new definition in ``enum ProcessSelector`` 382 + Add new process selector mask including the new definition 383 + Update ``kMaxProcessSelector`` 384 385 - Within `nsComponentManager <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/xpcom/components/nsComponentManager.cpp>`_ 386 387 + Add new selector match in ``ProcessSelectorMatches`` for your new process 388 (needed?) 389 + Add new process selector for ``gProcessMatchTable`` in 390 ``nsComponentManagerImpl::Init()`` 391 392 Glean telemetry 393 ############### 394 395 - Ensure your new IPDL includes on the child side 396 397 + `FlushFOGData 398 <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/PUtilityProcess.ipdl#55>`_ 399 + `TestTriggerMetrics 400 <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/PUtilityProcess.ipdl#60>`_ 401 402 - Provide a parent-side implementation for `FOGData 403 <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessParent.cpp#79-82>`_ 404 - Provide a child-side implementation for `FlushFOGData 405 <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessChild.cpp#179-183>`_ 406 - Child-side should flush its FOG data at IPC `ActorDestroy 407 <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessChild.cpp#199-201>`_ 408 - Child-side `test metrics 409 <https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessChild.cpp#185-191>`_ 410 - Within `FOGIPC 411 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/toolkit/components/glean/ipc/FOGIPC.cpp>`_ 412 413 + Add handling of your new process type within ``FlushAllChildData()`` `here 414 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/toolkit/components/glean/ipc/FOGIPC.cpp#106-121>`_ 415 and ``SendFOGData()`` `here 416 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/toolkit/components/glean/ipc/FOGIPC.cpp#165-182>`_ 417 + Add support for sending test metrics in ``TestTriggerMetrics()`` `here 418 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/toolkit/components/glean/ipc/FOGIPC.cpp#208-232>`_ 419 420 - Handle process shutdown in ``register_process_shutdown()`` of `glean 421 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/toolkit/components/glean/api/src/ipc.rs>`_ 422 423 Third-Party Modules 424 ################### 425 426 - Ensure your new IPDL includes on the child side 427 428 + `GetUntrustedModulesData 429 <https://searchfox.org/mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a/ipc/glue/PUtilityProcess.ipdl#106>`_ 430 + `UnblockUntrustedModulesThread 431 <https://searchfox.org/mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a/ipc/glue/PUtilityProcess.ipdl#113>`_ 432 433 - Provide a parent side implementation for both 434 435 - Add handling of your new process type in ``MultiGetUntrustedModulesData::GetUntrustedModuleLoadEvents()`` `here <https://searchfox.org/mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a/toolkit/components/telemetry/other/UntrustedModules.cpp#145-151>`_ 436 437 - `Update your IPDL <https://searchfox.org/mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a/ipc/glue/PUtilityProcess.ipdl#75>`_ and make sure your ``Init()`` can receive a boolean for 438 ``isReadyForBackgroundProcessing`` `like here <https://searchfox.org/mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a/ipc/glue/UtilityProcessChild.cpp#157-160>`_, then within the child's ``RecvInit()`` 439 make sure a call to ``DllServices``'s ``StartUntrustedModulesProcessor()`` `is 440 performed <https://searchfox.org/mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a/ipc/glue/UtilityProcessChild.cpp#185-186>`_. 441 442 - Ensure your new IPDL includes for the parent side 443 444 + `GetModulesTrust <https://searchfox.org/mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a/ipc/glue/PUtilityProcess.ipdl#60-61>`_ 445 446 - Provide an implementation on the `parent side <https://searchfox.org/mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a/ipc/glue/UtilityProcessParent.cpp#69-81>`_ 447 448 - Expose your new process type as supported in ``UntrustedModulesProcessor::IsSupportedProcessType()`` `like others <https://searchfox.org/mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a/toolkit/xre/dllservices/UntrustedModulesProcessor.cpp#76-91>`_ 449 450 - Update ``UntrustedModulesProcessor::SendGetModulesTrust()`` to call `your new child process <https://searchfox.org/mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a/toolkit/xre/dllservices/UntrustedModulesProcessor.cpp#757-761>`_ 451 452 Sandboxing 453 ########## 454 455 Sandboxing changes related to a new process can be non-trivial, so it is 456 strongly advised that you reach to the Sandboxing team in ``#hardening`` to 457 discuss your needs prior to making changes. 458 459 Linux Sandbox 460 _____________ 461 462 Linux sandboxing mostly works by allowing / blocking system calls for child 463 process and redirecting (brokering) some from the child to the parent. Rules 464 are written in a specific DSL: `BPF 465 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl.h#21-72>`_. 466 467 - Add new ``SetXXXSandbox()`` function within `linux sandbox 468 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/linux/Sandbox.cpp#719-748>`_ 469 - Within `sandbox filter 470 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/linux/SandboxFilter.cpp>`_ 471 472 + Add new helper ``GetXXXSandboxPolicy()`` `like this one 473 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/linux/SandboxFilter.cpp#2036-2040>`_ 474 called by ``SetXXXSandbox()`` 475 + Derive new class `similar to this 476 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/linux/SandboxFilter.cpp#2000-2034>`_ 477 inheriting ``SandboxPolicyCommon`` or ``SandboxPolicyBase`` and defining 478 the sandboxing policy 479 480 - Add new ``SandboxBrokerPolicyFactory::GetXXXProcessPolicy()`` in `sandbox 481 broker 482 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp#881-932>`_ 483 - Add new case handling in ``GetEffectiveSandboxLevel()`` in `sandbox launch 484 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/linux/launch/SandboxLaunch.cpp#243-271>`_ 485 - Add new entry in ``enum class ProcType`` of `sandbox reporter header 486 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/linux/reporter/SandboxReporterCommon.h#32-39>`_ 487 - Add new case handling in ``SubmitToTelemetry()`` in `sandbox reporter 488 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/linux/reporter/SandboxReporter.cpp#131-152>`_ 489 - Add new case handling in ``SandboxReportWrapper::GetProcType()`` of `sandbox 490 reporter wrapper 491 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/linux/reporter/SandboxReporterWrappers.cpp#69-91>`_ 492 493 MacOS Sandbox 494 _____________ 495 496 - Add new case handling in ``GeckoChildProcessHost::StartMacSandbox()`` of 497 `GeckoChildProcessHost <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/ipc/glue/GeckoChildProcessHost.cpp#1720-1743>`_ 498 - Add new entry in ``enum MacSandboxType`` defined in `macOS sandbox header 499 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/mac/Sandbox.h#12-20>`_ 500 - Within `macOS sandbox core 501 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/mac/Sandbox.mm>`_ 502 handle the new ``MacSandboxType`` in 503 504 + ``MacSandboxInfo::AppendAsParams()`` in the `switch statement 505 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/mac/Sandbox.mm#164-188>`_ 506 + ``StartMacSandbox()`` in the `series of if/else statements 507 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/mac/Sandbox.mm#286-436>`_. 508 This code sets template values for the sandbox string rendering, and is 509 running on the side of the main process. 510 + ``StartMacSandboxIfEnabled()`` in this `switch statement 511 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/mac/Sandbox.mm#753-782>`_. 512 You might also need a ``GetXXXSandboxParamsFromArgs()`` that performs CLI 513 parsing on behalf of ``StartMacSandbox()``. 514 515 - Create the new sandbox definition file 516 ``security/sandbox/mac/SandboxPolicy<XXX>.h`` for your new process ``<XXX>``, 517 and make it exposed in the ``EXPORTS.mozilla`` section of `moz.build 518 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/mac/moz.build#7-13>`_. 519 Those rules follows a specific Scheme-like language. You can learn more about 520 it in `Apple Sandbox Guide 521 <https://reverse.put.as/wp-content/uploads/2011/09/Apple-Sandbox-Guide-v1.0.pdf>`_ 522 as well as on your system within ``/System/Library/Sandbox/Profiles/``. 523 524 Windows Sandbox 525 _______________ 526 527 - Introduce a new ``SandboxBroker::SetSecurityLevelForXXXProcess()`` that 528 defines the new sandbox in the sandbox broker basing yourself on this 529 `example <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp#1241-1344>`_ 530 531 - Add new case handling in ``WindowsProcessLauncher::DoSetup()`` calling 532 ``SandboxBroker::SetSecurityLevelForXXXProcess()`` in `GeckoChildProcessHost 533 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/ipc/glue/GeckoChildProcessHost.cpp#1391-1470>`_. 534 This will apply actual sandboxing rules to your process. 535 536 Sandbox tests 537 _____________ 538 539 - New process' first top level actor needs to `include PSandboxTesting 540 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/common/test/PSandboxTesting.ipdl>`_ 541 and implement ``RecvInitSandboxTesting`` `like there 542 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/ipc/glue/UtilityProcessChild.cpp#165-174>`_. 543 - Add your new process ``string_name`` in the ``processTypes`` list of `sandbox 544 tests <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/test/browser_sandbox_test.js#17>`_ 545 - Add a new case in ``SandboxTest::StartTests()`` in `test core 546 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/common/test/SandboxTest.cpp#100-232>`_ 547 to handle your new process 548 - Add a new if branch for your new process in ``SandboxTestingChild::Bind()`` 549 in `testing child 550 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/common/test/SandboxTestingChild.cpp#68-96>`_ 551 - Add a new ``RunTestsXXX`` function for your new process (called by ``Bind()`` 552 above) `similar to that implementation 553 <https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/security/sandbox/common/test/SandboxTestingChildTests.h#333-363>`_ 554 555 Creating the New Process 556 ~~~~~~~~~~~~~~~~~~~~~~~~ 557 558 The sample does this in ``DemoParent::LaunchDemoProcess``. The core 559 behavior is fairly clear: 560 561 .. code-block:: cpp 562 563 /* static */ 564 bool DemoParent::LaunchDemoProcess( 565 base::ProcessId aParentPid, LaunchDemoProcessResolver&& aResolver) { 566 UniqueHost host(new Host(aParentPid, std::move(aResolver))); 567 568 // Prepare "command line" startup args for new process 569 std::vector<std::string> extraArgs; 570 if (!host->BuildProcessArgs(&extraArgs)) { 571 return false; 572 } 573 574 // Async launch creates a promise that we use below. 575 if (!host->AsyncLaunch(extraArgs)) { 576 return false; 577 } 578 579 host->WhenProcessHandleReady()->Then( 580 GetCurrentSerialEventTarget(), __func__, 581 [host = std::move(host)]( 582 const ipc::ProcessHandlePromise::ResolveOrRejectValue& 583 aResult) mutable { 584 if (aResult.IsReject()) { 585 host->ResolveAsFailure(); 586 return; 587 } 588 589 auto actor = MakeRefPtr<DemoParent>(std::move(host)); 590 actor->Init(); 591 }); 592 } 593 594 First, it creates an object of our ``GeckoChildProcessHost`` subclass (storing 595 some stuff for later). ``GeckoChildProcessHost`` is a base class that 596 abstracts the system-level operations involved in launching the new process. 597 It is the most substantive part of the launch procedure. After its 598 construction, the code prepares a bunch of strings to pass on the "command 599 line", which is the only way to pass data to the new process before IPDL is 600 established. All new processes will at least include ``-parentBuildId`` for 601 validating that dynamic libraries are properly versioned, and shared memory for 602 passing user preferences, which can affect early process behavior. Finally, it 603 tells ``GeckoChildProcessHost`` to asynchronously launch the process and run 604 the given lambda when it has a result. The lambda creates ``DemoParent`` with 605 the new host, if successful. 606 607 In this sample, the ``DemoParent`` is owned (in the reference-counting sense) 608 by IPDL, which is why it doesn't get assigned to anything. This simplifies the 609 design dramatically. IPDL takes ownership when the actor calls ``Bind`` from 610 the ``Init`` method: 611 612 .. code-block:: cpp 613 614 DemoParent::DemoParent(UniqueHost&& aHost) 615 : mHost(std::move(aHost)) {} 616 617 DemoParent::Init() { 618 mHost->TakeInitialEndpoint().Bind(this); 619 // ... 620 mHost->MakeBridgeAndResolve(); 621 } 622 623 After the ``Bind`` call, the actor is live and communication with the new 624 process can begin. The constructor concludes by initiating the process of 625 connecting the ``PDemoHelpline`` actors; ``Host::MakeBridgeAndResolve`` will be 626 covered in `Creating a New Top Level Actor`_. However, before we get into 627 that, we should finish defining the lifecycle of the process. In the next 628 section we look at launching the new process from the new process' perspective. 629 630 .. warning:: 631 The code could have chosen to create a ``DemoChild`` instead of a 632 ``DemoParent`` and the choice may seem cosmetic but it has substantial 633 implications that could affect browser stability. The most 634 significant is that the prohibitibition on synchronous IPDL messages going 635 from parent to child can no longer guarantee freedom from multiprocess 636 deadlock. 637 638 Initializing the New Process 639 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 640 641 The new process first adopts the **Demo** process type in 642 ``XRE_InitChildProcess``, where it responds to the **Demo** values we added to 643 some enums above. Specifically, we need to choose the type of MessageLoop our 644 main thread will run (this is discussed later) and we need to create our 645 ``ProcessChild`` subclass. This is not an insignificant choice so pay close 646 attention to the `MessageLoop` options: 647 648 .. code-block:: cpp 649 650 MessageLoop::Type uiLoopType; 651 switch (XRE_GetProcessType()) { 652 case GeckoProcessType_Demo: 653 uiLoopType = MessageLoop::TYPE_MOZILLA_CHILD; break; 654 // ... 655 } 656 657 // ... 658 659 UniquePtr<ProcessChild> process; 660 switch (XRE_GetProcessType()) { 661 // ... 662 case GeckoProcessType_Demo: 663 process = MakeUnique<DemoChild::Process>(parentPID); 664 break; 665 } 666 667 We then need to create our singleton ``DemoChild`` object, which can occur in 668 the constructor or the ``Process::Init()`` call, which is common. We store a 669 strong reference to the actor (as does IPDL) so that we are guaranteed that it 670 exists as long as the ``ProcessChild`` does -- although the message channel may 671 be closed. We will release the reference either when the process is properly 672 shutting down or when an IPC error closes the channel. 673 674 ``Init`` is given the command line arguments constructed above so it will need 675 to be overridden to parse them. It does this, binds our actor by 676 calling ``Bind`` as was done with the parent, then initializes a bunch of 677 components that the process expects to use: 678 679 .. code-block:: cpp 680 681 bool DemoChild::Init(int aArgc, char* aArgv[]) { 682 #if defined(MOZ_SANDBOX) && defined(XP_WIN) 683 mozilla::SandboxTarget::Instance()->StartSandbox(); 684 #elif defined(__OpenBSD__) && defined(MOZ_SANDBOX) 685 StartOpenBSDSandbox(GeckoProcessType_Demo); 686 #endif 687 688 if (!mozilla::ipc::ProcessChild::InitPrefs(aArgc, aArgv)) { 689 return false; 690 } 691 692 if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) { 693 return false; 694 } 695 696 if (NS_WARN_IF(!TakeInitialEndpoint().Bind(this))) { 697 return false; 698 } 699 700 // ... initializing components ... 701 702 if (NS_FAILED(NS_InitMinimalXPCOM())) { 703 return false; 704 } 705 706 return true; 707 } 708 709 This is a slimmed down version of the real ``Init`` method. We see that it 710 establishes a sandbox (more on this later) and then reads the command line and 711 preferences that we sent from the main process. It then initializes the thread 712 manager, which is required by for the subsequent ``Bind`` call. 713 714 Among the list of components we initialize in the sample code, XPCOM is 715 special. XPCOM includes a suite of components, including the component 716 manager, and is usually required for serious Gecko development. It is also 717 heavyweight and should be avoided if possible. We will leave the details of 718 XPCOM development to that module but we mention XPCOM configuration that is 719 special to new processes, namely ``ProcessSelector``. ``ProcessSelector`` 720 is used to determine what process types have access to what XPCOM components. 721 By default, a process has access to none. The code adds enums for selecting 722 a subset of process types, like 723 ``ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_DEMO_PROCESS``, to the 724 ``ProcessSelector`` enum in `gen_static_components.py 725 <https://searchfox.org/mozilla-central/source/xpcom/components/gen_static_components.py>`_ 726 and `Module.h 727 <https://searchfox.org/mozilla-central/source/xpcom/components/Module.h>`_. 728 It then updates the selectors in various ``components.conf`` files and 729 hardcoded spots like ``nsComponentManager.cpp`` to add the **Demo** processes 730 to the list that can use them. Some modules are required to bootstrap XPCOM 731 and will cause it to fail to initialize if they are not permitted. 732 733 At this point, the new process is idle, waiting for messages from the main 734 process that will start the ``PDemoHelpline`` actor. We discuss that in 735 `Creating a New Top Level Actor`_ below but, first, let's look at how the main 736 and **Demo** processes will handle clean destruction. 737 738 Destroying the New Process 739 ~~~~~~~~~~~~~~~~~~~~~~~~~~ 740 741 Gecko processes have a clean way for clients to request that they shutdown. 742 Simply calling ``Close()`` on the top level actor at either endpoint will begin 743 the shutdown procedure (so, ``PDemoParent::Close`` or ``PDemoChild::Close``). 744 The only other way for a child process to terminate is to crash. Each of these 745 three options requires some special handling. 746 747 .. note:: 748 There is no need to consider the case where the parent (main) process 749 crashed, because the **Demo** process would be quickly terminated by Gecko. 750 751 In cases where ``Close()`` is called, the shutdown procedure is fairly 752 straightforward. Once the call completes, the actor is no longer connected to 753 a channel -- messages will not be sent or received, as is the case with any 754 normal top-level actor (or any managed actor after calling 755 ``Send__delete__()``). In the sample code, we ``Close`` the ``DemoChild`` 756 when some (as yet unwritten) **Demo** process code calls 757 ``DemoChild::Shutdown``. 758 759 .. code-block:: cpp 760 761 /* static */ 762 void DemoChild::Shutdown() { 763 if (gDemoChild) { 764 // Wait for the other end to get everything we sent before shutting down. 765 // We never want to Close during a message (response) handler, so 766 // we dispatch a new runnable. 767 auto dc = gDemoChild; 768 RefPtr<nsIRunnable> runnable = NS_NewRunnableFunction( 769 "DemoChild::FinishShutdown", 770 [dc2 = std::move(gDemoChild)]() { dc2->Close(); }); 771 dc->SendEmptyMessageQueue( 772 [runnable](bool) { NS_DispatchToMainThread(runnable); }, 773 [runnable](mozilla::ipc::ResponseRejectReason) { 774 NS_DispatchToMainThread(runnable); 775 }); 776 } 777 } 778 779 The comment in the code makes two important points: 780 781 * ``Close`` should never be called from a message handler (e.g. in a 782 ``RecvFoo`` method). We schedule it to run later. 783 * If the ``DemoParent`` hasn't finished handling messages the ``DemoChild`` 784 sent, or vice-versa, those messages will be lost. For that reason, we have a 785 trivial sentinel message ``EmptyMessageQueue`` that we simply send and wait 786 to respond before we ``Close``. This guarantees that the main process will 787 have handled all of the messages we sent before it. Because we know the 788 details of the ``PDemo`` protocol, we know that this means we won't lose any 789 important messages this way. Note that we say "important" messages because 790 we could still lose messages sent *from* the main process. For example, a 791 ``RequestMemoryReport`` message sent by the MemoryReporter could be lost. 792 The actor would need a more complex shutdown protocol to catch all of these 793 messages but in our case there would be no point. A process that is 794 terminating is probably not going to produce useful memory consumption data. 795 Those messages can safely be lost. 796 797 `Debugging Process Startup`_ looks at what happens if we omit the 798 ``EmptyMessageQueue`` message. 799 800 We can also see that, once the ``EmptyMessageQueue`` response is run, we are 801 releasing ``gDemoChild``, which will result in the termination of the process. 802 803 .. code-block:: cpp 804 805 DemoChild::~DemoChild() { 806 // ... 807 XRE_ShutdownChildProcess(); 808 } 809 810 At this point, the ``DemoParent`` in the main process is alerted to the 811 channel closure because IPDL will call its :ref:`ActorDestroy <Actor Lifetimes 812 in C++>` method. 813 814 .. code-block:: cpp 815 816 void DemoParent::ActorDestroy(ActorDestroyReason aWhy) { 817 if (aWhy == AbnormalShutdown) { 818 GenerateCrashReport(OtherPid()); 819 } 820 // ... 821 } 822 823 IPDL then releases its (sole) reference to ``DemoParent`` and the destruction 824 of the process apparatus is complete. 825 826 The ``ActorDestroy`` code shows how we handle the one remaining shutdown case: 827 a crash in the **Demo** process. In this case, IPDL will *detect* the dead 828 process and free the ``DemoParent`` actor as above, only with an 829 ``AbnormalShutdown`` reason. We generate a crash report, which requires crash 830 reporter integration, but no additional "special" steps need to be taken. 831 832 Creating a New Top Level Actor 833 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 834 835 We now have a framework that creates the new process and connects it to the 836 main process. We now want to make another top-level actor but this one will be 837 responsible for our intended behavior, not just bootstrapping the new process. 838 Above, we saw that this is started by ``Host::MakeBridgeAndResolve`` after the 839 ``DemoParent`` connection is established. 840 841 .. code-block:: cpp 842 843 bool DemoParent::Host::MakeBridgeAndResolve() { 844 ipc::Endpoint<PDemoHelplineParent> parent; 845 ipc::Endpoint<PDemoHelplineChild> child; 846 847 auto resolveFail = MakeScopeExit([&] { mResolver(Nothing()); }); 848 849 // Parent side is first argument (main/content), child is second (demo). 850 nsresult rv = PDempHelpline::CreateEndpoints(&parent, &child); 851 852 // ... 853 854 if (!mActor->SendCreateDemoHelplineChild(std::move(child))) { 855 NS_WARNING("Failed to SendCreateDemoHelplineChild"); 856 return false; 857 } 858 859 resolveFail.release(); 860 mResolver(Some(std::move(parent))); 861 return true; 862 } 863 864 Because the operation of launching a process is asynchronous, we have 865 configured this so that it creates the two endpoints for the new top-level 866 actors, then we send the child one to the new process and resolve a promise 867 with the other. The **Demo** process creates its ``PDemoHelplineChild`` 868 easily: 869 870 .. code-block:: cpp 871 872 mozilla::ipc::IPCResult DemoChild::RecvCreateDemoHelplineChild( 873 Endpoint<PDemoHelplineChild>&& aEndpoint) { 874 mDemoHelplineChild = new DemoHelplineChild(); 875 if (!aEndpoint.Bind(mDemoHelplineChild)) { 876 return IPC_FAIL(this, "Unable to bind DemoHelplineChild"); 877 } 878 return IPC_OK(); 879 } 880 881 ``MakeProcessAndGetAssistance`` binds the same way: 882 883 .. code-block:: cpp 884 885 RefPtr<DemoHelplineParent> demoHelplineParent = new DemoHelplineParent(); 886 if (!endpoint.Bind(demoHelplineParent)) { 887 NS_WARNING("Unable to bind DemoHelplineParent"); 888 return false; 889 } 890 MOZ_ASSERT(ok); 891 892 However, the parent may be in the main process or in content. We handle both 893 cases in the next section. 894 895 .. _Connecting With Other Processes: 896 897 Connecting With Other Processes 898 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 899 900 ``DemoHelplineParent::MakeProcessAndGetAssistance`` is the method that we run 901 from either the main or the content process and that should kick off the 902 procedure that will result in sending a string (that we get from a new **Demo** 903 process) to a DOM promise. It starts by constructing a different promise -- 904 one like the ``mResolver`` in ``Host::MakeBridgeAndResolve`` in the last 905 section that produced a ``Maybe<Endpoint<PDemoHelplineParent>>``. In the main 906 process, we just make the promise ourselves and call 907 ``DemoParent::LaunchDemoProcess`` to start the procedure that will result in 908 it being resolved as already described. If we are calling from the content 909 process, we simply write an async ``PContent`` message that calls 910 ``DemoParent::LaunchDemoProcess`` and use the message handler's promise as 911 our promise: 912 913 .. code-block:: cpp 914 915 /* static */ 916 bool DemoHelplineParent::MakeProcessAndGetAssistance( 917 RefPtr<mozilla::dom::Promise> aPromise) { 918 RefPtr<LaunchDemoProcessPromise> resolver; 919 920 if (XRE_IsContentProcess()) { 921 auto* contentChild = mozilla::dom::ContentChild::GetSingleton(); 922 MOZ_ASSERT(contentChild); 923 924 resolver = contentChild->SendLaunchDemoProcess(); 925 } else { 926 MOZ_ASSERT(XRE_IsParentProcess()); 927 auto promise = MakeRefPtr<LaunchDemoProcessPromise::Private>(__func__); 928 resolver = promise; 929 930 if (!DemoParent::LaunchDemoProcess( 931 base::GetCurrentProcId(), 932 [promise = std::move(promise)]( 933 Maybe<Endpoint<PDemoHelplineParent>>&& aMaybeEndpoint) mutable { 934 promise->Resolve(std::move(aMaybeEndpoint), __func__); 935 })) { 936 NS_WARNING("Failed to launch Demo process"); 937 resolver->Reject(NS_ERROR_FAILURE); 938 return false; 939 } 940 } 941 942 resolver->Then( 943 GetMainThreadSerialEventTarget(), __func__, 944 [aPromise](Maybe<Endpoint<PDemoHelplineParent>>&& maybeEndpoint) mutable { 945 if (!maybeEndpoint) { 946 aPromise->MaybeReject(NS_ERROR_FAILURE); 947 return; 948 } 949 950 RefPtr<DemoHelplineParent> demoHelplineParent = new DemoHelplineParent(); 951 Endpoint<PDemoHelplineParent> endpoint = maybeEndpoint.extract(); 952 if (!endpoint.Bind(demoHelplineParent)) { 953 NS_WARNING("Unable to bind DemoHelplineParent"); 954 return false; 955 } 956 MOZ_ASSERT(ok); 957 958 // ... communicate with PDemoHelpline and write message to console ... 959 }, 960 [aPromise](mozilla::ipc::ResponseRejectReason&& aReason) { 961 aPromise->MaybeReject(NS_ERROR_FAILURE); 962 }); 963 964 return true; 965 } 966 967 mozilla::ipc::IPCResult ContentParent::RecvLaunchDemoProcess( 968 LaunchDemoProcessResolver&& aResolver) { 969 if (!DemoParent::LaunchDemoProcess(OtherPid(), 970 std::move(aResolver))) { 971 NS_WARNING("Failed to launch Demo process"); 972 } 973 return IPC_OK(); 974 } 975 976 To summarize, connecting processes always requires endpoints to be constructed 977 by the main process, even when neither process being connected is the main 978 process. It is the only process that creates ``Endpoint`` objects. From that 979 point, connecting is just a matter of sending the endpoints to the right 980 processes, constructing an actor for them, and then calling ``Endpoint::Bind``. 981 982 Completing the Sample 983 ~~~~~~~~~~~~~~~~~~~~~ 984 985 We have covered the main parts needed for the sample. Now we just need to wire 986 it all up. First, we add the new JS command to ``Navigator.webidl`` and 987 ``Navigator.h``/``Navigator.cpp``: 988 989 .. code-block:: cpp 990 991 partial interface Navigator { 992 [Throws] 993 Promise<DOMString> getAssistance(); 994 }; 995 996 already_AddRefed<Promise> Navigator::GetAssistance(ErrorResult& aRv) { 997 if (!mWindow || !mWindow->GetDocShell()) { 998 aRv.Throw(NS_ERROR_UNEXPECTED); 999 return nullptr; 1000 } 1001 1002 RefPtr<Promise> echoPromise = Promise::Create(mWindow->AsGlobal(), aRv); 1003 if (NS_WARN_IF(aRv.Failed())) { 1004 return nullptr; 1005 } 1006 1007 if (!DemoHelplineParent::MakeProcessAndGetAssistance(echoPromise)) { 1008 aRv.Throw(NS_ERROR_FAILURE); 1009 return nullptr; 1010 } 1011 1012 return echoPromise.forget(); 1013 } 1014 1015 Then, we need to add the part that gets the string we use to resolve the 1016 promise in ``MakeProcessAndGetAssistance`` (or reject it if it hasn't been 1017 resolved by the time ``ActorDestroy`` is called): 1018 1019 .. code-block:: cpp 1020 1021 using DemoPromise = MozPromise<nsString, nsresult, true>; 1022 1023 /* static */ 1024 bool DemoHelplineParent::MakeProcessAndGetAssistance( 1025 RefPtr<mozilla::dom::Promise> aPromise) { 1026 1027 // ... construct and connect demoHelplineParent ... 1028 1029 RefPtr<DemoPromise> promise = demoHelplineParent->mPromise.Ensure(__func__); 1030 promise->Then( 1031 GetMainThreadSerialEventTarget(), __func__, 1032 [demoHelplineParent, aPromise](nsString aMessage) mutable { 1033 aPromise->MaybeResolve(aMessage); 1034 }, 1035 [demoHelplineParent, aPromise](nsresult aErr) mutable { 1036 aPromise->MaybeReject(aErr); 1037 }); 1038 1039 if (!demoHelplineParent->SendRequestAssistance()) { 1040 NS_WARNING("DemoHelplineParent::SendRequestAssistance failed"); 1041 } 1042 } 1043 1044 mozilla::ipc::IPCResult DemoHelplineParent::RecvAssistance( 1045 nsString&& aMessage, const AssistanceResolver& aResolver) { 1046 mPromise.Resolve(aMessage, __func__); 1047 aResolver(true); 1048 return IPC_OK(); 1049 } 1050 1051 void DemoHelplineParent::ActorDestroy(ActorDestroyReason aWhy) { 1052 mPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); 1053 } 1054 1055 The ``DemoHelplineChild`` has to respond to the ``RequestAssistance`` method, 1056 which it does by returning a string and then calling ``Close`` on itself when 1057 the string has been received (but we do not call ``Close`` in the ``Recv`` 1058 method!). We use an async response to the ``GiveAssistance`` message to detect 1059 that the string was received. During closing, the actor's ``ActorDestroy`` 1060 method then calls the ``DemoChild::Shutdown`` method we defined in `Destroying 1061 the New Process`_: 1062 1063 .. code-block:: cpp 1064 1065 mozilla::ipc::IPCResult DemoHelplineChild::RecvRequestAssistance() { 1066 RefPtr<DemoHelplineChild> me = this; 1067 RefPtr<nsIRunnable> runnable = 1068 NS_NewRunnableFunction("DemoHelplineChild::Close", [me]() { me->Close(); }); 1069 1070 SendAssistance( 1071 nsString(HelpMessage()), 1072 [runnable](bool) { NS_DispatchToMainThread(runnable); }, 1073 [runnable](mozilla::ipc::ResponseRejectReason) { 1074 NS_DispatchToMainThread(runnable); 1075 }); 1076 1077 return IPC_OK(); 1078 } 1079 1080 void DemoHelplineChild::ActorDestroy(ActorDestroyReason aWhy) { 1081 DemoChild::Shutdown(); 1082 } 1083 1084 During the **Demo** process lifetime, there are two references to the 1085 ``DemoHelplineChild``, one from IPDL and one from the ``DemoChild``. The call 1086 to ``Close`` releases the one held by IPDL and the other isn't released until 1087 the ``DemoChild`` is destroyed. 1088 1089 Running the Sample 1090 ~~~~~~~~~~~~~~~~~~ 1091 1092 To run the sample, build and run and open the console. The new command is 1093 ``navigator.getAssistance().then(console.log)``. The message sent by 1094 ``SendAssistance`` is then logged to the console. The sample code also 1095 includes the name of the type of process that was used for the 1096 ``DemoHelplineParent`` so you can confirm that it works from main and from 1097 content. 1098 1099 Debugging Process Startup 1100 ------------------------- 1101 1102 Debugging a child process at the start of its life is tricky. With most 1103 platforms/toolchains, it is surprisingly difficult to connect a debugger before 1104 the main routine begins execution. You may also find that console logging is 1105 not yet established by the operating system, especially when working with 1106 sandboxed child processes. Gecko has some facilities that make this less 1107 painful. 1108 1109 .. _Debugging with IPDL Logging: 1110 1111 Debugging with IPDL Logging 1112 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1113 1114 This is also best seen with an example. To start, we can create a bug in the 1115 sample by removing the ``EmptyMessageQueue`` message sent to ``DemoParent``. 1116 This message was intended to guarantee that the ``DemoParent`` had handled all 1117 messages sent before it, so we could ``Close`` with the knowledge that we 1118 didn't miss anything. This sort of bug can be very difficult to track down 1119 because it is likely to be intermittent and may manifest more easily on some 1120 platforms/architectures than others. To create this bug, replace the 1121 ``SendEmptyMessageQueue`` call in ``DemoChild::Shutdown``: 1122 1123 .. code-block:: cpp 1124 1125 auto dc = gDemoChild; 1126 RefPtr<nsIRunnable> runnable = NS_NewRunnableFunction( 1127 "DemoChild::FinishShutdown", 1128 [dc2 = std::move(gDemoChild)]() { dc2->Close(); }); 1129 dc->SendEmptyMessageQueue( 1130 [runnable](bool) { NS_DispatchToMainThread(runnable); }, 1131 [runnable](mozilla::ipc::ResponseRejectReason) { 1132 NS_DispatchToMainThread(runnable); 1133 }); 1134 1135 with just an (asynchronous) call to ``Close``: 1136 1137 .. code-block:: cpp 1138 1139 NS_DispatchToMainThread(NS_NewRunnableFunction( 1140 "DemoChild::FinishShutdown", 1141 [dc = std::move(gDemoChild)]() { dc->Close(); })); 1142 1143 When we run the sample now, everything seems to behave ok but we see messages 1144 like these in the console: :: 1145 1146 ###!!! [Parent][RunMessage] Error: (msgtype=0x410001,name=PDemo::Msg_InitCrashReporter) Channel closing: too late to send/recv, messages will be lost 1147 1148 [Parent 16672, IPC I/O Parent] WARNING: file c:/mozilla-src/mozilla-unified/ipc/chromium/src/base/process_util_win.cc:167 1149 [Parent 16672, Main Thread] WARNING: Not resolving response because actor is dead.: file c:/mozilla-src/mozilla-unified/ipc/glue/ProtocolUtils.cpp:931 1150 [Parent 16672, Main Thread] WARNING: IPDL resolver dropped without being called!: file c:/mozilla-src/mozilla-unified/ipc/glue/ProtocolUtils.cpp:959 1151 1152 We could probably figure out what is happening here from the messages but, 1153 with more complex protocols, understanding what led to this may not be so easy. 1154 To begin diagnosing, we can turn on IPC Logging, which was defined in the IPDL 1155 section on :ref:`Message Logging`. We just need to set an environment variable 1156 before starting the browser. Let's turn it on for all ``PDemo`` and 1157 ``PDemoHelpline`` actors: :: 1158 1159 MOZ_IPC_MESSAGE_LOG="PDemo,PDemoHelpline" 1160 1161 To underscore what we said above, when logging is active, the change in timing 1162 makes the error message go away and everything closes properly on a tested 1163 Windows desktop. However, the issue remains on a Macbook Pro and the log 1164 shows the issue rather clearly: :: 1165 1166 [time: 1627075553937959][63096->63085] [PDemoChild] Sending PDemo::Msg_InitCrashReporter 1167 [time: 1627075553949441][63085->63096] [PDemoParent] Sending PDemo::Msg_CreateDemoHelplineChild 1168 [time: 1627075553950293][63092->63096] [PDemoHelplineParent] Sending PDemoHelpline::Msg_RequestAssistance 1169 [time: 1627075553979151][63096<-63085] [PDemoChild] Received PDemo::Msg_CreateDemoHelplineChild 1170 [time: 1627075553979433][63096<-63092] [PDemoHelplineChild] Received PDemoHelpline::Msg_RequestAssistance 1171 [time: 1627075553979498][63096->63092] [PDemoHelplineChild] Sending PDemoHelpline::Msg_GiveAssistance 1172 [time: 1627075553980105][63092<-63096] [PDemoHelplineParent] Received PDemoHelpline::Msg_GiveAssistance 1173 [time: 1627075553980181][63092->63096] [PDemoHelplineParent] Sending reply PDemoHelpline::Reply_GiveAssistance 1174 [time: 1627075553980449][63096<-63092] [PDemoHelplineChild] Received PDemoHelpline::Reply_GiveAssistance 1175 [tab 63092] NOTE: parent actor received `Goodbye' message. Closing channel. 1176 [default 63085] NOTE: parent actor received `Goodbye' message. Closing channel. 1177 [...] 1178 ###!!! [Parent][RunMessage] Error: (msgtype=0x420001,name=PDemo::Msg_InitCrashReporter) Channel closing: too late to send/recv, messages will be lost 1179 [...] 1180 [default 63085] NOTE: parent actor received `Goodbye' message. Closing channel. 1181 1182 The imbalance with ``Msg_InitCrashReporter`` is clear. The message was not 1183 *Received* before the channel was closed. Note that the first ``Goodbye`` for 1184 the main (default) process is for the ``PDemoHelpline`` actor -- in this case, 1185 its child actor was in a content (tab) process. The second default process 1186 ``Goodbye`` is from the **Demo** process, sent when doing ``Close``. It might 1187 seem that it should handle the ``Msg_InitCrashReporter`` if it can handle the 1188 later ``Goodbye`` but this does not happen for safety reasons. 1189 1190 Early Debugging For A New Process 1191 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1192 1193 Let's assume now that we still don't understand the problem -- maybe we don't 1194 know that the ``InitCrashReporter`` message is sent internally by the 1195 ``CrashReporterClient`` we initialized. Or maybe we're only looking at Windows 1196 builds. We decide we'd like to be able to hook a debugger to the new process 1197 so that we can break on the ``SendInitCrashReporter`` call. Attaching the 1198 debugger has to happen fast -- process startup probably completes in under a 1199 second. Debugging this is not always easy. 1200 1201 Windows users have options that work with both the Visual Studio and WinDbg 1202 debuggers. For Visual Studio users, there is an easy-to-use VS addon called 1203 the `Child Process Debugging Tool`_ that allows you to connect to *all* 1204 processes that are launched by a process you are debugging. So, if the VS 1205 debugger is connected to the main process, it will automatically connect to the 1206 new **Demo** process (and every other launched process) at the point that they 1207 are spawned. This way, the new process never does anything outside of the 1208 debugger. Breakpoints, etc work as expected. The addon mostly works like a 1209 toggle and will remain on until it is disabled from the VS menu. 1210 1211 WinDbg users can achieve essentially the same behavior with the `.childdbg`_ 1212 command. See the docs for details but essentially all there is to know is that 1213 ``.childdbg 1`` enables it and ``.childdbg 0`` disables it. You might add it 1214 to a startup config file (see the WinDbg ``-c`` command line option) 1215 1216 Linux and mac users should reference gdb's ``detach-on-fork``. The command to 1217 debug child processes is ``set detach-on-fork off``. Again, the behavior is 1218 largely what you would expect -- that all spawned processes are added to the 1219 current debug session. The command can be added to ``.gdbinit`` for ease. At 1220 the time of this writing, lldb does not support automatically connecting to 1221 newly spawned processes. 1222 1223 Finally, Linux users can use ``rr`` for time-travel debugging. See :ref:`Debugging 1224 Firefox with rr <Debugging Firefox with rr>` for details. 1225 1226 These solutions are not always desirable. For example, the fact that they hook 1227 *all* spawned processes can mean that targeting breakpoints to one process 1228 requires us to manually disconnect many other processes. In these cases, an 1229 easier solution may be to use Gecko environment variables that will cause the 1230 process to sleep for some number of seconds. During that time, you can find 1231 the process ID (PID) for the process you want to debug and connect your 1232 debugger to it. OS tools like ``ProcessMonitor`` can give you the PID but it 1233 will also be clearly logged to the console just before the process waits. 1234 1235 Set ``MOZ_DEBUG_CHILD_PROCESS=1`` to turn on process startup pausing. You can 1236 also set ``MOZ_DEBUG_CHILD_PAUSE=N`` where N is the number of seconds to sleep. 1237 The default is 10 seconds on Windows and 30 on other platforms. 1238 1239 Pausing for the debugger is not a panacea. Since the environmental variables 1240 are not specific to process type, you will be forced to wait for all of the 1241 processes Gecko creates before you wait for it to get to yours. The pauses can 1242 also end up exposing unknown concurrency bugs in the browser before it even 1243 gets to your issue, which is good to discover but doesn't fix your bug. That 1244 said, any of these strategies would be enough to facilitate easily breaking on 1245 ``SendInitCrashReporter`` and finding our sender. 1246 1247 .. _Child Process Debugging Tool: https://marketplace.visualstudio.com/items?itemName=vsdbgplat.MicrosoftChildProcessDebuggingPowerTool 1248 .. _.childdbg: https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-childdbg--debug-child-processes-