summaryrefslogtreecommitdiffstats
path: root/mobile/android/modules/geckoview/ChildCrashHandler.sys.mjs
blob: 5c6418091a24ce118342f8f48fe9ed1304eb39ab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
});

const { debug, warn } = GeckoViewUtils.initLogging("ChildCrashHandler");

function getDir(name) {
  const uAppDataPath = Services.dirsvc.get("UAppData", Ci.nsIFile).path;
  return PathUtils.join(uAppDataPath, "Crash Reports", name);
}

function getPendingMinidump(id) {
  const pendingDir = getDir("pending");

  return [".dmp", ".extra"].map(suffix => {
    return PathUtils.join(pendingDir, `${id}${suffix}`);
  });
}

export var ChildCrashHandler = {
  // Map a child ID to a remote type.
  childMap: new Map(),

  // The event listener for this is hooked up in GeckoViewStartup.sys.mjs
  observe(aSubject, aTopic, aData) {
    const childID = aData;

    switch (aTopic) {
      case "process-type-set":
      // Intentional fall-through
      case "ipc:content-created": {
        const pp = aSubject.QueryInterface(Ci.nsIDOMProcessParent);
        this.childMap.set(childID, pp.remoteType);
        break;
      }

      case "ipc:content-shutdown":
      // Intentional fall-through
      case "compositor:process-aborted": {
        aSubject.QueryInterface(Ci.nsIPropertyBag2);

        const disableReporting = Services.env.get(
          "MOZ_CRASHREPORTER_NO_REPORT"
        );

        if (
          !aSubject.get("abnormal") ||
          !AppConstants.MOZ_CRASHREPORTER ||
          disableReporting
        ) {
          return;
        }

        // If dumpID is empty the process was likely killed by the system and
        // we therefore do not want to report the crash. This includes most
        // "expected" extensions process crashes on Android.
        const dumpID = aSubject.get("dumpID");
        if (!dumpID) {
          Services.telemetry
            .getHistogramById("FX_CONTENT_CRASH_DUMP_UNAVAILABLE")
            .add(1);
          return;
        }

        debug`Notifying child process crash, dump ID ${dumpID}`;
        const [minidumpPath, extrasPath] = getPendingMinidump(dumpID);

        let remoteType = this.childMap.get(childID);
        this.childMap.delete(childID);

        if (remoteType?.length) {
          // Only send the remote type prefix since everything after a "=" is
          // dynamic, and used to control the process pool to use.
          remoteType = remoteType.split("=")[0];
        }

        // Report GPU and extension process crashes as occuring in a background
        // process, and others as foreground.
        const processType =
          aTopic === "compositor:process-aborted" || remoteType === "extension"
            ? "BACKGROUND_CHILD"
            : "FOREGROUND_CHILD";

        lazy.EventDispatcher.instance.sendRequest({
          type: "GeckoView:ChildCrashReport",
          minidumpPath,
          extrasPath,
          success: true,
          fatal: false,
          processType,
          remoteType,
        });

        break;
      }
    }
  },
};