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