diff -r 000000000000 -r 6474c204b198 b2g/chrome/content/shell.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/b2g/chrome/content/shell.js Wed Dec 31 06:09:35 2014 +0100
@@ -0,0 +1,1618 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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/. */
+
+Cu.import('resource://gre/modules/ContactService.jsm');
+Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm');
+Cu.import('resource://gre/modules/DataStoreChangeNotifier.jsm');
+Cu.import('resource://gre/modules/AlarmService.jsm');
+Cu.import('resource://gre/modules/ActivitiesService.jsm');
+Cu.import('resource://gre/modules/PermissionPromptHelper.jsm');
+Cu.import('resource://gre/modules/NotificationDB.jsm');
+Cu.import('resource://gre/modules/Payment.jsm');
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+Cu.import('resource://gre/modules/UserAgentOverrides.jsm');
+Cu.import('resource://gre/modules/Keyboard.jsm');
+Cu.import('resource://gre/modules/ErrorPage.jsm');
+#ifdef MOZ_WIDGET_GONK
+Cu.import('resource://gre/modules/NetworkStatsService.jsm');
+#endif
+
+// Identity
+Cu.import('resource://gre/modules/SignInToWebsite.jsm');
+SignInToWebsiteController.init();
+
+#ifdef MOZ_SERVICES_FXACCOUNTS
+Cu.import('resource://gre/modules/FxAccountsMgmtService.jsm');
+#endif
+
+Cu.import('resource://gre/modules/DownloadsAPI.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+ "resource://gre/modules/SystemAppProxy.jsm");
+
+Cu.import('resource://gre/modules/Webapps.jsm');
+DOMApplicationRegistry.allAppsLaunchable = true;
+
+XPCOMUtils.defineLazyServiceGetter(Services, 'env',
+ '@mozilla.org/process/environment;1',
+ 'nsIEnvironment');
+
+XPCOMUtils.defineLazyServiceGetter(Services, 'ss',
+ '@mozilla.org/content/style-sheet-service;1',
+ 'nsIStyleSheetService');
+
+XPCOMUtils.defineLazyServiceGetter(this, 'gSystemMessenger',
+ '@mozilla.org/system-message-internal;1',
+ 'nsISystemMessagesInternal');
+
+XPCOMUtils.defineLazyServiceGetter(Services, 'fm',
+ '@mozilla.org/focus-manager;1',
+ 'nsIFocusManager');
+
+XPCOMUtils.defineLazyGetter(this, 'DebuggerServer', function() {
+ Cu.import('resource://gre/modules/devtools/dbg-server.jsm');
+ return DebuggerServer;
+});
+
+XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
+ return Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+});
+
+#ifdef MOZ_WIDGET_GONK
+XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
+ Cu.import("resource://gre/modules/systemlibs.js");
+ return libcutils;
+});
+#endif
+
+#ifdef MOZ_CAPTIVEDETECT
+XPCOMUtils.defineLazyServiceGetter(Services, 'captivePortalDetector',
+ '@mozilla.org/toolkit/captive-detector;1',
+ 'nsICaptivePortalDetector');
+#endif
+
+function getContentWindow() {
+ return shell.contentBrowser.contentWindow;
+}
+
+function debug(str) {
+ dump(' -*- Shell.js: ' + str + '\n');
+}
+
+#ifdef MOZ_CRASHREPORTER
+function debugCrashReport(aStr) {
+ dump('Crash reporter : ' + aStr);
+}
+#else
+function debugCrashReport(aStr) {}
+#endif
+
+var shell = {
+
+ get CrashSubmit() {
+ delete this.CrashSubmit;
+#ifdef MOZ_CRASHREPORTER
+ Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
+ return this.CrashSubmit;
+#else
+ dump('Crash reporter : disabled at build time.');
+ return this.CrashSubmit = null;
+#endif
+ },
+
+ onlineForCrashReport: function shell_onlineForCrashReport() {
+ let wifiManager = navigator.mozWifiManager;
+ let onWifi = (wifiManager &&
+ (wifiManager.connection.status == 'connected'));
+ return !Services.io.offline && onWifi;
+ },
+
+ reportCrash: function shell_reportCrash(isChrome, aCrashID) {
+ let crashID = aCrashID;
+ try {
+ // For chrome crashes, we want to report the lastRunCrashID.
+ if (isChrome) {
+ crashID = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime).lastRunCrashID;
+ }
+ } catch(e) {
+ debugCrashReport('Failed to fetch crash id. Crash ID is "' + crashID
+ + '" Exception: ' + e);
+ }
+
+ // Bail if there isn't a valid crashID.
+ if (!this.CrashSubmit || !crashID && !this.CrashSubmit.pendingIDs().length) {
+ return;
+ }
+
+ // purge the queue.
+ this.CrashSubmit.pruneSavedDumps();
+
+ // check for environment affecting crash reporting
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ let shutdown = env.get("MOZ_CRASHREPORTER_SHUTDOWN");
+ if (shutdown) {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eForceQuit);
+ }
+
+ let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+ if (noReport) {
+ return;
+ }
+
+ try {
+ // Check if we should automatically submit this crash.
+ if (Services.prefs.getBoolPref('app.reportCrashes')) {
+ this.submitCrash(crashID);
+ } else {
+ this.deleteCrash(crashID);
+ }
+ } catch (e) {
+ debugCrashReport('Can\'t fetch app.reportCrashes. Exception: ' + e);
+ }
+
+ // We can get here if we're just submitting old pending crashes.
+ // Check that there's a valid crashID so that we only notify the
+ // user if a crash just happened and not when we OOM. Bug 829477
+ if (crashID) {
+ this.sendChromeEvent({
+ type: "handle-crash",
+ crashID: crashID,
+ chrome: isChrome
+ });
+ }
+ },
+
+ deleteCrash: function shell_deleteCrash(aCrashID) {
+ if (aCrashID) {
+ debugCrashReport('Deleting pending crash: ' + aCrashID);
+ shell.CrashSubmit.delete(aCrashID);
+ }
+ },
+
+ // this function submit the pending crashes.
+ // make sure you are online.
+ submitQueuedCrashes: function shell_submitQueuedCrashes() {
+ // submit the pending queue.
+ let pending = shell.CrashSubmit.pendingIDs();
+ for (let crashid of pending) {
+ debugCrashReport('Submitting crash: ' + crashid);
+ shell.CrashSubmit.submit(crashid);
+ }
+ },
+
+ // This function submits a crash when we're online.
+ submitCrash: function shell_submitCrash(aCrashID) {
+ if (this.onlineForCrashReport()) {
+ this.submitQueuedCrashes();
+ return;
+ }
+
+ debugCrashReport('Not online, postponing.');
+
+ Services.obs.addObserver(function observer(subject, topic, state) {
+ let network = subject.QueryInterface(Ci.nsINetworkInterface);
+ if (network.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED
+ && network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
+ shell.submitQueuedCrashes();
+
+ Services.obs.removeObserver(observer, topic);
+ }
+ }, "network-connection-state-changed", false);
+ },
+
+ get contentBrowser() {
+ delete this.contentBrowser;
+ return this.contentBrowser = document.getElementById('systemapp');
+ },
+
+ get homeURL() {
+ try {
+ let homeSrc = Services.env.get('B2G_HOMESCREEN');
+ if (homeSrc)
+ return homeSrc;
+ } catch (e) {}
+
+ return Services.prefs.getCharPref('browser.homescreenURL');
+ },
+
+ get manifestURL() {
+ return Services.prefs.getCharPref('browser.manifestURL');
+ },
+
+ _started: false,
+ hasStarted: function shell_hasStarted() {
+ return this._started;
+ },
+
+ start: function shell_start() {
+ this._started = true;
+
+ // This forces the initialization of the cookie service before we hit the
+ // network.
+ // See bug 810209
+ let cookies = Cc["@mozilla.org/cookieService;1"];
+
+ try {
+ let cr = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsICrashReporter);
+ // Dogfood id. We might want to remove it in the future.
+ // see bug 789466
+ try {
+ let dogfoodId = Services.prefs.getCharPref('prerelease.dogfood.id');
+ if (dogfoodId != "") {
+ cr.annotateCrashReport("Email", dogfoodId);
+ }
+ }
+ catch (e) { }
+
+#ifdef MOZ_WIDGET_GONK
+ // Annotate crash report
+ let annotations = [ [ "Android_Hardware", "ro.hardware" ],
+ [ "Android_Device", "ro.product.device" ],
+ [ "Android_CPU_ABI2", "ro.product.cpu.abi2" ],
+ [ "Android_CPU_ABI", "ro.product.cpu.abi" ],
+ [ "Android_Manufacturer", "ro.product.manufacturer" ],
+ [ "Android_Brand", "ro.product.brand" ],
+ [ "Android_Model", "ro.product.model" ],
+ [ "Android_Board", "ro.product.board" ],
+ ];
+
+ annotations.forEach(function (element) {
+ cr.annotateCrashReport(element[0], libcutils.property_get(element[1]));
+ });
+
+ let androidVersion = libcutils.property_get("ro.build.version.sdk") +
+ "(" + libcutils.property_get("ro.build.version.codename") + ")";
+ cr.annotateCrashReport("Android_Version", androidVersion);
+
+ SettingsListener.observe("deviceinfo.os", "", function(value) {
+ try {
+ let cr = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsICrashReporter);
+ cr.annotateCrashReport("B2G_OS_Version", value);
+ } catch(e) { }
+ });
+#endif
+ } catch(e) {
+ debugCrashReport('exception: ' + e);
+ }
+
+ let homeURL = this.homeURL;
+ if (!homeURL) {
+ let msg = 'Fatal error during startup: No homescreen found: try setting B2G_HOMESCREEN';
+ alert(msg);
+ return;
+ }
+ let manifestURL = this.manifestURL;
+ //
+ let systemAppFrame =
+ document.createElementNS('http://www.w3.org/1999/xhtml', 'html:iframe');
+ systemAppFrame.setAttribute('id', 'systemapp');
+ systemAppFrame.setAttribute('mozbrowser', 'true');
+ systemAppFrame.setAttribute('mozapp', manifestURL);
+ systemAppFrame.setAttribute('allowfullscreen', 'true');
+ systemAppFrame.setAttribute('style', "overflow: hidden; height: 100%; width: 100%; border: none;");
+ systemAppFrame.setAttribute('src', "data:text/html;charset=utf-8,%3C!DOCTYPE html>%3Cbody style='background:black;");
+ let container = document.getElementById('container');
+#ifdef MOZ_WIDGET_COCOA
+ // See shell.html
+ let hotfix = document.getElementById('placeholder');
+ if (hotfix) {
+ container.removeChild(hotfix);
+ }
+#endif
+ container.appendChild(systemAppFrame);
+
+ systemAppFrame.contentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .sessionHistory = Cc["@mozilla.org/browser/shistory;1"]
+ .createInstance(Ci.nsISHistory);
+
+ // On firefox mulet, shell.html is loaded in a tab
+ // and we have to listen on the chrome event handler
+ // to catch key events
+ let chromeEventHandler = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler || window;
+ // Capture all key events so we can filter out hardware buttons
+ // And send them to Gaia via mozChromeEvents.
+ // Ideally, hardware buttons wouldn't generate key events at all, or
+ // if they did, they would use keycodes that conform to DOM 3 Events.
+ // See discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=762362
+ chromeEventHandler.addEventListener('keydown', this, true);
+ chromeEventHandler.addEventListener('keypress', this, true);
+ chromeEventHandler.addEventListener('keyup', this, true);
+
+ window.addEventListener('MozApplicationManifest', this);
+ window.addEventListener('mozfullscreenchange', this);
+ window.addEventListener('MozAfterPaint', this);
+ window.addEventListener('sizemodechange', this);
+ window.addEventListener('unload', this);
+ this.contentBrowser.addEventListener('mozbrowserloadstart', this, true);
+
+ SystemAppProxy.registerFrame(this.contentBrowser);
+
+ CustomEventManager.init();
+ WebappsHelper.init();
+ UserAgentOverrides.init();
+ IndexedDBPromptHelper.init();
+ CaptivePortalLoginHelper.init();
+
+ this.contentBrowser.src = homeURL;
+ this.isHomeLoaded = false;
+
+ ppmm.addMessageListener("content-handler", this);
+ ppmm.addMessageListener("dial-handler", this);
+ ppmm.addMessageListener("sms-handler", this);
+ ppmm.addMessageListener("mail-handler", this);
+ ppmm.addMessageListener("app-notification-send", AlertsHelper);
+ ppmm.addMessageListener("file-picker", this);
+ ppmm.addMessageListener("getProfD", function(message) {
+ return Services.dirsvc.get("ProfD", Ci.nsIFile).path;
+ });
+ },
+
+ stop: function shell_stop() {
+ window.removeEventListener('unload', this);
+ window.removeEventListener('keydown', this, true);
+ window.removeEventListener('keypress', this, true);
+ window.removeEventListener('keyup', this, true);
+ window.removeEventListener('MozApplicationManifest', this);
+ window.removeEventListener('mozfullscreenchange', this);
+ window.removeEventListener('sizemodechange', this);
+ this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
+ ppmm.removeMessageListener("content-handler", this);
+ if (this.timer) {
+ this.timer.cancel();
+ this.timer = null;
+ }
+
+ UserAgentOverrides.uninit();
+ IndexedDBPromptHelper.uninit();
+ },
+
+ // If this key event actually represents a hardware button, filter it here
+ // and send a mozChromeEvent with detail.type set to xxx-button-press or
+ // xxx-button-release instead.
+ filterHardwareKeys: function shell_filterHardwareKeys(evt) {
+ var type;
+ switch (evt.keyCode) {
+ case evt.DOM_VK_HOME: // Home button
+ type = 'home-button';
+ break;
+ case evt.DOM_VK_SLEEP: // Sleep button
+ case evt.DOM_VK_END: // On desktop we don't have a sleep button
+ type = 'sleep-button';
+ break;
+ case evt.DOM_VK_PAGE_UP: // Volume up button
+ type = 'volume-up-button';
+ break;
+ case evt.DOM_VK_PAGE_DOWN: // Volume down button
+ type = 'volume-down-button';
+ break;
+ case evt.DOM_VK_ESCAPE: // Back button (should be disabled)
+ type = 'back-button';
+ break;
+ case evt.DOM_VK_CONTEXT_MENU: // Menu button
+ type = 'menu-button';
+ break;
+ case evt.DOM_VK_F1: // headset button
+ type = 'headset-button';
+ break;
+ }
+
+ let mediaKeys = {
+ 'MediaNextTrack': 'media-next-track-button',
+ 'MediaPreviousTrack': 'media-previous-track-button',
+ 'MediaPause': 'media-pause-button',
+ 'MediaPlay': 'media-play-button',
+ 'MediaPlayPause': 'media-play-pause-button',
+ 'MediaStop': 'media-stop-button',
+ 'MediaRewind': 'media-rewind-button',
+ 'FastFwd': 'media-fast-forward-button'
+ };
+
+ let isMediaKey = false;
+ if (mediaKeys[evt.key]) {
+ isMediaKey = true;
+ type = mediaKeys[evt.key];
+ }
+
+ if (!type) {
+ return;
+ }
+
+ // If we didn't return, then the key event represents a hardware key
+ // and we need to prevent it from propagating to Gaia
+ evt.stopImmediatePropagation();
+ evt.preventDefault(); // Prevent keypress events (when #501496 is fixed).
+
+ // If it is a key down or key up event, we send a chrome event to Gaia.
+ // If it is a keypress event we just ignore it.
+ switch (evt.type) {
+ case 'keydown':
+ type = type + '-press';
+ break;
+ case 'keyup':
+ type = type + '-release';
+ break;
+ case 'keypress':
+ return;
+ }
+
+ // Let applications receive the headset button key press/release event.
+ if (evt.keyCode == evt.DOM_VK_F1 && type !== this.lastHardwareButtonEventType) {
+ this.lastHardwareButtonEventType = type;
+ gSystemMessenger.broadcastMessage('headset-button', type);
+ return;
+ }
+
+ if (isMediaKey) {
+ this.lastHardwareButtonEventType = type;
+ gSystemMessenger.broadcastMessage('media-button', type);
+ return;
+ }
+
+ // On my device, the physical hardware buttons (sleep and volume)
+ // send multiple events (press press release release), but the
+ // soft home button just sends one. This hack is to manually
+ // "debounce" the keys. If the type of this event is the same as
+ // the type of the last one, then don't send it. We'll never send
+ // two presses or two releases in a row.
+ // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=761067
+ if (type !== this.lastHardwareButtonEventType) {
+ this.lastHardwareButtonEventType = type;
+ this.sendChromeEvent({type: type});
+ }
+ },
+
+ lastHardwareButtonEventType: null, // property for the hack above
+ needBufferOpenAppReq: true,
+ bufferedOpenAppReqs: [],
+ timer: null,
+ visibleNormalAudioActive: false,
+
+ handleEvent: function shell_handleEvent(evt) {
+ let content = this.contentBrowser.contentWindow;
+ switch (evt.type) {
+ case 'keydown':
+ case 'keyup':
+ case 'keypress':
+ this.filterHardwareKeys(evt);
+ break;
+ case 'mozfullscreenchange':
+ // When the screen goes fullscreen make sure to set the focus to the
+ // main window so noboby can prevent the ESC key to get out fullscreen
+ // mode
+ if (document.mozFullScreen)
+ Services.fm.focusedWindow = window;
+ break;
+ case 'sizemodechange':
+ if (window.windowState == window.STATE_MINIMIZED && !this.visibleNormalAudioActive) {
+ this.contentBrowser.setVisible(false);
+ } else {
+ this.contentBrowser.setVisible(true);
+ }
+ break;
+ case 'mozbrowserloadstart':
+ if (content.document.location == 'about:blank') {
+ this.contentBrowser.addEventListener('mozbrowserlocationchange', this, true);
+ return;
+ }
+
+ this.notifyContentStart();
+ break;
+ case 'mozbrowserlocationchange':
+ if (content.document.location == 'about:blank') {
+ return;
+ }
+
+ this.notifyContentStart();
+ break;
+
+ case 'MozApplicationManifest':
+ try {
+ if (!Services.prefs.getBoolPref('browser.cache.offline.enable'))
+ return;
+
+ let contentWindow = evt.originalTarget.defaultView;
+ let documentElement = contentWindow.document.documentElement;
+ if (!documentElement)
+ return;
+
+ let manifest = documentElement.getAttribute('manifest');
+ if (!manifest)
+ return;
+
+ let principal = contentWindow.document.nodePrincipal;
+ if (Services.perms.testPermissionFromPrincipal(principal, 'offline-app') == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
+ if (Services.prefs.getBoolPref('browser.offline-apps.notify')) {
+ // FIXME Bug 710729 - Add a UI for offline cache notifications
+ return;
+ }
+ return;
+ }
+
+ Services.perms.addFromPrincipal(principal, 'offline-app',
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ let documentURI = Services.io.newURI(contentWindow.document.documentURI,
+ null,
+ null);
+ let manifestURI = Services.io.newURI(manifest, null, documentURI);
+ let updateService = Cc['@mozilla.org/offlinecacheupdate-service;1']
+ .getService(Ci.nsIOfflineCacheUpdateService);
+ updateService.scheduleUpdate(manifestURI, documentURI, window);
+ } catch (e) {
+ dump('Error while creating offline cache: ' + e + '\n');
+ }
+ break;
+ case 'MozAfterPaint':
+ window.removeEventListener('MozAfterPaint', this);
+ this.sendChromeEvent({
+ type: 'system-first-paint'
+ });
+ break;
+ case 'unload':
+ this.stop();
+ break;
+ }
+ },
+
+ // Send an event to a specific window, document or element.
+ sendEvent: function shell_sendEvent(target, type, details) {
+ let doc = target.document || target.ownerDocument || target;
+ let event = doc.createEvent('CustomEvent');
+ event.initCustomEvent(type, true, true, details ? details : {});
+ target.dispatchEvent(event);
+ },
+
+ sendCustomEvent: function shell_sendCustomEvent(type, details) {
+ let target = getContentWindow();
+ let payload = details ? Cu.cloneInto(details, target) : {};
+ this.sendEvent(target, type, payload);
+ },
+
+ sendChromeEvent: function shell_sendChromeEvent(details) {
+ if (!this.isHomeLoaded) {
+ if (!('pendingChromeEvents' in this)) {
+ this.pendingChromeEvents = [];
+ }
+
+ this.pendingChromeEvents.push(details);
+ return;
+ }
+
+ this.sendEvent(getContentWindow(), "mozChromeEvent",
+ Cu.cloneInto(details, getContentWindow()));
+ },
+
+ openAppForSystemMessage: function shell_openAppForSystemMessage(msg) {
+ let payload = {
+ url: msg.pageURL,
+ manifestURL: msg.manifestURL,
+ isActivity: (msg.type == 'activity'),
+ onlyShowApp: msg.onlyShowApp,
+ showApp: msg.showApp,
+ target: msg.target,
+ expectingSystemMessage: true,
+ extra: msg.extra
+ }
+ this.sendCustomEvent('open-app', payload);
+ },
+
+ receiveMessage: function shell_receiveMessage(message) {
+ var activities = { 'content-handler': { name: 'view', response: null },
+ 'dial-handler': { name: 'dial', response: null },
+ 'mail-handler': { name: 'new', response: null },
+ 'sms-handler': { name: 'new', response: null },
+ 'file-picker': { name: 'pick', response: 'file-picked' } };
+
+ if (!(message.name in activities))
+ return;
+
+ let data = message.data;
+ let activity = activities[message.name];
+
+ let a = new MozActivity({
+ name: activity.name,
+ data: data
+ });
+
+ if (activity.response) {
+ a.onsuccess = function() {
+ let sender = message.target.QueryInterface(Ci.nsIMessageSender);
+ sender.sendAsyncMessage(activity.response, { success: true,
+ result: a.result });
+ }
+ a.onerror = function() {
+ let sender = message.target.QueryInterface(Ci.nsIMessageSender);
+ sender.sendAsyncMessage(activity.response, { success: false });
+ }
+ }
+ },
+
+ notifyContentStart: function shell_notifyContentStart() {
+ this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
+ this.contentBrowser.removeEventListener('mozbrowserlocationchange', this, true);
+
+ let content = this.contentBrowser.contentWindow;
+
+ this.reportCrash(true);
+
+ this.sendEvent(window, 'ContentStart');
+
+ Services.obs.notifyObservers(null, 'content-start', null);
+
+#ifdef MOZ_WIDGET_GONK
+ Cu.import('resource://gre/modules/OperatorApps.jsm');
+#endif
+
+ content.addEventListener('load', function shell_homeLoaded() {
+ content.removeEventListener('load', shell_homeLoaded);
+ shell.isHomeLoaded = true;
+
+#ifdef MOZ_WIDGET_GONK
+ libcutils.property_set('sys.boot_completed', '1');
+#endif
+
+ Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
+
+ SystemAppProxy.setIsReady();
+ if ('pendingChromeEvents' in shell) {
+ shell.pendingChromeEvents.forEach((shell.sendChromeEvent).bind(shell));
+ }
+ delete shell.pendingChromeEvents;
+ });
+ }
+};
+
+// Listen for the request of opening app and relay them to Gaia.
+Services.obs.addObserver(function onSystemMessageOpenApp(subject, topic, data) {
+ let msg = JSON.parse(data);
+ // Buffer non-activity request until content starts to load for 10 seconds.
+ // We'll revisit this later if new kind of requests don't need to be cached.
+ if (shell.needBufferOpenAppReq && msg.type !== 'activity') {
+ shell.bufferedOpenAppReqs.push(msg);
+ return;
+ }
+ shell.openAppForSystemMessage(msg);
+}, 'system-messages-open-app', false);
+
+Services.obs.addObserver(function onInterAppCommConnect(subject, topic, data) {
+ data = JSON.parse(data);
+ shell.sendChromeEvent({ type: "inter-app-comm-permission",
+ chromeEventID: data.callerID,
+ manifestURL: data.manifestURL,
+ keyword: data.keyword,
+ peers: data.appsToSelect });
+}, 'inter-app-comm-select-app', false);
+
+Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data) {
+ shell.sendChromeEvent({ type: "fullscreenoriginchange",
+ fullscreenorigin: data });
+}, "fullscreen-origin-change", false);
+
+DOMApplicationRegistry.registryStarted.then(function () {
+ shell.sendChromeEvent({ type: 'webapps-registry-start' });
+});
+DOMApplicationRegistry.registryReady.then(function () {
+ shell.sendChromeEvent({ type: 'webapps-registry-ready' });
+});
+
+Services.obs.addObserver(function onBluetoothVolumeChange(subject, topic, data) {
+ shell.sendChromeEvent({
+ type: "bluetooth-volumeset",
+ value: data
+ });
+}, 'bluetooth-volume-change', false);
+
+Services.obs.addObserver(function(subject, topic, data) {
+ shell.sendCustomEvent('mozmemorypressure');
+}, 'memory-pressure', false);
+
+var CustomEventManager = {
+ init: function custevt_init() {
+ window.addEventListener("ContentStart", (function(evt) {
+ let content = shell.contentBrowser.contentWindow;
+ content.addEventListener("mozContentEvent", this, false, true);
+
+ // After content starts to load for 10 seconds, send and
+ // clean up the buffered open-app requests if there is any.
+ //
+ // TODO: Bug 793420 - Remove the waiting timer for the 'open-app'
+ // mozChromeEvents requested by System Message
+ shell.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ shell.timer.initWithCallback(function timerCallback() {
+ shell.bufferedOpenAppReqs.forEach(function bufferOpenAppReq(msg) {
+ shell.openAppForSystemMessage(msg);
+ });
+ shell.bufferedOpenAppReqs.length = 0;
+ shell.needBufferOpenAppReq = false;
+ shell.timer = null;
+ }, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
+ }).bind(this), false);
+ },
+
+ handleEvent: function custevt_handleEvent(evt) {
+ let detail = evt.detail;
+ dump('XXX FIXME : Got a mozContentEvent: ' + detail.type + "\n");
+
+ switch(detail.type) {
+ case 'desktop-notification-show':
+ case 'desktop-notification-click':
+ case 'desktop-notification-close':
+ AlertsHelper.handleEvent(detail);
+ break;
+ case 'webapps-install-granted':
+ case 'webapps-install-denied':
+ WebappsHelper.handleEvent(detail);
+ break;
+ case 'select-choicechange':
+ FormsHelper.handleEvent(detail);
+ break;
+ case 'system-message-listener-ready':
+ Services.obs.notifyObservers(null, 'system-message-listener-ready', null);
+ break;
+ case 'remote-debugger-prompt':
+ RemoteDebugger.handleEvent(detail);
+ break;
+ case 'captive-portal-login-cancel':
+ CaptivePortalLoginHelper.handleEvent(detail);
+ break;
+ case 'inter-app-comm-permission':
+ Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result',
+ JSON.stringify({ callerID: detail.chromeEventID,
+ keyword: detail.keyword,
+ manifestURL: detail.manifestURL,
+ selectedApps: detail.peers }));
+ break;
+ case 'inputmethod-update-layouts':
+ KeyboardHelper.handleEvent(detail);
+ break;
+ }
+ }
+}
+
+var AlertsHelper = {
+ _listeners: {},
+ _count: 0,
+
+ handleEvent: function alert_handleEvent(detail) {
+ if (!detail || !detail.id)
+ return;
+
+ let uid = detail.id;
+ let listener = this._listeners[uid];
+ if (!listener)
+ return;
+
+ let topic;
+ if (detail.type == "desktop-notification-click") {
+ topic = "alertclickcallback";
+ } else if (detail.type == "desktop-notification-show") {
+ topic = "alertshow";
+ } else {
+ /* desktop-notification-close */
+ topic = "alertfinished";
+ }
+
+ if (uid.startsWith("alert")) {
+ try {
+ listener.observer.observe(null, topic, listener.cookie);
+ } catch (e) { }
+ } else {
+ try {
+ listener.mm.sendAsyncMessage("app-notification-return", {
+ uid: uid,
+ topic: topic,
+ target: listener.target
+ });
+ } catch (e) {
+ // we get an exception if the app is not launched yet
+ gSystemMessenger.sendMessage("notification", {
+ clicked: (detail.type === "desktop-notification-click"),
+ title: listener.title,
+ body: listener.text,
+ imageURL: listener.imageURL,
+ lang: listener.lang,
+ dir: listener.dir,
+ id: listener.id,
+ tag: listener.tag
+ },
+ Services.io.newURI(listener.target, null, null),
+ Services.io.newURI(listener.manifestURL, null, null)
+ );
+ }
+ }
+
+ // we're done with this notification
+ if (topic === "alertfinished") {
+ delete this._listeners[uid];
+ }
+ },
+
+ registerListener: function alert_registerListener(alertId, cookie, alertListener) {
+ this._listeners[alertId] = { observer: alertListener, cookie: cookie };
+ },
+
+ registerAppListener: function alert_registerAppListener(uid, listener) {
+ this._listeners[uid] = listener;
+
+ let app = DOMApplicationRegistry.getAppByManifestURL(listener.manifestURL);
+ DOMApplicationRegistry.getManifestFor(app.manifestURL).then((manifest) => {
+ let helper = new ManifestHelper(manifest, app.origin);
+ let getNotificationURLFor = function(messages) {
+ if (!messages)
+ return null;
+
+ for (let i = 0; i < messages.length; i++) {
+ let message = messages[i];
+ if (message === "notification") {
+ return helper.fullLaunchPath();
+ } else if (typeof message == "object" && "notification" in message) {
+ return helper.resolveFromOrigin(message["notification"]);
+ }
+ }
+
+ // No message found...
+ return null;
+ }
+
+ listener.target = getNotificationURLFor(manifest.messages);
+
+ // Bug 816944 - Support notification messages for entry_points.
+ });
+ },
+
+ showNotification: function alert_showNotification(imageURL,
+ title,
+ text,
+ textClickable,
+ cookie,
+ uid,
+ bidi,
+ lang,
+ manifestURL) {
+ function send(appName, appIcon) {
+ shell.sendChromeEvent({
+ type: "desktop-notification",
+ id: uid,
+ icon: imageURL,
+ title: title,
+ text: text,
+ bidi: bidi,
+ lang: lang,
+ appName: appName,
+ appIcon: appIcon,
+ manifestURL: manifestURL
+ });
+ }
+
+ if (!manifestURL || !manifestURL.length) {
+ send(null, null);
+ return;
+ }
+
+ // If we have a manifest URL, get the icon and title from the manifest
+ // to prevent spoofing.
+ let app = DOMApplicationRegistry.getAppByManifestURL(manifestURL);
+ DOMApplicationRegistry.getManifestFor(manifestURL).then((aManifest) => {
+ let helper = new ManifestHelper(aManifest, app.origin);
+ send(helper.name, helper.iconURLForSize(128));
+ });
+ },
+
+ showAlertNotification: function alert_showAlertNotification(imageURL,
+ title,
+ text,
+ textClickable,
+ cookie,
+ alertListener,
+ name,
+ bidi,
+ lang) {
+ let currentListener = this._listeners[name];
+ if (currentListener) {
+ currentListener.observer.observe(null, "alertfinished", currentListener.cookie);
+ }
+
+ this.registerListener(name, cookie, alertListener);
+ this.showNotification(imageURL, title, text, textClickable, cookie,
+ name, bidi, lang, null);
+ },
+
+ closeAlert: function alert_closeAlert(name) {
+ shell.sendChromeEvent({
+ type: "desktop-notification-close",
+ id: name
+ });
+ },
+
+ receiveMessage: function alert_receiveMessage(aMessage) {
+ if (!aMessage.target.assertAppHasPermission("desktop-notification")) {
+ Cu.reportError("Desktop-notification message " + aMessage.name +
+ " from a content process with no desktop-notification privileges.");
+ return;
+ }
+
+ let data = aMessage.data;
+ let details = data.details;
+ let listener = {
+ mm: aMessage.target,
+ title: data.title,
+ text: data.text,
+ manifestURL: details.manifestURL,
+ imageURL: data.imageURL,
+ lang: details.lang || undefined,
+ id: details.id || undefined,
+ dir: details.dir || undefined,
+ tag: details.tag || undefined
+ };
+ this.registerAppListener(data.uid, listener);
+
+ this.showNotification(data.imageURL, data.title, data.text,
+ details.textClickable, null,
+ data.uid, details.dir,
+ details.lang, details.manifestURL);
+ },
+}
+
+var WebappsHelper = {
+ _installers: {},
+ _count: 0,
+
+ init: function webapps_init() {
+ Services.obs.addObserver(this, "webapps-launch", false);
+ Services.obs.addObserver(this, "webapps-ask-install", false);
+ Services.obs.addObserver(this, "webapps-close", false);
+ },
+
+ registerInstaller: function webapps_registerInstaller(data) {
+ let id = "installer" + this._count++;
+ this._installers[id] = data;
+ return id;
+ },
+
+ handleEvent: function webapps_handleEvent(detail) {
+ if (!detail || !detail.id)
+ return;
+
+ let installer = this._installers[detail.id];
+ delete this._installers[detail.id];
+ switch (detail.type) {
+ case "webapps-install-granted":
+ DOMApplicationRegistry.confirmInstall(installer);
+ break;
+ case "webapps-install-denied":
+ DOMApplicationRegistry.denyInstall(installer);
+ break;
+ }
+ },
+
+ observe: function webapps_observe(subject, topic, data) {
+ let json = JSON.parse(data);
+ json.mm = subject;
+
+ switch(topic) {
+ case "webapps-launch":
+ DOMApplicationRegistry.getManifestFor(json.manifestURL).then((aManifest) => {
+ if (!aManifest)
+ return;
+
+ let manifest = new ManifestHelper(aManifest, json.origin);
+ let payload = {
+ __exposedProps__: {
+ timestamp: "r",
+ url: "r",
+ manifestURL: "r"
+ },
+ timestamp: json.timestamp,
+ url: manifest.fullLaunchPath(json.startPoint),
+ manifestURL: json.manifestURL
+ }
+ shell.sendEvent(getContentWindow(), "webapps-launch", payload);
+ });
+ break;
+ case "webapps-ask-install":
+ let id = this.registerInstaller(json);
+ shell.sendChromeEvent({
+ type: "webapps-ask-install",
+ id: id,
+ app: json.app
+ });
+ break;
+ case "webapps-close":
+ shell.sendEvent(getContentWindow(), "webapps-close",
+ {
+ __exposedProps__: { "manifestURL": "r" },
+ "manifestURL": json.manifestURL
+ });
+ break;
+ }
+ }
+}
+
+let IndexedDBPromptHelper = {
+ _quotaPrompt: "indexedDB-quota-prompt",
+ _quotaResponse: "indexedDB-quota-response",
+
+ init:
+ function IndexedDBPromptHelper_init() {
+ Services.obs.addObserver(this, this._quotaPrompt, false);
+ },
+
+ uninit:
+ function IndexedDBPromptHelper_uninit() {
+ Services.obs.removeObserver(this, this._quotaPrompt);
+ },
+
+ observe:
+ function IndexedDBPromptHelper_observe(subject, topic, data) {
+ if (topic != this._quotaPrompt) {
+ throw new Error("Unexpected topic!");
+ }
+
+ let observer = subject.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIObserver);
+ let responseTopic = this._quotaResponse;
+
+ setTimeout(function() {
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.DENY_ACTION);
+ }, 0);
+ }
+}
+
+let RemoteDebugger = {
+ _promptDone: false,
+ _promptAnswer: false,
+ _running: false,
+
+ prompt: function debugger_prompt() {
+ this._promptDone = false;
+
+ shell.sendChromeEvent({
+ "type": "remote-debugger-prompt"
+ });
+
+ while(!this._promptDone) {
+ Services.tm.currentThread.processNextEvent(true);
+ }
+
+ return this._promptAnswer;
+ },
+
+ handleEvent: function debugger_handleEvent(detail) {
+ this._promptAnswer = detail.value;
+ this._promptDone = true;
+ },
+
+ get isDebugging() {
+ if (!this._running) {
+ return false;
+ }
+
+ return DebuggerServer._connections &&
+ Object.keys(DebuggerServer._connections).length > 0;
+ },
+
+ // Start the debugger server.
+ start: function debugger_start() {
+ if (this._running) {
+ return;
+ }
+
+ if (!DebuggerServer.initialized) {
+ // Ask for remote connections.
+ DebuggerServer.init(this.prompt.bind(this));
+
+ // /!\ Be careful when adding a new actor, especially global actors.
+ // Any new global actor will be exposed and returned by the root actor.
+
+ // Add Firefox-specific actors, but prevent tab actors to be loaded in
+ // the parent process, unless we enable certified apps debugging.
+ let restrictPrivileges = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps");
+ DebuggerServer.addBrowserActors("navigator:browser", restrictPrivileges);
+
+ /**
+ * Construct a root actor appropriate for use in a server running in B2G.
+ * The returned root actor respects the factories registered with
+ * DebuggerServer.addGlobalActor only if certified apps debugging is on,
+ * otherwise we used an explicit limited list of global actors
+ *
+ * * @param connection DebuggerServerConnection
+ * The conection to the client.
+ */
+ DebuggerServer.createRootActor = function createRootActor(connection)
+ {
+ let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
+ let parameters = {
+ // We do not expose browser tab actors yet,
+ // but we still have to define tabList.getList(),
+ // otherwise, client won't be able to fetch global actors
+ // from listTabs request!
+ tabList: {
+ getList: function() {
+ return promise.resolve([]);
+ }
+ },
+ // Use an explicit global actor list to prevent exposing
+ // unexpected actors
+ globalActorFactories: restrictPrivileges ? {
+ webappsActor: DebuggerServer.globalActorFactories.webappsActor,
+ deviceActor: DebuggerServer.globalActorFactories.deviceActor,
+ } : DebuggerServer.globalActorFactories
+ };
+ let root = new DebuggerServer.RootActor(connection, parameters);
+ root.applicationType = "operating-system";
+ return root;
+ };
+
+#ifdef MOZ_WIDGET_GONK
+ DebuggerServer.on("connectionchange", function() {
+ AdbController.updateState();
+ });
+#endif
+ }
+
+ let path = Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") ||
+ "/data/local/debugger-socket";
+ try {
+ DebuggerServer.openListener(path);
+ // Temporary event, until bug 942756 lands and offers a way to know
+ // when the server is up and running.
+ Services.obs.notifyObservers(null, 'debugger-server-started', null);
+ this._running = true;
+ } catch (e) {
+ dump('Unable to start debugger server: ' + e + '\n');
+ }
+ },
+
+ stop: function debugger_stop() {
+ if (!this._running) {
+ return;
+ }
+
+ if (!DebuggerServer.initialized) {
+ // Can this really happen if we are running?
+ this._running = false;
+ return;
+ }
+
+ try {
+ DebuggerServer.closeListener();
+ } catch (e) {
+ dump('Unable to stop debugger server: ' + e + '\n');
+ }
+ this._running = false;
+ }
+}
+
+let KeyboardHelper = {
+ handleEvent: function keyboard_handleEvent(detail) {
+ Keyboard.setLayouts(detail.layouts);
+ }
+};
+
+// This is the backend for Gaia's screenshot feature. Gaia requests a
+// screenshot by sending a mozContentEvent with detail.type set to
+// 'take-screenshot'. Then we take a screenshot and send a
+// mozChromeEvent with detail.type set to 'take-screenshot-success'
+// and detail.file set to the an image/png blob
+window.addEventListener('ContentStart', function ss_onContentStart() {
+ let content = shell.contentBrowser.contentWindow;
+ content.addEventListener('mozContentEvent', function ss_onMozContentEvent(e) {
+ if (e.detail.type !== 'take-screenshot')
+ return;
+
+ try {
+ var canvas = document.createElementNS('http://www.w3.org/1999/xhtml',
+ 'canvas');
+ var width = window.innerWidth;
+ var height = window.innerHeight;
+ var scale = window.devicePixelRatio;
+ canvas.setAttribute('width', width * scale);
+ canvas.setAttribute('height', height * scale);
+
+ var context = canvas.getContext('2d');
+ var flags =
+ context.DRAWWINDOW_DRAW_CARET |
+ context.DRAWWINDOW_DRAW_VIEW |
+ context.DRAWWINDOW_USE_WIDGET_LAYERS;
+ context.scale(scale, scale);
+ context.drawWindow(window, 0, 0, width, height,
+ 'rgb(255,255,255)', flags);
+
+ // I can't use sendChromeEvent() here because it doesn't wrap
+ // the blob in the detail object correctly. So I use __exposedProps__
+ // instead to safely send the chrome detail object to content.
+ shell.sendEvent(getContentWindow(), 'mozChromeEvent', {
+ __exposedProps__: { type: 'r', file: 'r' },
+ type: 'take-screenshot-success',
+ file: canvas.mozGetAsFile('screenshot', 'image/png')
+ });
+ } catch (e) {
+ dump('exception while creating screenshot: ' + e + '\n');
+ shell.sendChromeEvent({
+ type: 'take-screenshot-error',
+ error: String(e)
+ });
+ }
+ });
+});
+
+(function contentCrashTracker() {
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ let props = aSubject.QueryInterface(Ci.nsIPropertyBag2);
+ if (props.hasKey("abnormal") && props.hasKey("dumpID")) {
+ shell.reportCrash(false, props.getProperty("dumpID"));
+ }
+ },
+ "ipc:content-shutdown", false);
+})();
+
+var CaptivePortalLoginHelper = {
+ init: function init() {
+ Services.obs.addObserver(this, 'captive-portal-login', false);
+ Services.obs.addObserver(this, 'captive-portal-login-abort', false);
+ },
+ handleEvent: function handleEvent(detail) {
+ Services.captivePortalDetector.cancelLogin(detail.id);
+ },
+ observe: function observe(subject, topic, data) {
+ shell.sendChromeEvent(JSON.parse(data));
+ }
+}
+
+// Listen for crashes submitted through the crash reporter UI.
+window.addEventListener('ContentStart', function cr_onContentStart() {
+ let content = shell.contentBrowser.contentWindow;
+ content.addEventListener("mozContentEvent", function cr_onMozContentEvent(e) {
+ if (e.detail.type == "submit-crash" && e.detail.crashID) {
+ debugCrashReport("submitting crash at user request ", e.detail.crashID);
+ shell.submitCrash(e.detail.crashID);
+ } else if (e.detail.type == "delete-crash" && e.detail.crashID) {
+ debugCrashReport("deleting crash at user request ", e.detail.crashID);
+ shell.deleteCrash(e.detail.crashID);
+ }
+ });
+});
+
+window.addEventListener('ContentStart', function update_onContentStart() {
+ Cu.import('resource://gre/modules/WebappsUpdater.jsm');
+ WebappsUpdater.handleContentStart(shell);
+
+ let promptCc = Cc["@mozilla.org/updates/update-prompt;1"];
+ if (!promptCc) {
+ return;
+ }
+
+ let updatePrompt = promptCc.createInstance(Ci.nsIUpdatePrompt);
+ if (!updatePrompt) {
+ return;
+ }
+
+ updatePrompt.wrappedJSObject.handleContentStart(shell);
+});
+
+(function geolocationStatusTracker() {
+ let gGeolocationActive = false;
+
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ let oldState = gGeolocationActive;
+ if (aData == "starting") {
+ gGeolocationActive = true;
+ } else if (aData == "shutdown") {
+ gGeolocationActive = false;
+ }
+
+ if (gGeolocationActive != oldState) {
+ shell.sendChromeEvent({
+ type: 'geolocation-status',
+ active: gGeolocationActive
+ });
+ }
+}, "geolocation-device-events", false);
+})();
+
+(function headphonesStatusTracker() {
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ shell.sendChromeEvent({
+ type: 'headphones-status-changed',
+ state: aData
+ });
+}, "headphones-status-changed", false);
+})();
+
+(function audioChannelChangedTracker() {
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ shell.sendChromeEvent({
+ type: 'audio-channel-changed',
+ channel: aData
+ });
+}, "audio-channel-changed", false);
+})();
+
+(function defaultVolumeChannelChangedTracker() {
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ shell.sendChromeEvent({
+ type: 'default-volume-channel-changed',
+ channel: aData
+ });
+}, "default-volume-channel-changed", false);
+})();
+
+(function visibleAudioChannelChangedTracker() {
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ shell.sendChromeEvent({
+ type: 'visible-audio-channel-changed',
+ channel: aData
+ });
+ shell.visibleNormalAudioActive = (aData == 'normal');
+}, "visible-audio-channel-changed", false);
+})();
+
+(function recordingStatusTracker() {
+ // Recording status is tracked per process with following data structure:
+ // {: {: {isApp: ,
+ // count: ,
+ // audioCount: ,
+ // videoCount: }}
+ let gRecordingActiveProcesses = {};
+
+ let recordingHandler = function(aSubject, aTopic, aData) {
+ let props = aSubject.QueryInterface(Ci.nsIPropertyBag2);
+ let processId = (props.hasKey('childID')) ? props.get('childID')
+ : 'main';
+ if (processId && !gRecordingActiveProcesses.hasOwnProperty(processId)) {
+ gRecordingActiveProcesses[processId] = {};
+ }
+
+ let commandHandler = function (requestURL, command) {
+ let currentProcess = gRecordingActiveProcesses[processId];
+ let currentActive = currentProcess[requestURL];
+ let wasActive = (currentActive['count'] > 0);
+ let wasAudioActive = (currentActive['audioCount'] > 0);
+ let wasVideoActive = (currentActive['videoCount'] > 0);
+
+ switch (command.type) {
+ case 'starting':
+ currentActive['count']++;
+ currentActive['audioCount'] += (command.isAudio) ? 1 : 0;
+ currentActive['videoCount'] += (command.isVideo) ? 1 : 0;
+ break;
+ case 'shutdown':
+ currentActive['count']--;
+ currentActive['audioCount'] -= (command.isAudio) ? 1 : 0;
+ currentActive['videoCount'] -= (command.isVideo) ? 1 : 0;
+ break;
+ case 'content-shutdown':
+ currentActive['count'] = 0;
+ currentActive['audioCount'] = 0;
+ currentActive['videoCount'] = 0;
+ break;
+ }
+
+ if (currentActive['count'] > 0) {
+ currentProcess[requestURL] = currentActive;
+ } else {
+ delete currentProcess[requestURL];
+ }
+
+ // We need to track changes if any active state is changed.
+ let isActive = (currentActive['count'] > 0);
+ let isAudioActive = (currentActive['audioCount'] > 0);
+ let isVideoActive = (currentActive['videoCount'] > 0);
+ if ((isActive != wasActive) ||
+ (isAudioActive != wasAudioActive) ||
+ (isVideoActive != wasVideoActive)) {
+ shell.sendChromeEvent({
+ type: 'recording-status',
+ active: isActive,
+ requestURL: requestURL,
+ isApp: currentActive['isApp'],
+ isAudio: isAudioActive,
+ isVideo: isVideoActive
+ });
+ }
+ };
+
+ switch (aData) {
+ case 'starting':
+ case 'shutdown':
+ // create page record if it is not existed yet.
+ let requestURL = props.get('requestURL');
+ if (requestURL &&
+ !gRecordingActiveProcesses[processId].hasOwnProperty(requestURL)) {
+ gRecordingActiveProcesses[processId][requestURL] = {isApp: props.get('isApp'),
+ count: 0,
+ audioCount: 0,
+ videoCount: 0};
+ }
+ commandHandler(requestURL, { type: aData,
+ isAudio: props.get('isAudio'),
+ isVideo: props.get('isVideo')});
+ break;
+ case 'content-shutdown':
+ // iterate through all the existing active processes
+ Object.keys(gRecordingActiveProcesses[processId]).forEach(function(requestURL) {
+ commandHandler(requestURL, { type: aData,
+ isAudio: true,
+ isVideo: true});
+ });
+ break;
+ }
+
+ // clean up process record if no page record in it.
+ if (Object.keys(gRecordingActiveProcesses[processId]).length == 0) {
+ delete gRecordingActiveProcesses[processId];
+ }
+ };
+ Services.obs.addObserver(recordingHandler, 'recording-device-events', false);
+ Services.obs.addObserver(recordingHandler, 'recording-device-ipc-events', false);
+
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ // send additional recording events if content process is being killed
+ let processId = aSubject.QueryInterface(Ci.nsIPropertyBag2).get('childID');
+ if (gRecordingActiveProcesses.hasOwnProperty(processId)) {
+ Services.obs.notifyObservers(aSubject, 'recording-device-ipc-events', 'content-shutdown');
+ }
+ }, 'ipc:content-shutdown', false);
+})();
+
+(function volumeStateTracker() {
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ shell.sendChromeEvent({
+ type: 'volume-state-changed',
+ active: (aData == 'Shared')
+ });
+}, 'volume-state-changed', false);
+})();
+
+#ifdef MOZ_WIDGET_GONK
+// Devices don't have all the same partition size for /cache where we
+// store the http cache.
+(function setHTTPCacheSize() {
+ let path = Services.prefs.getCharPref("browser.cache.disk.parent_directory");
+ let volumeService = Cc["@mozilla.org/telephony/volume-service;1"]
+ .getService(Ci.nsIVolumeService);
+
+ let stats = volumeService.createOrGetVolumeByPath(path).getStats();
+
+ // We must set the size in KB, and keep a bit of free space.
+ let size = Math.floor(stats.totalBytes / 1024) - 1024;
+ Services.prefs.setIntPref("browser.cache.disk.capacity", size);
+}) ()
+#endif
+
+#ifdef MOZ_WIDGET_GONK
+let SensorsListener = {
+ sensorsListenerDevices: ['crespo'],
+ device: libcutils.property_get("ro.product.device"),
+
+ deviceNeedsWorkaround: function SensorsListener_deviceNeedsWorkaround() {
+ return (this.sensorsListenerDevices.indexOf(this.device) != -1);
+ },
+
+ handleEvent: function SensorsListener_handleEvent(evt) {
+ switch(evt.type) {
+ case 'devicemotion':
+ // Listener that does nothing, we need this to have the sensor being
+ // able to report correct values, as explained in bug 753245, comment 6
+ // and in bug 871916
+ break;
+
+ default:
+ break;
+ }
+ },
+
+ observe: function SensorsListener_observe(subject, topic, data) {
+ // We remove the listener when the screen is off, otherwise sensor will
+ // continue to bother us with data and we won't be able to get the
+ // system into suspend state, thus draining battery.
+ if (data === 'on') {
+ window.addEventListener('devicemotion', this);
+ } else {
+ window.removeEventListener('devicemotion', this);
+ }
+ },
+
+ init: function SensorsListener_init() {
+ if (this.deviceNeedsWorkaround()) {
+ // On boot, enable the listener, screen will be on.
+ window.addEventListener('devicemotion', this);
+
+ // Then listen for further screen state changes
+ Services.obs.addObserver(this, 'screen-state-changed', false);
+ }
+ }
+}
+
+SensorsListener.init();
+#endif
+
+// Calling this observer will cause a shutdown an a profile reset.
+// Use eg. : Services.obs.notifyObservers(null, 'b2g-reset-profile', null);
+Services.obs.addObserver(function resetProfile(subject, topic, data) {
+ Services.obs.removeObserver(resetProfile, topic);
+
+ // Listening for 'profile-before-change2' which is late in the shutdown
+ // sequence, but still has xpcom access.
+ Services.obs.addObserver(function clearProfile(subject, topic, data) {
+ Services.obs.removeObserver(clearProfile, topic);
+#ifdef MOZ_WIDGET_GONK
+ let json = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
+ json.initWithPath('/system/b2g/webapps/webapps.json');
+ let toRemove = json.exists()
+ // This is a user build, just rm -r /data/local /data/b2g/mozilla
+ ? ['/data/local', '/data/b2g/mozilla']
+ // This is an eng build. We clear the profile and a set of files
+ // under /data/local.
+ : ['/data/b2g/mozilla',
+ '/data/local/permissions.sqlite',
+ '/data/local/storage',
+ '/data/local/OfflineCache'];
+
+ toRemove.forEach(function(dir) {
+ try {
+ let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
+ file.initWithPath(dir);
+ file.remove(true);
+ } catch(e) { dump(e); }
+ });
+#else
+ // Desktop builds.
+ let profile = Services.dirsvc.get('ProfD', Ci.nsIFile);
+
+ // We don't want to remove everything from the profile, since this
+ // would prevent us from starting up.
+ let whitelist = ['defaults', 'extensions', 'settings.json',
+ 'user.js', 'webapps'];
+ let enumerator = profile.directoryEntries;
+ while (enumerator.hasMoreElements()) {
+ let file = enumerator.getNext().QueryInterface(Ci.nsIFile);
+ if (whitelist.indexOf(file.leafName) == -1) {
+ file.remove(true);
+ }
+ }
+#endif
+ },
+ 'profile-before-change2', false);
+
+ let appStartup = Cc['@mozilla.org/toolkit/app-startup;1']
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eForceQuit);
+}, 'b2g-reset-profile', false);
+
+/**
+ * CID of our implementation of nsIDownloadManagerUI.
+ */
+const kTransferCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}");
+
+/**
+ * Contract ID of the service implementing nsITransfer.
+ */
+const kTransferContractId = "@mozilla.org/transfer;1";
+
+// Override Toolkit's nsITransfer implementation with the one from the
+// JavaScript API for downloads. This will eventually be removed when
+// nsIDownloadManager will not be available anymore (bug 851471). The
+// old code in this module will be removed in bug 899110.
+Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(kTransferCid, "",
+ kTransferContractId, null);