The Sphinx configuration already supports both RST and Markdown via
myst-parser. Converting these docs to Markdown for consistency with
the rest of the tree.
Done with rst-to-myst plus manual fixups: replace eval-rst tables that
relied on RST substitutions with Markdown tables using inline HTML for
strikethrough/line-break, fix :ref: cross-references to use explicit
slug labels, and update the cross-doc link in index.md to use {doc}.
docs/config.yml warning patterns updated from .rst to .md.
Differential Revision: https://phabricator.services.mozilla.com/D304196
1177 lines
60 KiB
Markdown
1177 lines
60 KiB
Markdown
# Gecko Processes
|
|
|
|
## Before Creating a New Process
|
|
|
|
Firefox started out as a one process application. Then, one became two as
|
|
NPAPI plugins like Flash were pushed into their own process (plugin processes)
|
|
for security and stability reasons. Then, it split again so that the browser
|
|
could also disentangle itself from web content (content processes). Then,
|
|
implementations on some platforms developed processes for graphics ("GPU"
|
|
processes). And for media codecs. And VR. And file URLs. And sockets. And
|
|
even more content processes. And so on...
|
|
|
|
Here is an incomplete list of *good* reasons we've created new processes:
|
|
|
|
- Separating HTML and JS from the browser makes it possible to secure the
|
|
browser and the rest of the system from them, even when those APIs are
|
|
compromised.
|
|
- Browser stability was also improved by separating HTML and JS from the
|
|
browser, since catastrophic failures related to a tab could be limited to the
|
|
tab instead of crashing the browser.
|
|
- Site isolation requires additional processes to separate HTML and JS for
|
|
different sites. The separation of memory spaces undermines many types of
|
|
exploits.
|
|
- Sandboxing processes offers great security guarantees but requires making
|
|
tradeoffs between power and protection. More processes means more options.
|
|
For example, we heavily sandbox content processes to protect from external
|
|
code, while the File process, which is a content process that can access
|
|
`file://` URLs, has a sandbox that is similar but allows access to local
|
|
files.
|
|
- One of the benefits of the GPU process was that it improved browser
|
|
stability by separating a system component that had frequent stability
|
|
issues -- GPU drivers. The same logic inspired the NPAPI (Flash) plugin
|
|
process.
|
|
|
|
Informed by this history, there is some of non-obvious preparation that you
|
|
should do before starting down this path. This falls under the category of
|
|
"First, do no harm":
|
|
|
|
- **Consult the Platform and IPC teams** (#ipc) to develop the plan for the
|
|
way your process will integrate with the systems in which it will exist, as
|
|
well as how it will be handled on any platforms where it will *not* exist.
|
|
For example, an application's process hierarchy forms a tree where one process
|
|
spawns another. Currently, all processes in Firefox are spawned by the main
|
|
process (excepting the [launcher process]). There is good reason for this,
|
|
mostly based on our sandboxing restrictions that forbid non-main processes
|
|
from launching new processes themselves. But it means that the main process
|
|
will need to know to create your process. If you make the decision to do
|
|
this from, say, a content process, you will need a safe, performant and
|
|
stable way to request this of the main process. You will also need a way to
|
|
efficiently communicate directly with your new process. And you will need to
|
|
consider limitations of some platforms (think Android) where you may not want
|
|
to or not be able to spawn the new process.
|
|
- **Consult the sandboxing team** (#hardening) to discuss what the sandbox for
|
|
your new process will look like. Anything that compromises security is a
|
|
non-starter. You may, for instance, want to create a new process to escape
|
|
the confines of the sandbox in a content process. This can be legitimate,
|
|
for example you may need access to some device API that is unavailable to a
|
|
content process, but the security for your new process will then have to come
|
|
from a different source. "I won't run Javascript" is not sufficient. Keep
|
|
in mind that your process will have to have some mechanism for communication
|
|
with other processes to be useful, so it is always a potential target.
|
|
|
|
:::{note}
|
|
Firefox has, to date, undergone exactly one occurrence of the *removal* of
|
|
a process type. In 2020, the NPAPI plugin process was removed when the
|
|
last supported plugin, Adobe's FlashPlayer, reached its end-of-life.
|
|
:::
|
|
|
|
## Firefox Process Hierarchy
|
|
|
|
This diagram shows the primary process types in Firefox.
|
|
|
|
```{mermaid}
|
|
graph TD
|
|
RDD -->|PRemoteMediaManager| Content
|
|
RDD(Data Decoder) ==>|PRDD| Main
|
|
|
|
Launcher --> Main
|
|
|
|
Main ==>|PContent| Content
|
|
Main ==>|PSocketProcess| Socket(Network Socket)
|
|
Main ==>|PGMP| GMP(Gecko Media Plugins)
|
|
VR ==>|PVR| Main
|
|
GPU ==>|PGPU| Main
|
|
|
|
Socket -->|PSocketProcessBridge| Content
|
|
|
|
GPU -->|PCompositorManager| Main
|
|
GPU -->|PCompositorManager| Content
|
|
|
|
Content -->|PGMPContent| GMP
|
|
|
|
VR -->|PVRGPU| GPU
|
|
```
|
|
|
|
:::{warning}
|
|
The main process is sometimes called the UI process, the chrome process,
|
|
the browser process or the parent process. This is true for documentation,
|
|
conversation and, most significantly, **code**. Due to the syntactic
|
|
overlap with IPDL actors, that last name can get pretty confusing. Less
|
|
commonly, the content process is called the renderer process, which is it's
|
|
name in Chromium code. Since the content process sandbox won't allow it,
|
|
Firefox never does (hardware) rendering in the content/rendering process!
|
|
:::
|
|
|
|
The arrows point from the parent side to the child. Bolded arrows indicate the
|
|
first top-level actors for the various process types. The other arrows show
|
|
important actors that are usually the first connections established between the
|
|
two processes. These relationships difficult to discern from code. Processes
|
|
should clearly document their top-level connections in their IPDL files.
|
|
|
|
Some process types only exist on some platforms and some processes may only be
|
|
created on demand. For example, Mac builds do not use a GPU process but
|
|
instead fold the same actor connections into its main process (except `PGPU`,
|
|
which it does not use). These exceptions are also very hard to learn from code
|
|
and should be clearly documented.
|
|
|
|
`about:processes` shows statistics for the processes in a currently running
|
|
browser. It is also useful to see the distribution of web pages across content
|
|
processes.
|
|
|
|
(adding-a-new-type-of-process)=
|
|
|
|
## Adding a New Type of Process
|
|
|
|
Adding a new process type doesn't require any especially difficult steps but it
|
|
does require a lot of steps that are not obvious. This section will focus on
|
|
the steps as it builds an example. It will be light on the details of the
|
|
classes and protocols involved. Some implementations may need to seek out a
|
|
deeper understanding of the components set up here but most should instead
|
|
strive for simplicity.
|
|
|
|
In the spirit of creating a *responsible* process, the sample will connect
|
|
several components that any deployed Gecko process is likely to need. These
|
|
include configuring a sandbox, {ref}`registration with the CrashReporter service <Crash Reporter>`
|
|
and ("minimal") XPCOM initialization. Consult documentation for these
|
|
components for more information on their integration.
|
|
|
|
This example will be loosely based on the old (now defunct) IPDL **Extending a
|
|
Protocol** example for adding a new actor. We will add a command to the
|
|
browser's `navigator` JS object, `navigator.getAssistance()`. When the
|
|
user enters the new command in, say, the browser's console window, it will
|
|
create a new process of our new **Demo** process type and ask that process for
|
|
"assistance" in the form of a string that it will then print to the console.
|
|
Once that is done, the new process will be cleanly destroyed.
|
|
|
|
Code for the complete demo can be found [here](https://phabricator.services.mozilla.com/D119038).
|
|
|
|
### Common Architecture
|
|
|
|
Every type of process (besides the launcher and main processes) needs two
|
|
classes and an actor pair to launch. This sample will be adding a process type
|
|
we call **Demo**.
|
|
|
|
- An actor pair where the parent actor is a top-level actor in the main process
|
|
and the child is the (first) top-level actor in the new process. It is common
|
|
for this actor to simply take the name of the process type. The sample uses
|
|
`PDemo`, so it creates `DemoParent` and `DemoChild` actor subclasses
|
|
as usual (see {ref}`IPDL: Inter-Thread and Inter-Process Message Passing`).
|
|
- A subclass of {searchfox}`GeckoChildProcessHost <ipc/glue/GeckoChildProcessHost.h>`
|
|
that exists in the main process (where new processes are created) and handles
|
|
most of the machinery needed for new process creation. It is common for these
|
|
names to be the process type plus `ProcessParent` or `ProcessHost`. The
|
|
sample uses `DemoParent::Host`, a private class, which keeps
|
|
`GeckoChildProcessHost` out of the **Demo** process' *public interface*
|
|
since it is large, complicated and mostly unimportant externally. This
|
|
complexity is also why it is a bad idea to add extra responsibilities to the
|
|
`Host` object that inherits it.
|
|
- A subclass of {searchfox}`ProcessChild <ipc/glue/ProcessChild.h>` that
|
|
exists in the new process. These names are usually generated by affixing
|
|
`ProcessChild` or `ProcessImpl` to the type. The sample will use
|
|
`DemoChild::Process`, another private class, for the same reasons it did
|
|
with the `Host`.
|
|
|
|
A fifth class is optional but integration with common services requires
|
|
something like it:
|
|
|
|
- A singleton class that "manages" the collective of processes (usually the
|
|
Host objects) of the new type in the main process. In many instances, there
|
|
is at most one instance of a process type, so this becomes a singleton that
|
|
manages a singleton... that manages a singleton. Object ownership is often
|
|
hard to establish between manager objects and the hosts they manage. It is
|
|
wise to limit the power of these classes. This class will often get its name
|
|
by appending `ProcessManager` to the process type. The sample provides a
|
|
very simple manager in `DemoParent::Manager`.
|
|
|
|
Finally, it is highly probable and usually desirable for the new process to
|
|
include another new top-level actor that represents the top-level operations
|
|
and communications of the new process. This actor will use the new process as
|
|
a child but may have any other process as the parent, unlike `PDemo` whose
|
|
parent is always the main process. This new actor will be created by the main
|
|
process, which creates a pair of `Endpoint` objects specifically for the
|
|
desired process pairing, and then sends those `Endpoint` objects to their
|
|
respective processes. The **Demo** example is interesting because the user can
|
|
issue the command from a content process or the main one, by opening the
|
|
console in a normal or a privileged page (e.g. `about:sessionrestore`),
|
|
respectively. Supporting both of these cases will involve very little
|
|
additional effort. The sample will show this as part of implementing the
|
|
second top-level actor pair `PDemoHelpline` in [Connecting With Other
|
|
Processes](#connecting-with-other-processes), where the parent can be in either the main or a content process.
|
|
|
|
The rest of the sections will explain how to compose these classes and
|
|
integrate them with Gecko.
|
|
|
|
### Process Bookkeeping
|
|
|
|
(process-bookkeeping)=
|
|
|
|
To begin with, look at the {searchfox}`geckoprocesstypes generator <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:xpcom/geckoprocesstypes_generator/geckoprocesstypes/__init__.py>`
|
|
which adds the bones for a new process (by defining enum values and so on).
|
|
Some further manual intervention is still required, and you need to follow the
|
|
following checklists depending on your needs.
|
|
|
|
#### Basic requirements
|
|
|
|
- Add a new entry to the {searchfox}`enum WebIDLProcType <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:dom/chrome-webidl/ChromeUtils.webidl#610-638>`
|
|
- Update the {searchfox}`static_assert <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:toolkit/xre/nsAppRunner.cpp#988-990>`
|
|
call checking for boundary against `GeckoProcessType_End`
|
|
- Add your process to the correct `MessageLoop::TYPE_x` in the first
|
|
`switch(XRE_GetProcessType())` in {searchfox}`XRE_InitChildProcess <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:toolkit/xre/nsEmbedFunctions.cpp#572-590>`.
|
|
You can get more information about that topic in {searchfox}`this comment <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:ipc/chromium/src/base/message_loop.h#159-187>`
|
|
- Instantiate your child within the second `switch (XRE_GetProcessType())` in
|
|
{searchfox}`XRE_InitChildProcess <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:toolkit/xre/nsEmbedFunctions.cpp#615-671>`
|
|
- Add a new entry `PROCESS_TYPE_x` in {searchfox}`nsIXULRuntime interface <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:xpcom/system/nsIXULRuntime.idl#183-196>`
|
|
|
|
##### Graphics
|
|
|
|
If you need graphics-related interaction, hack into {searchfox}`gfxPlatform <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:gfx/thebes/gfxPlatform.cpp>`
|
|
|
|
- Add a call to your process manager init in `gfxPlatform::Init()` in
|
|
{searchfox}`gfxPlatform <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:gfx/thebes/gfxPlatform.cpp#808-810>`
|
|
- Add a call to your process manager shutdown in `gfxPlatform::Shutdown()` in
|
|
{searchfox}`gfxPlatform <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:gfx/thebes/gfxPlatform.cpp#1255-1259>`
|
|
|
|
##### Android
|
|
|
|
You might want to talk with `#geckoview` maintainers to ensure if this is
|
|
required or applicable to your new process type.
|
|
|
|
- Add a new `<service>` entry against
|
|
`org.mozilla.gecko.process.GeckoChildProcessServices$XXX` in the
|
|
{searchfox}`AndroidManifest <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:mobile/android/geckoview/src/main/AndroidManifest.xml#45-81>`
|
|
- Add matching class inheritance from {searchfox}`GeckoChildProcessServices <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoChildProcessServices.jinja#10-13>`
|
|
- Add new entry in {searchfox}`public enum GeckoProcessType <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessType.java#11-23>`
|
|
|
|
##### Crash reporting
|
|
|
|
- Add `InitCrashReporter` message to the parent-side {searchfox}`InitCrashReporter <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/PUtilityProcess.ipdl#30>`
|
|
- Ensure your parent class inherits [public ipc::CrashReporterHelper\<GeckoProcessType_Xxx>](https://searchfox.org/mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc/ipc/glue/UtilityProcessParent.h#23)
|
|
- Add new `Xxx*Status` {searchfox}`annotations <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:toolkit/crashreporter/CrashAnnotations.yaml#968-971>`
|
|
entry for your new process type description. The link here points to
|
|
`UtilityProcessStatus` so you can see the similar description you have to
|
|
write, but you might want to respect ordering in that file and put your new
|
|
code at the appropriate place.
|
|
- Add entry in {searchfox}`PROCESS_CRASH_SUBMIT_ATTEMPT <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:toolkit/components/telemetry/Histograms.json#13403-13422>` and
|
|
`submit_attempt` in `toolkit/components/crashes/metrics.yaml`.
|
|
|
|
##### Memory reporting
|
|
|
|
Throughout the linked code, please consider those methods more as boilerplate code that will require some trivial modification to fit your exact usecase.
|
|
|
|
- Add definition of memory reporter to your new {ref}`top-level actor <top-level-actors>`
|
|
|
|
- Type inclusion {searchfox}`MemoryReportTypes <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/PUtilityProcess.ipdl#6>`
|
|
- To parent-side {searchfox}`AddMemoryReport <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/PUtilityProcess.ipdl#32>`
|
|
- To child-side {searchfox}`RequestMemoryReport <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/PUtilityProcess.ipdl#44-48>`
|
|
|
|
- Add handling for your new process within {searchfox}`nsMemoryReporterManager::GetReportsExtended <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:xpcom/base/nsMemoryReporterManager.cpp#1813-1819>`
|
|
|
|
- Provide a process manager level abstraction
|
|
|
|
- Implement a new class deriving `MemoryReportingProcess` such as {searchfox}`UtilityMemoryReporter <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/UtilityProcessManager.cpp#253-292>`
|
|
- Write a {searchfox}`GetProcessMemoryReport <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/UtilityProcessManager.cpp#294-300>`
|
|
|
|
- On the child side, provide an implementation for {searchfox}`RequestMemoryReport <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/UtilityProcessChild.cpp#153-166>`
|
|
|
|
- On the parent side
|
|
|
|
- Provide an implementation for {searchfox}`RequestMemoryReport <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/UtilityProcessParent.cpp#41-69>`
|
|
- Provide an implementation for {searchfox}`AddMemoryReport <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/UtilityProcessParent.cpp#71-77>`
|
|
|
|
If you want to add a test that ensures proper behavior, you can have a look at the {searchfox}`utility process memory report test <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/test/browser/browser_utility_memoryReport.js>`
|
|
|
|
##### Process reporting
|
|
|
|
Those elements will be used for exposing processes to users in some `about:`
|
|
pages. You might want to ping `#fluent-reviewers` to ensure if you need your
|
|
process there.
|
|
|
|
- Add a {searchfox}`user-facing localizable name <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:toolkit/locales/en-US/toolkit/global/processTypes.ftl#39-57>`
|
|
for your process, if needed
|
|
|
|
- Hashmap from process type to user-facing string above in {searchfox}`const ProcessType <mozilla-central/rev/c5c002f81f08a73e04868e0c2bf0eb113f200b03:toolkit/modules/ProcessType.sys.mjs#10-16>`
|
|
|
|
- For `about:processes` you will probably want to follow the following steps:
|
|
|
|
- Add handling for your new process type producing a unique {searchfox}`fluentName <mozilla-central/rev/be4604e4be8c71b3c1dbff2398a5b05f15411673:toolkit/components/aboutprocesses/content/aboutProcesses.js#472-539>`, i.e., constructing a dynamic name is highly discouraged
|
|
- Add matching localization strings within {searchfox}`about:processes localization file <mozilla-central/rev/be4604e4be8c71b3c1dbff2398a5b05f15411673:toolkit/locales/en-US/toolkit/about/aboutProcesses.ftl#35-55>`
|
|
- Add matching localization strings within {searchfox}`about:support localization file <toolkit/locales/en-US/toolkit/global/processTypes.ftl#54-60>`
|
|
|
|
##### Profiler
|
|
|
|
- Add definition of `PProfiler` to your new IPDL
|
|
|
|
- Type inclusion {searchfox}`protocol PProfiler <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/PUtilityProcess.ipdl#9>`
|
|
- Child-side {searchfox}`InitProfiler <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/PUtilityProcess.ipdl#42>`
|
|
|
|
- Make sure your initialization path contains a {searchfox}`SendInitProfiler <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.
|
|
|
|
- Provide an implementation for {searchfox}`InitProfiler <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/UtilityProcessChild.cpp#147-151>`
|
|
|
|
- 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.
|
|
|
|
##### Static Components
|
|
|
|
The amount of changes required here are significant, [Bug 1740485: Improve
|
|
StaticComponents code generation](https://bugzilla.mozilla.org/show_bug.cgi?id=1740485) tracks improving that.
|
|
|
|
- Update allowance in those configuration files to match new process selector
|
|
that includes your new process. When exploring those components definitions,
|
|
keep in mind that you are looking at updating `processes` field in the
|
|
`Classes` object. The `ProcessSelector` value will come from what the reader
|
|
writes based on the instructions below. Some of these also contains several
|
|
services, so you might have to ensure you have all your bases covered. Some of
|
|
the components might not need to be updated as well.
|
|
|
|
- {searchfox}`libpref <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:modules/libpref/components.conf>`
|
|
- {searchfox}`telemetry <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:toolkit/components/telemetry/core/components.conf>`
|
|
- {searchfox}`android <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:widget/android/components.conf>`
|
|
- {searchfox}`gtk <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:widget/gtk/components.conf>`
|
|
- {searchfox}`windows <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:widget/windows/components.conf>`
|
|
- {searchfox}`base <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:xpcom/base/components.conf>`
|
|
- {searchfox}`components <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:xpcom/components/components.conf>`
|
|
- {searchfox}`ds <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:xpcom/ds/components.conf>`
|
|
- {searchfox}`threads <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:xpcom/threads/components.conf>`
|
|
- {searchfox}`cocoa kWidgetModule <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:widget/cocoa/nsWidgetFactory.mm#194-202>`
|
|
- {searchfox}`build <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:xpcom/build/components.conf>`
|
|
- {searchfox}`XPCOMinit kXPCOMModule <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:xpcom/build/XPCOMInit.cpp#172-180>`
|
|
|
|
- Within {searchfox}`static components generator <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:xpcom/components/gen_static_components.py>`
|
|
|
|
- Add new definition in `ProcessSelector` for your new process
|
|
`ALLOW_IN_x_PROCESS = 0x..`
|
|
- Add new process selector masks including your new process definition
|
|
- Also add those into the `PROCESSES` structure
|
|
|
|
- Within {searchfox}`module definition <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:xpcom/components/Module.h>`
|
|
|
|
- Add new definition in `enum ProcessSelector`
|
|
- Add new process selector mask including the new definition
|
|
- Update `kMaxProcessSelector`
|
|
|
|
- Within {searchfox}`nsComponentManager <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:xpcom/components/nsComponentManager.cpp>`
|
|
|
|
- Add new selector match in `ProcessSelectorMatches` for your new process
|
|
(needed?)
|
|
- Add new process selector for `gProcessMatchTable` in
|
|
`nsComponentManagerImpl::Init()`
|
|
|
|
##### Glean telemetry
|
|
|
|
- Ensure your new IPDL includes on the child side
|
|
|
|
- {searchfox}`FlushFOGData <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/PUtilityProcess.ipdl#55>`
|
|
- {searchfox}`TestTriggerMetrics <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/PUtilityProcess.ipdl#60>`
|
|
|
|
- Provide a parent-side implementation for {searchfox}`FOGData <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/UtilityProcessParent.cpp#79-82>`
|
|
|
|
- Provide a child-side implementation for {searchfox}`FlushFOGData <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/UtilityProcessChild.cpp#179-183>`
|
|
|
|
- Child-side should flush its FOG data at IPC {searchfox}`ActorDestroy <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/UtilityProcessChild.cpp#199-201>`
|
|
|
|
- Child-side {searchfox}`test metrics <mozilla-central/rev/fc4d4a8d01b0e50d20c238acbb1739ccab317ebc:ipc/glue/UtilityProcessChild.cpp#185-191>`
|
|
|
|
- Within {searchfox}`FOGIPC <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:toolkit/components/glean/ipc/FOGIPC.cpp>`
|
|
|
|
- Add handling of your new process type within `FlushAllChildData()` {searchfox}`here <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:toolkit/components/glean/ipc/FOGIPC.cpp#106-121>`
|
|
and `SendFOGData()` {searchfox}`here <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:toolkit/components/glean/ipc/FOGIPC.cpp#165-182>`
|
|
- Add support for sending test metrics in `TestTriggerMetrics()` {searchfox}`here <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:toolkit/components/glean/ipc/FOGIPC.cpp#208-232>`
|
|
|
|
- Handle process shutdown in `register_process_shutdown()` of {searchfox}`glean <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:toolkit/components/glean/api/src/ipc.rs>`
|
|
|
|
##### Third-Party Modules
|
|
|
|
- Ensure your new IPDL includes on the child side
|
|
|
|
- {searchfox}`GetUntrustedModulesData <mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a:ipc/glue/PUtilityProcess.ipdl#106>`
|
|
- {searchfox}`UnblockUntrustedModulesThread <mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a:ipc/glue/PUtilityProcess.ipdl#113>`
|
|
|
|
- Provide a parent side implementation for both
|
|
|
|
- Add handling of your new process type in `MultiGetUntrustedModulesData::GetUntrustedModuleLoadEvents()` {searchfox}`here <mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a:toolkit/components/telemetry/other/UntrustedModules.cpp#145-151>`
|
|
|
|
- {searchfox}`Update your IPDL <mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a:ipc/glue/PUtilityProcess.ipdl#75>` and make sure your `Init()` can receive a boolean for
|
|
`isReadyForBackgroundProcessing` {searchfox}`like here <mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a:ipc/glue/UtilityProcessChild.cpp#157-160>`, then within the child's `RecvInit()`
|
|
make sure a call to `DllServices`'s `StartUntrustedModulesProcessor()` {searchfox}`is performed <mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a:ipc/glue/UtilityProcessChild.cpp#185-186>`.
|
|
|
|
- Ensure your new IPDL includes for the parent side
|
|
|
|
- {searchfox}`GetModulesTrust <mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a:ipc/glue/PUtilityProcess.ipdl#60-61>`
|
|
|
|
- Provide an implementation on the {searchfox}`parent side <mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a:ipc/glue/UtilityProcessParent.cpp#69-81>`
|
|
|
|
- Expose your new process type as supported in `UntrustedModulesProcessor::IsSupportedProcessType()` {searchfox}`like others <mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a:toolkit/xre/dllservices/UntrustedModulesProcessor.cpp#76-91>`
|
|
|
|
- Update `UntrustedModulesProcessor::SendGetModulesTrust()` to call {searchfox}`your new child process <mozilla-central/rev/2ce39261ea6a69e49d87f76a119494b2a7a7e42a:toolkit/xre/dllservices/UntrustedModulesProcessor.cpp#757-761>`
|
|
|
|
##### Sandboxing
|
|
|
|
Sandboxing changes related to a new process can be non-trivial, so it is
|
|
strongly advised that you reach to the Sandboxing team in `#hardening` to
|
|
discuss your needs prior to making changes.
|
|
|
|
###### Linux Sandbox
|
|
|
|
Linux sandboxing mostly works by allowing / blocking system calls for child
|
|
process and redirecting (brokering) some from the child to the parent. Rules
|
|
are written in a specific DSL: {searchfox}`BPF <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl.h#21-72>`.
|
|
|
|
- Add new `SetXXXSandbox()` function within {searchfox}`linux sandbox <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/linux/Sandbox.cpp#719-748>`
|
|
|
|
- Within {searchfox}`sandbox filter <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/linux/SandboxFilter.cpp>`
|
|
|
|
- Add new helper `GetXXXSandboxPolicy()` {searchfox}`like this one <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/linux/SandboxFilter.cpp#2036-2040>`
|
|
called by `SetXXXSandbox()`
|
|
- Derive new class {searchfox}`similar to this <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/linux/SandboxFilter.cpp#2000-2034>`
|
|
inheriting `SandboxPolicyCommon` or `SandboxPolicyBase` and defining
|
|
the sandboxing policy
|
|
|
|
- Add new `SandboxBrokerPolicyFactory::GetXXXProcessPolicy()` in {searchfox}`sandbox broker <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp#881-932>`
|
|
|
|
- Add new case handling in `GetEffectiveSandboxLevel()` in {searchfox}`sandbox launch <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/linux/launch/SandboxLaunch.cpp#243-271>`
|
|
|
|
- Add new entry in `enum class ProcType` of {searchfox}`sandbox reporter header <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/linux/reporter/SandboxReporterCommon.h#32-39>`
|
|
|
|
- Add new case handling in `SubmitToTelemetry()` in {searchfox}`sandbox reporter <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/linux/reporter/SandboxReporter.cpp#131-152>`
|
|
|
|
- Add new case handling in `SandboxReportWrapper::GetProcType()` of {searchfox}`sandbox reporter wrapper <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/linux/reporter/SandboxReporterWrappers.cpp#69-91>`
|
|
|
|
###### MacOS Sandbox
|
|
|
|
- Add new case handling in `GeckoChildProcessHost::StartMacSandbox()` of
|
|
{searchfox}`GeckoChildProcessHost <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:ipc/glue/GeckoChildProcessHost.cpp#1720-1743>`
|
|
|
|
- Add new entry in `enum MacSandboxType` defined in {searchfox}`macOS sandbox header <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/mac/Sandbox.h#12-20>`
|
|
|
|
- Within {searchfox}`macOS sandbox core <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/mac/Sandbox.mm>`
|
|
handle the new `MacSandboxType` in
|
|
|
|
> - `MacSandboxInfo::AppendAsParams()` in the {searchfox}`switch statement <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/mac/Sandbox.mm#164-188>`
|
|
> - `StartMacSandbox()` in the {searchfox}`series of if/else statements <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/mac/Sandbox.mm#286-436>`.
|
|
> This code sets template values for the sandbox string rendering, and is
|
|
> running on the side of the main process.
|
|
> - `StartMacSandboxIfEnabled()` in this {searchfox}`switch statement <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/mac/Sandbox.mm#753-782>`.
|
|
> You might also need a `GetXXXSandboxParamsFromArgs()` that performs CLI
|
|
> parsing on behalf of `StartMacSandbox()`.
|
|
|
|
- Create the new sandbox definition file
|
|
`security/sandbox/mac/SandboxPolicy<XXX>.h` for your new process `<XXX>`,
|
|
and make it exposed in the `EXPORTS.mozilla` section of {searchfox}`moz.build <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/mac/moz.build#7-13>`.
|
|
Those rules follows a specific Scheme-like language. You can learn more about
|
|
it in [Apple Sandbox Guide](https://reverse.put.as/wp-content/uploads/2011/09/Apple-Sandbox-Guide-v1.0.pdf)
|
|
as well as on your system within `/System/Library/Sandbox/Profiles/`.
|
|
|
|
###### Windows Sandbox
|
|
|
|
- Introduce a new `SandboxBroker::SetSecurityLevelForXXXProcess()` that
|
|
defines the new sandbox in the sandbox broker basing yourself on this
|
|
{searchfox}`example <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp#1241-1344>`
|
|
- Add new case handling in `WindowsProcessLauncher::DoSetup()` calling
|
|
`SandboxBroker::SetSecurityLevelForXXXProcess()` in {searchfox}`GeckoChildProcessHost <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:ipc/glue/GeckoChildProcessHost.cpp#1391-1470>`.
|
|
This will apply actual sandboxing rules to your process.
|
|
|
|
###### Sandbox tests
|
|
|
|
- New process' first top level actor needs to {searchfox}`include PSandboxTesting <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/common/test/PSandboxTesting.ipdl>`
|
|
and implement `RecvInitSandboxTesting` {searchfox}`like there <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:ipc/glue/UtilityProcessChild.cpp#165-174>`.
|
|
- Add your new process `string_name` in the `processTypes` list of {searchfox}`sandbox tests <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/test/browser_sandbox_test.js#17>`
|
|
- Add a new case in `SandboxTest::StartTests()` in {searchfox}`test core <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/common/test/SandboxTest.cpp#100-232>`
|
|
to handle your new process
|
|
- Add a new if branch for your new process in `SandboxTestingChild::Bind()`
|
|
in {searchfox}`testing child <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/common/test/SandboxTestingChild.cpp#68-96>`
|
|
- Add a new `RunTestsXXX` function for your new process (called by `Bind()`
|
|
above) {searchfox}`similar to that implementation <mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854:security/sandbox/common/test/SandboxTestingChildTests.h#333-363>`
|
|
|
|
### Creating the New Process
|
|
|
|
The sample does this in `DemoParent::LaunchDemoProcess`. The core
|
|
behavior is fairly clear:
|
|
|
|
```cpp
|
|
/* static */
|
|
bool DemoParent::LaunchDemoProcess(
|
|
base::ProcessId aParentPid, LaunchDemoProcessResolver&& aResolver) {
|
|
UniqueHost host(new Host(aParentPid, std::move(aResolver)));
|
|
|
|
// Prepare "command line" startup args for new process
|
|
std::vector<std::string> extraArgs;
|
|
if (!host->BuildProcessArgs(&extraArgs)) {
|
|
return false;
|
|
}
|
|
|
|
// Async launch creates a promise that we use below.
|
|
if (!host->AsyncLaunch(extraArgs)) {
|
|
return false;
|
|
}
|
|
|
|
host->WhenProcessHandleReady()->Then(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[host = std::move(host)](
|
|
const ipc::ProcessHandlePromise::ResolveOrRejectValue&
|
|
aResult) mutable {
|
|
if (aResult.IsReject()) {
|
|
host->ResolveAsFailure();
|
|
return;
|
|
}
|
|
|
|
auto actor = MakeRefPtr<DemoParent>(std::move(host));
|
|
actor->Init();
|
|
});
|
|
}
|
|
```
|
|
|
|
First, it creates an object of our `GeckoChildProcessHost` subclass (storing
|
|
some stuff for later). `GeckoChildProcessHost` is a base class that
|
|
abstracts the system-level operations involved in launching the new process.
|
|
It is the most substantive part of the launch procedure. After its
|
|
construction, the code prepares a bunch of strings to pass on the "command
|
|
line", which is the only way to pass data to the new process before IPDL is
|
|
established. All new processes will at least include `-parentBuildId` for
|
|
validating that dynamic libraries are properly versioned, and shared memory for
|
|
passing user preferences, which can affect early process behavior. Finally, it
|
|
tells `GeckoChildProcessHost` to asynchronously launch the process and run
|
|
the given lambda when it has a result. The lambda creates `DemoParent` with
|
|
the new host, if successful.
|
|
|
|
In this sample, the `DemoParent` is owned (in the reference-counting sense)
|
|
by IPDL, which is why it doesn't get assigned to anything. This simplifies the
|
|
design dramatically. IPDL takes ownership when the actor calls `Bind` from
|
|
the `Init` method:
|
|
|
|
```cpp
|
|
DemoParent::DemoParent(UniqueHost&& aHost)
|
|
: mHost(std::move(aHost)) {}
|
|
|
|
DemoParent::Init() {
|
|
mHost->TakeInitialEndpoint().Bind(this);
|
|
// ...
|
|
mHost->MakeBridgeAndResolve();
|
|
}
|
|
```
|
|
|
|
After the `Bind` call, the actor is live and communication with the new
|
|
process can begin. The constructor concludes by initiating the process of
|
|
connecting the `PDemoHelpline` actors; `Host::MakeBridgeAndResolve` will be
|
|
covered in [Creating a New Top Level Actor](#creating-a-new-top-level-actor). However, before we get into
|
|
that, we should finish defining the lifecycle of the process. In the next
|
|
section we look at launching the new process from the new process' perspective.
|
|
|
|
:::{warning}
|
|
The code could have chosen to create a `DemoChild` instead of a
|
|
`DemoParent` and the choice may seem cosmetic but it has substantial
|
|
implications that could affect browser stability. The most
|
|
significant is that the prohibitibition on synchronous IPDL messages going
|
|
from parent to child can no longer guarantee freedom from multiprocess
|
|
deadlock.
|
|
:::
|
|
|
|
### Initializing the New Process
|
|
|
|
The new process first adopts the **Demo** process type in
|
|
`XRE_InitChildProcess`, where it responds to the **Demo** values we added to
|
|
some enums above. Specifically, we need to choose the type of MessageLoop our
|
|
main thread will run (this is discussed later) and we need to create our
|
|
`ProcessChild` subclass. This is not an insignificant choice so pay close
|
|
attention to the `MessageLoop` options:
|
|
|
|
```cpp
|
|
MessageLoop::Type uiLoopType;
|
|
switch (XRE_GetProcessType()) {
|
|
case GeckoProcessType_Demo:
|
|
uiLoopType = MessageLoop::TYPE_MOZILLA_CHILD; break;
|
|
// ...
|
|
}
|
|
|
|
// ...
|
|
|
|
UniquePtr<ProcessChild> process;
|
|
switch (XRE_GetProcessType()) {
|
|
// ...
|
|
case GeckoProcessType_Demo:
|
|
process = MakeUnique<DemoChild::Process>(parentPID);
|
|
break;
|
|
}
|
|
```
|
|
|
|
We then need to create our singleton `DemoChild` object, which can occur in
|
|
the constructor or the `Process::Init()` call, which is common. We store a
|
|
strong reference to the actor (as does IPDL) so that we are guaranteed that it
|
|
exists as long as the `ProcessChild` does -- although the message channel may
|
|
be closed. We will release the reference either when the process is properly
|
|
shutting down or when an IPC error closes the channel.
|
|
|
|
`Init` is given the command line arguments constructed above so it will need
|
|
to be overridden to parse them. It does this, binds our actor by
|
|
calling `Bind` as was done with the parent, then initializes a bunch of
|
|
components that the process expects to use:
|
|
|
|
```cpp
|
|
bool DemoChild::Init(int aArgc, char* aArgv[]) {
|
|
#if defined(MOZ_SANDBOX) && defined(XP_WIN)
|
|
mozilla::SandboxTarget::Instance()->StartSandbox();
|
|
#elif defined(__OpenBSD__) && defined(MOZ_SANDBOX)
|
|
StartOpenBSDSandbox(GeckoProcessType_Demo);
|
|
#endif
|
|
|
|
if (!mozilla::ipc::ProcessChild::InitPrefs(aArgc, aArgv)) {
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) {
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!TakeInitialEndpoint().Bind(this))) {
|
|
return false;
|
|
}
|
|
|
|
// ... initializing components ...
|
|
|
|
if (NS_FAILED(NS_InitMinimalXPCOM())) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
This is a slimmed down version of the real `Init` method. We see that it
|
|
establishes a sandbox (more on this later) and then reads the command line and
|
|
preferences that we sent from the main process. It then initializes the thread
|
|
manager, which is required by for the subsequent `Bind` call.
|
|
|
|
Among the list of components we initialize in the sample code, XPCOM is
|
|
special. XPCOM includes a suite of components, including the component
|
|
manager, and is usually required for serious Gecko development. It is also
|
|
heavyweight and should be avoided if possible. We will leave the details of
|
|
XPCOM development to that module but we mention XPCOM configuration that is
|
|
special to new processes, namely `ProcessSelector`. `ProcessSelector`
|
|
is used to determine what process types have access to what XPCOM components.
|
|
By default, a process has access to none. The code adds enums for selecting
|
|
a subset of process types, like
|
|
`ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_DEMO_PROCESS`, to the
|
|
`ProcessSelector` enum in {searchfox}`gen_static_components.py <xpcom/components/gen_static_components.py>`
|
|
and {searchfox}`Module.h <xpcom/components/Module.h>`.
|
|
It then updates the selectors in various `components.conf` files and
|
|
hardcoded spots like `nsComponentManager.cpp` to add the **Demo** processes
|
|
to the list that can use them. Some modules are required to bootstrap XPCOM
|
|
and will cause it to fail to initialize if they are not permitted.
|
|
|
|
At this point, the new process is idle, waiting for messages from the main
|
|
process that will start the `PDemoHelpline` actor. We discuss that in
|
|
[Creating a New Top Level Actor](#creating-a-new-top-level-actor) below but, first, let's look at how the main
|
|
and **Demo** processes will handle clean destruction.
|
|
|
|
### Destroying the New Process
|
|
|
|
Gecko processes have a clean way for clients to request that they shutdown.
|
|
Simply calling `Close()` on the top level actor at either endpoint will begin
|
|
the shutdown procedure (so, `PDemoParent::Close` or `PDemoChild::Close`).
|
|
The only other way for a child process to terminate is to crash. Each of these
|
|
three options requires some special handling.
|
|
|
|
:::{note}
|
|
There is no need to consider the case where the parent (main) process
|
|
crashed, because the **Demo** process would be quickly terminated by Gecko.
|
|
:::
|
|
|
|
In cases where `Close()` is called, the shutdown procedure is fairly
|
|
straightforward. Once the call completes, the actor is no longer connected to
|
|
a channel -- messages will not be sent or received, as is the case with any
|
|
normal top-level actor (or any managed actor after calling
|
|
`Send__delete__()`). In the sample code, we `Close` the `DemoChild`
|
|
when some (as yet unwritten) **Demo** process code calls
|
|
`DemoChild::Shutdown`.
|
|
|
|
```cpp
|
|
/* static */
|
|
void DemoChild::Shutdown() {
|
|
if (gDemoChild) {
|
|
// Wait for the other end to get everything we sent before shutting down.
|
|
// We never want to Close during a message (response) handler, so
|
|
// we dispatch a new runnable.
|
|
auto dc = gDemoChild;
|
|
RefPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
|
|
"DemoChild::FinishShutdown",
|
|
[dc2 = std::move(gDemoChild)]() { dc2->Close(); });
|
|
dc->SendEmptyMessageQueue(
|
|
[runnable](bool) { NS_DispatchToMainThread(runnable); },
|
|
[runnable](mozilla::ipc::ResponseRejectReason) {
|
|
NS_DispatchToMainThread(runnable);
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
The comment in the code makes two important points:
|
|
|
|
- `Close` should never be called from a message handler (e.g. in a
|
|
`RecvFoo` method). We schedule it to run later.
|
|
- If the `DemoParent` hasn't finished handling messages the `DemoChild`
|
|
sent, or vice-versa, those messages will be lost. For that reason, we have a
|
|
trivial sentinel message `EmptyMessageQueue` that we simply send and wait
|
|
to respond before we `Close`. This guarantees that the main process will
|
|
have handled all of the messages we sent before it. Because we know the
|
|
details of the `PDemo` protocol, we know that this means we won't lose any
|
|
important messages this way. Note that we say "important" messages because
|
|
we could still lose messages sent *from* the main process. For example, a
|
|
`RequestMemoryReport` message sent by the MemoryReporter could be lost.
|
|
The actor would need a more complex shutdown protocol to catch all of these
|
|
messages but in our case there would be no point. A process that is
|
|
terminating is probably not going to produce useful memory consumption data.
|
|
Those messages can safely be lost.
|
|
|
|
[Debugging Process Startup](#debugging-process-startup) looks at what happens if we omit the
|
|
`EmptyMessageQueue` message.
|
|
|
|
We can also see that, once the `EmptyMessageQueue` response is run, we are
|
|
releasing `gDemoChild`, which will result in the termination of the process.
|
|
|
|
```cpp
|
|
DemoChild::~DemoChild() {
|
|
// ...
|
|
XRE_ShutdownChildProcess();
|
|
}
|
|
```
|
|
|
|
At this point, the `DemoParent` in the main process is alerted to the
|
|
channel closure because IPDL will call its
|
|
{ref}`ActorDestroy <actor-lifetimes-in-c>` method.
|
|
|
|
```cpp
|
|
void DemoParent::ActorDestroy(ActorDestroyReason aWhy) {
|
|
if (aWhy == AbnormalShutdown) {
|
|
GenerateCrashReport(OtherPid());
|
|
}
|
|
// ...
|
|
}
|
|
```
|
|
|
|
IPDL then releases its (sole) reference to `DemoParent` and the destruction
|
|
of the process apparatus is complete.
|
|
|
|
The `ActorDestroy` code shows how we handle the one remaining shutdown case:
|
|
a crash in the **Demo** process. In this case, IPDL will *detect* the dead
|
|
process and free the `DemoParent` actor as above, only with an
|
|
`AbnormalShutdown` reason. We generate a crash report, which requires crash
|
|
reporter integration, but no additional "special" steps need to be taken.
|
|
|
|
### Creating a New Top Level Actor
|
|
|
|
We now have a framework that creates the new process and connects it to the
|
|
main process. We now want to make another top-level actor but this one will be
|
|
responsible for our intended behavior, not just bootstrapping the new process.
|
|
Above, we saw that this is started by `Host::MakeBridgeAndResolve` after the
|
|
`DemoParent` connection is established.
|
|
|
|
```cpp
|
|
bool DemoParent::Host::MakeBridgeAndResolve() {
|
|
ipc::Endpoint<PDemoHelplineParent> parent;
|
|
ipc::Endpoint<PDemoHelplineChild> child;
|
|
|
|
auto resolveFail = MakeScopeExit([&] { mResolver(Nothing()); });
|
|
|
|
// Parent side is first argument (main/content), child is second (demo).
|
|
nsresult rv = PDempHelpline::CreateEndpoints(&parent, &child);
|
|
|
|
// ...
|
|
|
|
if (!mActor->SendCreateDemoHelplineChild(std::move(child))) {
|
|
NS_WARNING("Failed to SendCreateDemoHelplineChild");
|
|
return false;
|
|
}
|
|
|
|
resolveFail.release();
|
|
mResolver(Some(std::move(parent)));
|
|
return true;
|
|
}
|
|
```
|
|
|
|
Because the operation of launching a process is asynchronous, we have
|
|
configured this so that it creates the two endpoints for the new top-level
|
|
actors, then we send the child one to the new process and resolve a promise
|
|
with the other. The **Demo** process creates its `PDemoHelplineChild`
|
|
easily:
|
|
|
|
```cpp
|
|
mozilla::ipc::IPCResult DemoChild::RecvCreateDemoHelplineChild(
|
|
Endpoint<PDemoHelplineChild>&& aEndpoint) {
|
|
mDemoHelplineChild = new DemoHelplineChild();
|
|
if (!aEndpoint.Bind(mDemoHelplineChild)) {
|
|
return IPC_FAIL(this, "Unable to bind DemoHelplineChild");
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
```
|
|
|
|
`MakeProcessAndGetAssistance` binds the same way:
|
|
|
|
```cpp
|
|
RefPtr<DemoHelplineParent> demoHelplineParent = new DemoHelplineParent();
|
|
if (!endpoint.Bind(demoHelplineParent)) {
|
|
NS_WARNING("Unable to bind DemoHelplineParent");
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(ok);
|
|
```
|
|
|
|
However, the parent may be in the main process or in content. We handle both
|
|
cases in the next section.
|
|
|
|
(connecting-with-other-processes)=
|
|
|
|
### Connecting With Other Processes
|
|
|
|
`DemoHelplineParent::MakeProcessAndGetAssistance` is the method that we run
|
|
from either the main or the content process and that should kick off the
|
|
procedure that will result in sending a string (that we get from a new **Demo**
|
|
process) to a DOM promise. It starts by constructing a different promise --
|
|
one like the `mResolver` in `Host::MakeBridgeAndResolve` in the last
|
|
section that produced a `Maybe<Endpoint<PDemoHelplineParent>>`. In the main
|
|
process, we just make the promise ourselves and call
|
|
`DemoParent::LaunchDemoProcess` to start the procedure that will result in
|
|
it being resolved as already described. If we are calling from the content
|
|
process, we simply write an async `PContent` message that calls
|
|
`DemoParent::LaunchDemoProcess` and use the message handler's promise as
|
|
our promise:
|
|
|
|
```cpp
|
|
/* static */
|
|
bool DemoHelplineParent::MakeProcessAndGetAssistance(
|
|
RefPtr<mozilla::dom::Promise> aPromise) {
|
|
RefPtr<LaunchDemoProcessPromise> resolver;
|
|
|
|
if (XRE_IsContentProcess()) {
|
|
auto* contentChild = mozilla::dom::ContentChild::GetSingleton();
|
|
MOZ_ASSERT(contentChild);
|
|
|
|
resolver = contentChild->SendLaunchDemoProcess();
|
|
} else {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
auto promise = MakeRefPtr<LaunchDemoProcessPromise::Private>(__func__);
|
|
resolver = promise;
|
|
|
|
if (!DemoParent::LaunchDemoProcess(
|
|
base::GetCurrentProcId(),
|
|
[promise = std::move(promise)](
|
|
Maybe<Endpoint<PDemoHelplineParent>>&& aMaybeEndpoint) mutable {
|
|
promise->Resolve(std::move(aMaybeEndpoint), __func__);
|
|
})) {
|
|
NS_WARNING("Failed to launch Demo process");
|
|
resolver->Reject(NS_ERROR_FAILURE);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
resolver->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[aPromise](Maybe<Endpoint<PDemoHelplineParent>>&& maybeEndpoint) mutable {
|
|
if (!maybeEndpoint) {
|
|
aPromise->MaybeReject(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
RefPtr<DemoHelplineParent> demoHelplineParent = new DemoHelplineParent();
|
|
Endpoint<PDemoHelplineParent> endpoint = maybeEndpoint.extract();
|
|
if (!endpoint.Bind(demoHelplineParent)) {
|
|
NS_WARNING("Unable to bind DemoHelplineParent");
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(ok);
|
|
|
|
// ... communicate with PDemoHelpline and write message to console ...
|
|
},
|
|
[aPromise](mozilla::ipc::ResponseRejectReason&& aReason) {
|
|
aPromise->MaybeReject(NS_ERROR_FAILURE);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
mozilla::ipc::IPCResult ContentParent::RecvLaunchDemoProcess(
|
|
LaunchDemoProcessResolver&& aResolver) {
|
|
if (!DemoParent::LaunchDemoProcess(OtherPid(),
|
|
std::move(aResolver))) {
|
|
NS_WARNING("Failed to launch Demo process");
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
```
|
|
|
|
To summarize, connecting processes always requires endpoints to be constructed
|
|
by the main process, even when neither process being connected is the main
|
|
process. It is the only process that creates `Endpoint` objects. From that
|
|
point, connecting is just a matter of sending the endpoints to the right
|
|
processes, constructing an actor for them, and then calling `Endpoint::Bind`.
|
|
|
|
### Completing the Sample
|
|
|
|
We have covered the main parts needed for the sample. Now we just need to wire
|
|
it all up. First, we add the new JS command to `Navigator.webidl` and
|
|
`Navigator.h`/`Navigator.cpp`:
|
|
|
|
```cpp
|
|
partial interface Navigator {
|
|
[Throws]
|
|
Promise<DOMString> getAssistance();
|
|
};
|
|
|
|
already_AddRefed<Promise> Navigator::GetAssistance(ErrorResult& aRv) {
|
|
if (!mWindow || !mWindow->GetDocShell()) {
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<Promise> echoPromise = Promise::Create(mWindow->AsGlobal(), aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!DemoHelplineParent::MakeProcessAndGetAssistance(echoPromise)) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return nullptr;
|
|
}
|
|
|
|
return echoPromise.forget();
|
|
}
|
|
```
|
|
|
|
Then, we need to add the part that gets the string we use to resolve the
|
|
promise in `MakeProcessAndGetAssistance` (or reject it if it hasn't been
|
|
resolved by the time `ActorDestroy` is called):
|
|
|
|
```cpp
|
|
using DemoPromise = MozPromise<nsString, nsresult, true>;
|
|
|
|
/* static */
|
|
bool DemoHelplineParent::MakeProcessAndGetAssistance(
|
|
RefPtr<mozilla::dom::Promise> aPromise) {
|
|
|
|
// ... construct and connect demoHelplineParent ...
|
|
|
|
RefPtr<DemoPromise> promise = demoHelplineParent->mPromise.Ensure(__func__);
|
|
promise->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[demoHelplineParent, aPromise](nsString aMessage) mutable {
|
|
aPromise->MaybeResolve(aMessage);
|
|
},
|
|
[demoHelplineParent, aPromise](nsresult aErr) mutable {
|
|
aPromise->MaybeReject(aErr);
|
|
});
|
|
|
|
if (!demoHelplineParent->SendRequestAssistance()) {
|
|
NS_WARNING("DemoHelplineParent::SendRequestAssistance failed");
|
|
}
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DemoHelplineParent::RecvAssistance(
|
|
nsString&& aMessage, const AssistanceResolver& aResolver) {
|
|
mPromise.Resolve(aMessage, __func__);
|
|
aResolver(true);
|
|
return IPC_OK();
|
|
}
|
|
|
|
void DemoHelplineParent::ActorDestroy(ActorDestroyReason aWhy) {
|
|
mPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
```
|
|
|
|
The `DemoHelplineChild` has to respond to the `RequestAssistance` method,
|
|
which it does by returning a string and then calling `Close` on itself when
|
|
the string has been received (but we do not call `Close` in the `Recv`
|
|
method!). We use an async response to the `GiveAssistance` message to detect
|
|
that the string was received. During closing, the actor's `ActorDestroy`
|
|
method then calls the `DemoChild::Shutdown` method we defined in [Destroying
|
|
the New Process](#destroying-the-new-process):
|
|
|
|
```cpp
|
|
mozilla::ipc::IPCResult DemoHelplineChild::RecvRequestAssistance() {
|
|
RefPtr<DemoHelplineChild> me = this;
|
|
RefPtr<nsIRunnable> runnable =
|
|
NS_NewRunnableFunction("DemoHelplineChild::Close", [me]() { me->Close(); });
|
|
|
|
SendAssistance(
|
|
nsString(HelpMessage()),
|
|
[runnable](bool) { NS_DispatchToMainThread(runnable); },
|
|
[runnable](mozilla::ipc::ResponseRejectReason) {
|
|
NS_DispatchToMainThread(runnable);
|
|
});
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
void DemoHelplineChild::ActorDestroy(ActorDestroyReason aWhy) {
|
|
DemoChild::Shutdown();
|
|
}
|
|
```
|
|
|
|
During the **Demo** process lifetime, there are two references to the
|
|
`DemoHelplineChild`, one from IPDL and one from the `DemoChild`. The call
|
|
to `Close` releases the one held by IPDL and the other isn't released until
|
|
the `DemoChild` is destroyed.
|
|
|
|
### Running the Sample
|
|
|
|
To run the sample, build and run and open the console. The new command is
|
|
`navigator.getAssistance().then(console.log)`. The message sent by
|
|
`SendAssistance` is then logged to the console. The sample code also
|
|
includes the name of the type of process that was used for the
|
|
`DemoHelplineParent` so you can confirm that it works from main and from
|
|
content.
|
|
|
|
## Debugging Process Startup
|
|
|
|
Debugging a child process at the start of its life is tricky. With most
|
|
platforms/toolchains, it is surprisingly difficult to connect a debugger before
|
|
the main routine begins execution. You may also find that console logging is
|
|
not yet established by the operating system, especially when working with
|
|
sandboxed child processes. Gecko has some facilities that make this less
|
|
painful.
|
|
|
|
(debugging-with-ipdl-logging)=
|
|
|
|
### Debugging with IPDL Logging
|
|
|
|
This is also best seen with an example. To start, we can create a bug in the
|
|
sample by removing the `EmptyMessageQueue` message sent to `DemoParent`.
|
|
This message was intended to guarantee that the `DemoParent` had handled all
|
|
messages sent before it, so we could `Close` with the knowledge that we
|
|
didn't miss anything. This sort of bug can be very difficult to track down
|
|
because it is likely to be intermittent and may manifest more easily on some
|
|
platforms/architectures than others. To create this bug, replace the
|
|
`SendEmptyMessageQueue` call in `DemoChild::Shutdown`:
|
|
|
|
```cpp
|
|
auto dc = gDemoChild;
|
|
RefPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
|
|
"DemoChild::FinishShutdown",
|
|
[dc2 = std::move(gDemoChild)]() { dc2->Close(); });
|
|
dc->SendEmptyMessageQueue(
|
|
[runnable](bool) { NS_DispatchToMainThread(runnable); },
|
|
[runnable](mozilla::ipc::ResponseRejectReason) {
|
|
NS_DispatchToMainThread(runnable);
|
|
});
|
|
```
|
|
|
|
with just an (asynchronous) call to `Close`:
|
|
|
|
```cpp
|
|
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
|
"DemoChild::FinishShutdown",
|
|
[dc = std::move(gDemoChild)]() { dc->Close(); }));
|
|
```
|
|
|
|
When we run the sample now, everything seems to behave ok but we see messages
|
|
like these in the console:
|
|
|
|
```
|
|
###!!! [Parent][RunMessage] Error: (msgtype=0x410001,name=PDemo::Msg_InitCrashReporter) Channel closing: too late to send/recv, messages will be lost
|
|
|
|
[Parent 16672, IPC I/O Parent] WARNING: file c:/mozilla-src/mozilla-unified/ipc/chromium/src/base/process_util_win.cc:167
|
|
[Parent 16672, Main Thread] WARNING: Not resolving response because actor is dead.: file c:/mozilla-src/mozilla-unified/ipc/glue/ProtocolUtils.cpp:931
|
|
[Parent 16672, Main Thread] WARNING: IPDL resolver dropped without being called!: file c:/mozilla-src/mozilla-unified/ipc/glue/ProtocolUtils.cpp:959
|
|
```
|
|
|
|
We could probably figure out what is happening here from the messages but,
|
|
with more complex protocols, understanding what led to this may not be so easy.
|
|
To begin diagnosing, we can turn on IPC Logging, which was defined in the IPDL
|
|
section on {ref}`message-logging`. We just need to set an environment variable
|
|
before starting the browser. Let's turn it on for all `PDemo` and
|
|
`PDemoHelpline` actors:
|
|
|
|
```
|
|
MOZ_IPC_MESSAGE_LOG="PDemo,PDemoHelpline"
|
|
```
|
|
|
|
To underscore what we said above, when logging is active, the change in timing
|
|
makes the error message go away and everything closes properly on a tested
|
|
Windows desktop. However, the issue remains on a Macbook Pro and the log
|
|
shows the issue rather clearly:
|
|
|
|
```
|
|
[time: 1627075553937959][63096->63085] [PDemoChild] Sending PDemo::Msg_InitCrashReporter
|
|
[time: 1627075553949441][63085->63096] [PDemoParent] Sending PDemo::Msg_CreateDemoHelplineChild
|
|
[time: 1627075553950293][63092->63096] [PDemoHelplineParent] Sending PDemoHelpline::Msg_RequestAssistance
|
|
[time: 1627075553979151][63096<-63085] [PDemoChild] Received PDemo::Msg_CreateDemoHelplineChild
|
|
[time: 1627075553979433][63096<-63092] [PDemoHelplineChild] Received PDemoHelpline::Msg_RequestAssistance
|
|
[time: 1627075553979498][63096->63092] [PDemoHelplineChild] Sending PDemoHelpline::Msg_GiveAssistance
|
|
[time: 1627075553980105][63092<-63096] [PDemoHelplineParent] Received PDemoHelpline::Msg_GiveAssistance
|
|
[time: 1627075553980181][63092->63096] [PDemoHelplineParent] Sending reply PDemoHelpline::Reply_GiveAssistance
|
|
[time: 1627075553980449][63096<-63092] [PDemoHelplineChild] Received PDemoHelpline::Reply_GiveAssistance
|
|
[tab 63092] NOTE: parent actor received `Goodbye' message. Closing channel.
|
|
[default 63085] NOTE: parent actor received `Goodbye' message. Closing channel.
|
|
[...]
|
|
###!!! [Parent][RunMessage] Error: (msgtype=0x420001,name=PDemo::Msg_InitCrashReporter) Channel closing: too late to send/recv, messages will be lost
|
|
[...]
|
|
[default 63085] NOTE: parent actor received `Goodbye' message. Closing channel.
|
|
```
|
|
|
|
The imbalance with `Msg_InitCrashReporter` is clear. The message was not
|
|
*Received* before the channel was closed. Note that the first `Goodbye` for
|
|
the main (default) process is for the `PDemoHelpline` actor -- in this case,
|
|
its child actor was in a content (tab) process. The second default process
|
|
`Goodbye` is from the **Demo** process, sent when doing `Close`. It might
|
|
seem that it should handle the `Msg_InitCrashReporter` if it can handle the
|
|
later `Goodbye` but this does not happen for safety reasons.
|
|
|
|
### Early Debugging For A New Process
|
|
|
|
Let's assume now that we still don't understand the problem -- maybe we don't
|
|
know that the `InitCrashReporter` message is sent internally by the
|
|
`CrashReporterClient` we initialized. Or maybe we're only looking at Windows
|
|
builds. We decide we'd like to be able to hook a debugger to the new process
|
|
so that we can break on the `SendInitCrashReporter` call. Attaching the
|
|
debugger has to happen fast -- process startup probably completes in under a
|
|
second. Debugging this is not always easy.
|
|
|
|
Windows users have options that work with both the Visual Studio and WinDbg
|
|
debuggers. For Visual Studio users, there is an easy-to-use VS addon called
|
|
the [Child Process Debugging Tool] that allows you to connect to *all*
|
|
processes that are launched by a process you are debugging. So, if the VS
|
|
debugger is connected to the main process, it will automatically connect to the
|
|
new **Demo** process (and every other launched process) at the point that they
|
|
are spawned. This way, the new process never does anything outside of the
|
|
debugger. Breakpoints, etc work as expected. The addon mostly works like a
|
|
toggle and will remain on until it is disabled from the VS menu.
|
|
|
|
WinDbg users can achieve essentially the same behavior with the [.childdbg]
|
|
command. See the docs for details but essentially all there is to know is that
|
|
`.childdbg 1` enables it and `.childdbg 0` disables it. You might add it
|
|
to a startup config file (see the WinDbg `-c` command line option)
|
|
|
|
Linux and mac users should reference gdb's `detach-on-fork`. The command to
|
|
debug child processes is `set detach-on-fork off`. Again, the behavior is
|
|
largely what you would expect -- that all spawned processes are added to the
|
|
current debug session. The command can be added to `.gdbinit` for ease. At
|
|
the time of this writing, lldb does not support automatically connecting to
|
|
newly spawned processes.
|
|
|
|
Finally, Linux users can use `rr` for time-travel debugging. See {ref}`Debugging
|
|
Firefox with rr <Debugging Firefox with rr>` for details.
|
|
|
|
These solutions are not always desirable. For example, the fact that they hook
|
|
*all* spawned processes can mean that targeting breakpoints to one process
|
|
requires us to manually disconnect many other processes. In these cases, an
|
|
easier solution may be to use Gecko environment variables that will cause the
|
|
process to sleep for some number of seconds. During that time, you can find
|
|
the process ID (PID) for the process you want to debug and connect your
|
|
debugger to it. OS tools like `ProcessMonitor` can give you the PID but it
|
|
will also be clearly logged to the console just before the process waits.
|
|
|
|
Set `MOZ_DEBUG_CHILD_PROCESS=1` to turn on process startup pausing. You can
|
|
also set `MOZ_DEBUG_CHILD_PAUSE=N` where N is the number of seconds to sleep.
|
|
The default is 10 seconds on Windows and 30 on other platforms.
|
|
|
|
Pausing for the debugger is not a panacea. Since the environmental variables
|
|
are not specific to process type, you will be forced to wait for all of the
|
|
processes Gecko creates before you wait for it to get to yours. The pauses can
|
|
also end up exposing unknown concurrency bugs in the browser before it even
|
|
gets to your issue, which is good to discover but doesn't fix your bug. That
|
|
said, any of these strategies would be enough to facilitate easily breaking on
|
|
`SendInitCrashReporter` and finding our sender.
|
|
|
|
[.childdbg]: https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-childdbg--debug-child-processes-
|
|
[child process debugging tool]: https://marketplace.visualstudio.com/items?itemName=vsdbgplat.MicrosoftChildProcessDebuggingPowerTool
|
|
[launcher process]: https://wiki.mozilla.org/Platform/Integration/InjectEject/Launcher_Process/
|