summaryrefslogtreecommitdiffstats
path: root/b2g/components/SafeMode.jsm
blob: 9f9342f679e54377ecc73dcf3bb0e01311ed738a (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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/* 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/. */

"use strict";

this.EXPORTED_SYMBOLS = ["SafeMode"];

const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");

const kSafeModePref = "b2g.safe_mode";
const kSafeModePage = "safe_mode.html";

function debug(aStr) {
  //dump("-*- SafeMode: " + aStr + "\n");
}

// This module is responsible for checking whether we want to start in safe
// mode or not. The flow is as follow:
// - wait for the `b2g.safe_mode` preference to be set to something different
//   than `unset` by nsAppShell
// - If it's set to `no`, just start normally.
// - If it's set to `yes`, we load a stripped down system app from safe_mode.html"
//   - This page is responsible to dispatch a mozContentEvent to us.
//   - If the user choose SafeMode, we disable all add-ons.
//   - We go on with startup.

this.SafeMode = {
  // Returns a promise that resolves when nsAppShell has set the
  // b2g.safe_mode_state_ready preference to `true`.
  _waitForPref: function() {
    debug("waitForPref");
    try {
      let currentMode = Services.prefs.getCharPref(kSafeModePref);
      debug("current mode: " + currentMode);
      if (currentMode !== "unset") {
        return Promise.resolve();
      }
    } catch(e) { debug("No current mode available!"); }

    // Wait for the preference to toggle.
    return new Promise((aResolve, aReject) => {
      let observer = function(aSubject, aTopic, aData) {
        if (Services.prefs.getCharPref(kSafeModePref)) {
          Services.prefs.removeObserver(kSafeModePref, observer, false);
          aResolve();
        }
      }

      Services.prefs.addObserver(kSafeModePref, observer, false);
    });
  },

  // Resolves once the user has decided how to start.
  // Note that all the actions happen here, so there is no other action from
  // consumers than to go on.
  _waitForUser: function() {
    debug("waitForUser");
    let isSafeMode = Services.prefs.getCharPref(kSafeModePref) === "yes";
    if (!isSafeMode) {
      return Promise.resolve();
    }
    debug("Starting in Safe Mode!");

    // Load $system_app/safe_mode.html as a full screen iframe, and wait for
    // the user to make a choice.
    let shell = SafeMode.window.shell;
    let document = SafeMode.window.document;
    SafeMode.window.screen.mozLockOrientation("portrait");

    let url = Services.io.newURI(shell.homeURL, null, null)
                         .resolve(kSafeModePage);
    debug("Registry is ready, loading " + url);
    let frame = document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
    frame.setAttribute("mozbrowser", "true");
    frame.setAttribute("mozapp", shell.manifestURL);
    frame.setAttribute("id", "systemapp"); // To keep screen.js happy.
    let contentBrowser = document.body.appendChild(frame);

    return new Promise((aResolve, aReject) => {
      let content = contentBrowser.contentWindow;

      // Stripped down version of the system app bootstrap.
      function handleEvent(e) {
        switch(e.type) {
          case "mozbrowserloadstart":
            if (content.document.location == "about:blank") {
              contentBrowser.addEventListener("mozbrowserlocationchange", handleEvent, true);
              contentBrowser.removeEventListener("mozbrowserloadstart", handleEvent, true);
              return;
            }

            notifyContentStart();
            break;
          case "mozbrowserlocationchange":
            if (content.document.location == "about:blank") {
              return;
            }

            contentBrowser.removeEventListener("mozbrowserlocationchange", handleEvent, true);
            notifyContentStart();
            break;
          case "mozContentEvent":
            content.removeEventListener("mozContentEvent", handleEvent, true);
            contentBrowser.parentNode.removeChild(contentBrowser);

            if (e.detail == "safemode-yes")  {
              // Really starting in safe mode, let's disable add-ons first.
              // TODO: disable add-ons
              aResolve();
            } else {
              aResolve();
            }
            break;
        }
      }

      function notifyContentStart() {
        let window = SafeMode.window;
        window.shell.sendEvent(window, "SafeModeStart");
        contentBrowser.setVisible(true);

        // browser-ui-startup-complete is used by the AppShell to stop the
        // boot animation and start gecko rendering.
        Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
        content.addEventListener("mozContentEvent", handleEvent, true);
      }

      contentBrowser.addEventListener("mozbrowserloadstart", handleEvent, true);
      contentBrowser.src = url;
    });
  },

  // Returns a Promise that resolves once we have decided to run in safe mode
  // or not. All the safe mode switching actions happen before resolving the
  // promise.
  check: function(aWindow) {
    debug("check");
    this.window = aWindow;
    if (AppConstants.platform !== "gonk") {
      // For now we only have gonk support.
      return Promise.resolve();
    }

    return this._waitForPref().then(this._waitForUser);
  }
}