diff options
Diffstat (limited to 'b2g/chrome/content/shell.js')
-rw-r--r-- | b2g/chrome/content/shell.js | 1308 |
1 files changed, 0 insertions, 1308 deletions
diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js deleted file mode 100644 index d483f9a64..000000000 --- a/b2g/chrome/content/shell.js +++ /dev/null @@ -1,1308 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ - -window.performance.mark('gecko-shell-loadstart'); - -Cu.import('resource://gre/modules/NotificationDB.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'); -Cu.import('resource://gre/modules/AlertsHelper.jsm'); -Cu.import('resource://gre/modules/SystemUpdateService.jsm'); - -if (isGonk) { - Cu.import('resource://gre/modules/NetworkStatsService.jsm'); - Cu.import('resource://gre/modules/ResourceStatsService.jsm'); -} - -// Identity -Cu.import('resource://gre/modules/SignInToWebsite.jsm'); -SignInToWebsiteController.init(); - -Cu.import('resource://gre/modules/FxAccountsMgmtService.jsm'); -Cu.import('resource://gre/modules/DownloadsAPI.jsm'); -Cu.import('resource://gre/modules/PresentationDeviceInfoManager.jsm'); -Cu.import('resource://gre/modules/AboutServiceWorkers.jsm'); - -XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", - "resource://gre/modules/SystemAppProxy.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Screenshot", - "resource://gre/modules/Screenshot.jsm"); - -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.defineLazyGetter(this, "ppmm", function() { - return Cc["@mozilla.org/parentprocessmessagemanager;1"] - .getService(Ci.nsIMessageListenerManager); -}); - -if (isGonk) { - XPCOMUtils.defineLazyGetter(this, "libcutils", function () { - Cu.import("resource://gre/modules/systemlibs.js"); - return libcutils; - }); -} - -XPCOMUtils.defineLazyServiceGetter(Services, 'captivePortalDetector', - '@mozilla.org/toolkit/captive-detector;1', - 'nsICaptivePortalDetector'); - -XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing", - "resource://gre/modules/SafeBrowsing.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "SafeMode", - "resource://gre/modules/SafeMode.jsm"); - -window.performance.measure('gecko-shell-jsm-loaded', 'gecko-shell-loadstart'); - -function debug(str) { - dump(' -*- Shell.js: ' + str + '\n'); -} - -const once = event => { - let target = shell.contentBrowser; - return new Promise((resolve, reject) => { - target.addEventListener(event, function gotEvent(evt) { - target.removeEventListener(event, gotEvent, false); - resolve(evt); - }, false); - }); -} - -function clearCache() { - let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"] - .getService(Ci.nsICacheStorageService); - cache.clear(); -} - -function clearCacheAndReload() { - // Reload the main frame with a cleared cache. - debug('Reloading ' + shell.contentBrowser.contentWindow.location); - clearCache(); - shell.contentBrowser.contentWindow.location.reload(true); - once('mozbrowserlocationchange').then( - evt => { - shell.sendEvent(window, "ContentStart"); - }); -} - -function restart() { - let appStartup = Cc['@mozilla.org/toolkit/app-startup;1'] - .getService(Ci.nsIAppStartup); - appStartup.quit(Ci.nsIAppStartup.eForceQuit | Ci.nsIAppStartup.eRestart); -} - -function debugCrashReport(aStr) { - AppConstants.MOZ_CRASHREPORTER && dump('Crash reporter : ' + aStr); -} - -var shell = { - - get CrashSubmit() { - delete this.CrashSubmit; - if (AppConstants.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; - } - }, - - 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.nsINetworkInfo); - if (network.state == Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED - && network.type == Ci.nsINetworkInfo.NETWORK_TYPE_WIFI) { - shell.submitQueuedCrashes(); - - Services.obs.removeObserver(observer, topic); - } - }, "network-connection-state-changed", false); - }, - - get homeURL() { - try { - let homeSrc = Services.env.get('B2G_HOMESCREEN'); - if (homeSrc) - return homeSrc; - } catch (e) {} - - return Services.prefs.getCharPref('b2g.system_startup_url'); - }, - - get manifestURL() { - return Services.prefs.getCharPref('b2g.system_manifest_url'); - }, - - _started: false, - hasStarted: function shell_hasStarted() { - return this._started; - }, - - bootstrap: function() { - window.performance.mark('gecko-shell-bootstrap'); - - // Before anything, check if we want to start in safe mode. - SafeMode.check(window).then(() => { - let startManifestURL = - Cc['@mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap'] - .getService(Ci.nsISupports).wrappedJSObject.startManifestURL; - - // If --start-manifest hasn't been specified, we re-use the latest specified manifest. - // If it's the first launch, we will fallback to b2g.default.start_manifest_url - if (AppConstants.MOZ_GRAPHENE && !startManifestURL) { - try { - startManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url"); - } catch(e) {} - } - - if (!startManifestURL) { - try { - startManifestURL = Services.prefs.getCharPref("b2g.default.start_manifest_url"); - } catch(e) {} - } - - if (startManifestURL) { - Cu.import('resource://gre/modules/Bootstraper.jsm'); - - if (AppConstants.MOZ_GRAPHENE && Bootstraper.isInstallRequired(startManifestURL)) { - // Installing the app my take some time. We don't want to keep the - // native window hidden. - showInstallScreen(); - } - - Bootstraper.ensureSystemAppInstall(startManifestURL) - .then(this.start.bind(this)) - .catch(Bootstraper.bailout); - } else { - this.start(); - } - }); - }, - - start: function shell_start() { - window.performance.mark('gecko-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) { } - - if (isGonk) { - // 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) { } - }); - } - } 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; - // <html:iframe id="systemapp" - // mozbrowser="true" allowfullscreen="true" - // style="overflow: hidden; height: 100%; width: 100%; border: none;" - // src="data:text/html;charset=utf-8,%3C!DOCTYPE html>%3Cbody style='background:black;'>"/> - 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('src', 'blank.html'); - let container = document.getElementById('container'); - - if (AppConstants.platform == 'macosx') { - // See shell.html - let hotfix = document.getElementById('placeholder'); - if (hotfix) { - container.removeChild(hotfix); - } - } - - this.contentBrowser = container.appendChild(systemAppFrame); - - let webNav = systemAppFrame.contentWindow - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation); - webNav.sessionHistory = Cc["@mozilla.org/browser/shistory;1"].createInstance(Ci.nsISHistory); - - if (AppConstants.MOZ_GRAPHENE) { - webNav.QueryInterface(Ci.nsIDocShell).windowDraggingAllowed = true; - } - - let audioChannels = systemAppFrame.allowedAudioChannels; - audioChannels && audioChannels.forEach(function(audioChannel) { - // Set all audio channels as unmuted by default - // because some audio in System app will be played - // before AudioChannelService[1] is Gaia is loaded. - // [1]: https://github.com/mozilla-b2g/gaia/blob/master/apps/system/js/audio_channel_service.js - audioChannel.setMuted(false); - }); - - // 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('keyup', this, true); - - window.addEventListener('MozApplicationManifest', this); - window.addEventListener('MozAfterPaint', this); - window.addEventListener('sizemodechange', this); - window.addEventListener('unload', this); - this.contentBrowser.addEventListener('mozbrowserloadstart', this, true); - this.contentBrowser.addEventListener('mozbrowserscrollviewchange', this, true); - this.contentBrowser.addEventListener('mozbrowsercaretstatechanged', this); - - CustomEventManager.init(); - UserAgentOverrides.init(); - CaptivePortalLoginHelper.init(); - - this.contentBrowser.src = homeURL; - - this._isEventListenerReady = false; - - window.performance.mark('gecko-shell-system-frame-set'); - - ppmm.addMessageListener("content-handler", this); - ppmm.addMessageListener("dial-handler", this); - ppmm.addMessageListener("sms-handler", this); - ppmm.addMessageListener("mail-handler", this); - ppmm.addMessageListener("file-picker", this); - - setTimeout(function() { - SafeBrowsing.init(); - }, 5000); - }, - - stop: function shell_stop() { - window.removeEventListener('unload', this); - window.removeEventListener('keydown', this, true); - window.removeEventListener('keyup', this, true); - window.removeEventListener('MozApplicationManifest', this); - window.removeEventListener('sizemodechange', this); - this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true); - this.contentBrowser.removeEventListener('mozbrowserscrollviewchange', this, true); - this.contentBrowser.removeEventListener('mozbrowsercaretstatechanged', this); - ppmm.removeMessageListener("content-handler", this); - - UserAgentOverrides.uninit(); - }, - - // If this key event represents a hardware button which needs to be send as - // a message, broadcasts it with the message set to 'xxx-button-press' or - // 'xxx-button-release'. - broadcastHardwareKeys: function shell_broadcastHardwareKeys(evt) { - let type; - let message; - - let mediaKeys = { - 'MediaTrackNext': 'media-next-track-button', - 'MediaTrackPrevious': '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', - 'MediaFastForward': 'media-fast-forward-button' - }; - - if (evt.keyCode == evt.DOM_VK_F1) { - type = 'headset-button'; - message = 'headset-button'; - } else if (mediaKeys[evt.key]) { - type = 'media-button'; - message = mediaKeys[evt.key]; - } else { - return; - } - - switch (evt.type) { - case 'keydown': - message = message + '-press'; - break; - case 'keyup': - message = message + '-release'; - break; - } - - // Let applications receive the headset button and media key press/release message. - if (message !== this.lastHardwareButtonMessage) { - this.lastHardwareButtonMessage = message; - gSystemMessenger.broadcastMessage(type, message); - } - }, - - lastHardwareButtonMessage: null, // property for the hack above - visibleNormalAudioActive: false, - - handleEvent: function shell_handleEvent(evt) { - function checkReloadKey() { - if (evt.type !== 'keyup') { - return false; - } - - try { - let key = JSON.parse(Services.prefs.getCharPref('b2g.reload_key')); - return (evt.keyCode == key.key && - evt.ctrlKey == key.ctrl && - evt.altKey == key.alt && - evt.shiftKey == key.shift && - evt.metaKey == key.meta); - } catch(e) { - debug('Failed to get key: ' + e); - } - - return false; - } - - let content = this.contentBrowser.contentWindow; - switch (evt.type) { - case 'keydown': - case 'keyup': - if (checkReloadKey()) { - clearCacheAndReload(); - } else { - this.broadcastHardwareKeys(evt); - } - break; - case 'sizemodechange': - if (window.windowState == window.STATE_MINIMIZED && !this.visibleNormalAudioActive) { - this.contentBrowser.setVisible(false); - } else { - this.contentBrowser.setVisible(true); - } - break; - case 'load': - if (content.document.location == 'about:blank') { - return; - } - content.removeEventListener('load', this, true); - this.notifyContentWindowLoaded(); - 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 'mozbrowserscrollviewchange': - this.sendChromeEvent({ - type: 'scrollviewchange', - detail: evt.detail, - }); - break; - case 'mozbrowsercaretstatechanged': - { - let elt = evt.target; - let win = elt.ownerDocument.defaultView; - let offsetX = win.mozInnerScreenX - window.mozInnerScreenX; - let offsetY = win.mozInnerScreenY - window.mozInnerScreenY; - - let rect = elt.getBoundingClientRect(); - offsetX += rect.left; - offsetY += rect.top; - - let data = evt.detail; - data.offsetX = offsetX; - data.offsetY = offsetY; - data.sendDoCommandMsg = null; - - shell.sendChromeEvent({ - type: 'caretstatechanged', - detail: data, - }); - } - 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, principal, window); - } catch (e) { - dump('Error while creating offline cache: ' + e + '\n'); - } - break; - case 'MozAfterPaint': - window.removeEventListener('MozAfterPaint', this); - // This event should be sent before System app returns with - // system-message-listener-ready mozContentEvent, because it's on - // the critical launch path of the app. - SystemAppProxy._sendCustomEvent('mozChromeEvent', { - type: 'system-first-paint' - }, /* noPending */ true); - break; - case 'unload': - this.stop(); - break; - } - }, - - // Send an event to a specific window, document or element. - sendEvent: function shell_sendEvent(target, type, details) { - if (target === this.contentBrowser) { - // We must ask SystemAppProxy to send the event in this case so - // that event would be dispatched from frame.contentWindow instead of - // on the System app frame. - SystemAppProxy._sendCustomEvent(type, details); - return; - } - - 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) { - SystemAppProxy._sendCustomEvent(type, details); - }, - - sendChromeEvent: function shell_sendChromeEvent(details) { - this.sendCustomEvent("mozChromeEvent", details); - }, - - 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() { - window.performance.mark('gecko-shell-notify-content-start'); - this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true); - this.contentBrowser.removeEventListener('mozbrowserlocationchange', this, true); - - let content = this.contentBrowser.contentWindow; - content.addEventListener('load', this, true); - - this.reportCrash(true); - - SystemAppProxy.registerFrame(shell.contentBrowser); - - this.sendEvent(window, 'ContentStart'); - - Services.obs.notifyObservers(null, 'content-start', null); - - if (AppConstants.MOZ_GRAPHENE && - Services.prefs.getBoolPref("b2g.nativeWindowGeometry.fullscreen")) { - window.fullScreen = true; - } - - shell.handleCmdLine(); - }, - - handleCmdLine: function() { - // This isn't supported on devices. - if (!isGonk) { - let b2gcmds = Cc["@mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds"] - .getService(Ci.nsISupports); - let args = b2gcmds.wrappedJSObject.cmdLine; - try { - // Returns null if -url is not present. - let url = args.handleFlagWithParam("url", false); - if (url) { - this.sendChromeEvent({type: "mozbrowseropenwindow", url}); - args.preventDefault = true; - } - } catch(e) { - // Throws if -url is present with no params. - } - } - }, - - // This gets called when window.onload fires on the System app content window, - // which means things in <html> are parsed and statically referenced <script>s - // and <script defer>s are loaded and run. - notifyContentWindowLoaded: function shell_notifyContentWindowLoaded() { - isGonk && libcutils.property_set('sys.boot_completed', '1'); - - // This will cause Gonk Widget to remove boot animation from the screen - // and reveals the page. - Services.obs.notifyObservers(null, "browser-ui-startup-complete", ""); - - SystemAppProxy.setIsLoaded(); - }, - - // This gets called when the content sends us system-message-listener-ready - // mozContentEvent, OR when an observer message tell us we should consider - // the content as ready. - notifyEventListenerReady: function shell_notifyEventListenerReady() { - if (this._isEventListenerReady) { - Cu.reportError('shell.js: SystemApp has already been declared as being ready.'); - return; - } - this._isEventListenerReady = true; - - if (Services.prefs.getBoolPref('b2g.orientation.animate')) { - Cu.import('resource://gre/modules/OrientationChangeHandler.jsm'); - } - - SystemAppProxy.setIsReady(); - } -}; - -Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data) { - shell.sendChromeEvent({ type: "fullscreenoriginchange", - fullscreenorigin: data }); -}, "fullscreen-origin-change", false); - -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); - -Services.obs.addObserver(function(subject, topic, data) { - shell.notifyEventListenerReady(); -}, 'system-message-listener-ready', false); - -var permissionMap = new Map([ - ['unknown', Services.perms.UNKNOWN_ACTION], - ['allow', Services.perms.ALLOW_ACTION], - ['deny', Services.perms.DENY_ACTION], - ['prompt', Services.perms.PROMPT_ACTION], -]); -var permissionMapRev = new Map(Array.from(permissionMap.entries()).reverse()); - -var CustomEventManager = { - init: function custevt_init() { - window.addEventListener("ContentStart", (function(evt) { - let content = shell.contentBrowser.contentWindow; - content.addEventListener("mozContentEvent", this, false, true); - }).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 'system-message-listener-ready': - Services.obs.notifyObservers(null, 'system-message-listener-ready', null); - break; - case 'captive-portal-login-cancel': - CaptivePortalLoginHelper.handleEvent(detail); - break; - case 'inputmethod-update-layouts': - case 'inputregistry-add': - case 'inputregistry-remove': - KeyboardHelper.handleEvent(detail); - break; - case 'copypaste-do-command': - Services.obs.notifyObservers({ wrappedJSObject: shell.contentBrowser }, - 'ask-children-to-execute-copypaste-command', detail.cmd); - break; - case 'add-permission': - Services.perms.add(Services.io.newURI(detail.uri, null, null), - detail.permissionType, permissionMap.get(detail.permission)); - break; - case 'remove-permission': - Services.perms.remove(Services.io.newURI(detail.uri, null, null), - detail.permissionType); - break; - case 'test-permission': - let result = Services.perms.testExactPermission( - Services.io.newURI(detail.uri, null, null), detail.permissionType); - // Not equal check here because we want to prevent default only if it's not set - if (result !== permissionMapRev.get(detail.permission)) { - evt.preventDefault(); - } - break; - case 'shutdown-application': - let appStartup = Cc['@mozilla.org/toolkit/app-startup;1'] - .getService(Ci.nsIAppStartup); - appStartup.quit(appStartup.eAttemptQuit); - break; - case 'toggle-fullscreen-native-window': - window.fullScreen = !window.fullScreen; - Services.prefs.setBoolPref("b2g.nativeWindowGeometry.fullscreen", - window.fullScreen); - break; - case 'minimize-native-window': - window.minimize(); - break; - case 'clear-cache-and-reload': - clearCacheAndReload(); - break; - case 'clear-cache-and-restart': - clearCache(); - restart(); - break; - case 'restart': - restart(); - break; - } - } -} - -var KeyboardHelper = { - handleEvent: function keyboard_handleEvent(detail) { - switch (detail.type) { - case 'inputmethod-update-layouts': - Keyboard.setLayouts(detail.layouts); - - break; - case 'inputregistry-add': - case 'inputregistry-remove': - Keyboard.inputRegistryGlue.returnMessage(detail); - - break; - } - } -}; - -// 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 { - shell.sendChromeEvent({ - type: 'take-screenshot-success', - file: Screenshot.get() - }); - } 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); - Services.obs.addObserver(this, 'captive-portal-login-success', 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() { - if (!AppConstants.MOZ_UPDATER) { - return; - } - - 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); -}); -/* The "GPSChipOn" is to indicate that GPS engine is turned ON by the modem. - During this GPS engine is turned ON by the modem, we make the location tracking icon visible to user. - Once GPS engine is turned OFF, the location icon will disappear. - If GPS engine is not turned ON by the modem or GPS location service is triggered, - we let GPS service take over the control of showing the location tracking icon. - The regular sequence of the geolocation-device-events is: starting-> GPSStarting-> shutdown-> GPSShutdown -*/ - - -(function geolocationStatusTracker() { - let gGeolocationActive = false; - let GPSChipOn = false; - - Services.obs.addObserver(function(aSubject, aTopic, aData) { - let oldState = gGeolocationActive; - let promptWarning = false; - switch (aData) { - case "GPSStarting": - if (!gGeolocationActive) { - gGeolocationActive = true; - GPSChipOn = true; - promptWarning = true; - } - break; - case "GPSShutdown": - if (GPSChipOn) { - gGeolocationActive = false; - GPSChipOn = false; - } - break; - case "starting": - gGeolocationActive = true; - GPSChipOn = false; - break; - case "shutdown": - gGeolocationActive = false; - break; - } - - if (gGeolocationActive != oldState) { - shell.sendChromeEvent({ - type: 'geolocation-status', - active: gGeolocationActive, - prompt: promptWarning - }); - } -}, "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: - // {<processId>: {<requestURL>: {isApp: <isApp>, - // count: <N>, - // audioCount: <N>, - // videoCount: <N>}} - 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); -})(); - -if (isGonk) { - // 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; - - // keep the default value if it is smaller than the physical partition size. - let oldSize = Services.prefs.getIntPref("browser.cache.disk.capacity"); - if (size < oldSize) { - Services.prefs.setIntPref("browser.cache.disk.capacity", size); - } - })(); - - try { - let gmpService = Cc["@mozilla.org/gecko-media-plugin-service;1"] - .getService(Ci.mozIGeckoMediaPluginChromeService); - gmpService.addPluginDirectory("/system/b2g/gmp-clearkey/0.1"); - } catch(e) { - dump("Failed to add clearkey path! " + e + "\n"); - } -} - -// 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-change-telemetry' 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); - if (isGonk) { - 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); - } - } - } - }, - 'profile-before-change-telemetry', false); - - let appStartup = Cc['@mozilla.org/toolkit/app-startup;1'] - .getService(Ci.nsIAppStartup); - appStartup.quit(Ci.nsIAppStartup.eForceQuit); -}, 'b2g-reset-profile', false); - -var showInstallScreen; - -if (AppConstants.MOZ_GRAPHENE) { - const restoreWindowGeometry = () => { - let screenX = Services.prefs.getIntPref("b2g.nativeWindowGeometry.screenX"); - let screenY = Services.prefs.getIntPref("b2g.nativeWindowGeometry.screenY"); - let width = Services.prefs.getIntPref("b2g.nativeWindowGeometry.width"); - let height = Services.prefs.getIntPref("b2g.nativeWindowGeometry.height"); - - if (screenX == -1) { - // Center - screenX = (screen.width - width) / 2; - screenY = (screen.height - height) / 2; - } - - moveTo(screenX, screenY); - resizeTo(width, height); - } - restoreWindowGeometry(); - - const saveWindowGeometry = () => { - window.removeEventListener("unload", saveWindowGeometry); - Services.prefs.setIntPref("b2g.nativeWindowGeometry.screenX", screenX); - Services.prefs.setIntPref("b2g.nativeWindowGeometry.screenY", screenY); - Services.prefs.setIntPref("b2g.nativeWindowGeometry.width", outerWidth); - Services.prefs.setIntPref("b2g.nativeWindowGeometry.height", outerHeight); - } - window.addEventListener("unload", saveWindowGeometry); - - var baseWindow = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem) - .treeOwner - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIBaseWindow); - - const showNativeWindow = () => baseWindow.visibility = true; - const hideNativeWindow = () => baseWindow.visibility = false; - - showInstallScreen = () => { - const grapheneStrings = - Services.strings.createBundle('chrome://b2g-l10n/locale/graphene.properties'); - document.querySelector('#installing > .message').textContent = - grapheneStrings.GetStringFromName('installing'); - showNativeWindow(); - } - - const hideInstallScreen = () => { - document.body.classList.add('content-loaded'); - } - - window.addEventListener('ContentStart', () => { - shell.contentBrowser.contentWindow.addEventListener('load', () => { - hideInstallScreen(); - showNativeWindow(); - }); - }); - - hideNativeWindow(); -} |