1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/b2g/chrome/content/shell.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1618 @@ 1.4 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / 1.5 +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +Cu.import('resource://gre/modules/ContactService.jsm'); 1.11 +Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm'); 1.12 +Cu.import('resource://gre/modules/DataStoreChangeNotifier.jsm'); 1.13 +Cu.import('resource://gre/modules/AlarmService.jsm'); 1.14 +Cu.import('resource://gre/modules/ActivitiesService.jsm'); 1.15 +Cu.import('resource://gre/modules/PermissionPromptHelper.jsm'); 1.16 +Cu.import('resource://gre/modules/NotificationDB.jsm'); 1.17 +Cu.import('resource://gre/modules/Payment.jsm'); 1.18 +Cu.import("resource://gre/modules/AppsUtils.jsm"); 1.19 +Cu.import('resource://gre/modules/UserAgentOverrides.jsm'); 1.20 +Cu.import('resource://gre/modules/Keyboard.jsm'); 1.21 +Cu.import('resource://gre/modules/ErrorPage.jsm'); 1.22 +#ifdef MOZ_WIDGET_GONK 1.23 +Cu.import('resource://gre/modules/NetworkStatsService.jsm'); 1.24 +#endif 1.25 + 1.26 +// Identity 1.27 +Cu.import('resource://gre/modules/SignInToWebsite.jsm'); 1.28 +SignInToWebsiteController.init(); 1.29 + 1.30 +#ifdef MOZ_SERVICES_FXACCOUNTS 1.31 +Cu.import('resource://gre/modules/FxAccountsMgmtService.jsm'); 1.32 +#endif 1.33 + 1.34 +Cu.import('resource://gre/modules/DownloadsAPI.jsm'); 1.35 + 1.36 +XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", 1.37 + "resource://gre/modules/SystemAppProxy.jsm"); 1.38 + 1.39 +Cu.import('resource://gre/modules/Webapps.jsm'); 1.40 +DOMApplicationRegistry.allAppsLaunchable = true; 1.41 + 1.42 +XPCOMUtils.defineLazyServiceGetter(Services, 'env', 1.43 + '@mozilla.org/process/environment;1', 1.44 + 'nsIEnvironment'); 1.45 + 1.46 +XPCOMUtils.defineLazyServiceGetter(Services, 'ss', 1.47 + '@mozilla.org/content/style-sheet-service;1', 1.48 + 'nsIStyleSheetService'); 1.49 + 1.50 +XPCOMUtils.defineLazyServiceGetter(this, 'gSystemMessenger', 1.51 + '@mozilla.org/system-message-internal;1', 1.52 + 'nsISystemMessagesInternal'); 1.53 + 1.54 +XPCOMUtils.defineLazyServiceGetter(Services, 'fm', 1.55 + '@mozilla.org/focus-manager;1', 1.56 + 'nsIFocusManager'); 1.57 + 1.58 +XPCOMUtils.defineLazyGetter(this, 'DebuggerServer', function() { 1.59 + Cu.import('resource://gre/modules/devtools/dbg-server.jsm'); 1.60 + return DebuggerServer; 1.61 +}); 1.62 + 1.63 +XPCOMUtils.defineLazyGetter(this, "ppmm", function() { 1.64 + return Cc["@mozilla.org/parentprocessmessagemanager;1"] 1.65 + .getService(Ci.nsIMessageListenerManager); 1.66 +}); 1.67 + 1.68 +#ifdef MOZ_WIDGET_GONK 1.69 +XPCOMUtils.defineLazyGetter(this, "libcutils", function () { 1.70 + Cu.import("resource://gre/modules/systemlibs.js"); 1.71 + return libcutils; 1.72 +}); 1.73 +#endif 1.74 + 1.75 +#ifdef MOZ_CAPTIVEDETECT 1.76 +XPCOMUtils.defineLazyServiceGetter(Services, 'captivePortalDetector', 1.77 + '@mozilla.org/toolkit/captive-detector;1', 1.78 + 'nsICaptivePortalDetector'); 1.79 +#endif 1.80 + 1.81 +function getContentWindow() { 1.82 + return shell.contentBrowser.contentWindow; 1.83 +} 1.84 + 1.85 +function debug(str) { 1.86 + dump(' -*- Shell.js: ' + str + '\n'); 1.87 +} 1.88 + 1.89 +#ifdef MOZ_CRASHREPORTER 1.90 +function debugCrashReport(aStr) { 1.91 + dump('Crash reporter : ' + aStr); 1.92 +} 1.93 +#else 1.94 +function debugCrashReport(aStr) {} 1.95 +#endif 1.96 + 1.97 +var shell = { 1.98 + 1.99 + get CrashSubmit() { 1.100 + delete this.CrashSubmit; 1.101 +#ifdef MOZ_CRASHREPORTER 1.102 + Cu.import("resource://gre/modules/CrashSubmit.jsm", this); 1.103 + return this.CrashSubmit; 1.104 +#else 1.105 + dump('Crash reporter : disabled at build time.'); 1.106 + return this.CrashSubmit = null; 1.107 +#endif 1.108 + }, 1.109 + 1.110 + onlineForCrashReport: function shell_onlineForCrashReport() { 1.111 + let wifiManager = navigator.mozWifiManager; 1.112 + let onWifi = (wifiManager && 1.113 + (wifiManager.connection.status == 'connected')); 1.114 + return !Services.io.offline && onWifi; 1.115 + }, 1.116 + 1.117 + reportCrash: function shell_reportCrash(isChrome, aCrashID) { 1.118 + let crashID = aCrashID; 1.119 + try { 1.120 + // For chrome crashes, we want to report the lastRunCrashID. 1.121 + if (isChrome) { 1.122 + crashID = Cc["@mozilla.org/xre/app-info;1"] 1.123 + .getService(Ci.nsIXULRuntime).lastRunCrashID; 1.124 + } 1.125 + } catch(e) { 1.126 + debugCrashReport('Failed to fetch crash id. Crash ID is "' + crashID 1.127 + + '" Exception: ' + e); 1.128 + } 1.129 + 1.130 + // Bail if there isn't a valid crashID. 1.131 + if (!this.CrashSubmit || !crashID && !this.CrashSubmit.pendingIDs().length) { 1.132 + return; 1.133 + } 1.134 + 1.135 + // purge the queue. 1.136 + this.CrashSubmit.pruneSavedDumps(); 1.137 + 1.138 + // check for environment affecting crash reporting 1.139 + let env = Cc["@mozilla.org/process/environment;1"] 1.140 + .getService(Ci.nsIEnvironment); 1.141 + let shutdown = env.get("MOZ_CRASHREPORTER_SHUTDOWN"); 1.142 + if (shutdown) { 1.143 + let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"] 1.144 + .getService(Ci.nsIAppStartup); 1.145 + appStartup.quit(Ci.nsIAppStartup.eForceQuit); 1.146 + } 1.147 + 1.148 + let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT"); 1.149 + if (noReport) { 1.150 + return; 1.151 + } 1.152 + 1.153 + try { 1.154 + // Check if we should automatically submit this crash. 1.155 + if (Services.prefs.getBoolPref('app.reportCrashes')) { 1.156 + this.submitCrash(crashID); 1.157 + } else { 1.158 + this.deleteCrash(crashID); 1.159 + } 1.160 + } catch (e) { 1.161 + debugCrashReport('Can\'t fetch app.reportCrashes. Exception: ' + e); 1.162 + } 1.163 + 1.164 + // We can get here if we're just submitting old pending crashes. 1.165 + // Check that there's a valid crashID so that we only notify the 1.166 + // user if a crash just happened and not when we OOM. Bug 829477 1.167 + if (crashID) { 1.168 + this.sendChromeEvent({ 1.169 + type: "handle-crash", 1.170 + crashID: crashID, 1.171 + chrome: isChrome 1.172 + }); 1.173 + } 1.174 + }, 1.175 + 1.176 + deleteCrash: function shell_deleteCrash(aCrashID) { 1.177 + if (aCrashID) { 1.178 + debugCrashReport('Deleting pending crash: ' + aCrashID); 1.179 + shell.CrashSubmit.delete(aCrashID); 1.180 + } 1.181 + }, 1.182 + 1.183 + // this function submit the pending crashes. 1.184 + // make sure you are online. 1.185 + submitQueuedCrashes: function shell_submitQueuedCrashes() { 1.186 + // submit the pending queue. 1.187 + let pending = shell.CrashSubmit.pendingIDs(); 1.188 + for (let crashid of pending) { 1.189 + debugCrashReport('Submitting crash: ' + crashid); 1.190 + shell.CrashSubmit.submit(crashid); 1.191 + } 1.192 + }, 1.193 + 1.194 + // This function submits a crash when we're online. 1.195 + submitCrash: function shell_submitCrash(aCrashID) { 1.196 + if (this.onlineForCrashReport()) { 1.197 + this.submitQueuedCrashes(); 1.198 + return; 1.199 + } 1.200 + 1.201 + debugCrashReport('Not online, postponing.'); 1.202 + 1.203 + Services.obs.addObserver(function observer(subject, topic, state) { 1.204 + let network = subject.QueryInterface(Ci.nsINetworkInterface); 1.205 + if (network.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED 1.206 + && network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { 1.207 + shell.submitQueuedCrashes(); 1.208 + 1.209 + Services.obs.removeObserver(observer, topic); 1.210 + } 1.211 + }, "network-connection-state-changed", false); 1.212 + }, 1.213 + 1.214 + get contentBrowser() { 1.215 + delete this.contentBrowser; 1.216 + return this.contentBrowser = document.getElementById('systemapp'); 1.217 + }, 1.218 + 1.219 + get homeURL() { 1.220 + try { 1.221 + let homeSrc = Services.env.get('B2G_HOMESCREEN'); 1.222 + if (homeSrc) 1.223 + return homeSrc; 1.224 + } catch (e) {} 1.225 + 1.226 + return Services.prefs.getCharPref('browser.homescreenURL'); 1.227 + }, 1.228 + 1.229 + get manifestURL() { 1.230 + return Services.prefs.getCharPref('browser.manifestURL'); 1.231 + }, 1.232 + 1.233 + _started: false, 1.234 + hasStarted: function shell_hasStarted() { 1.235 + return this._started; 1.236 + }, 1.237 + 1.238 + start: function shell_start() { 1.239 + this._started = true; 1.240 + 1.241 + // This forces the initialization of the cookie service before we hit the 1.242 + // network. 1.243 + // See bug 810209 1.244 + let cookies = Cc["@mozilla.org/cookieService;1"]; 1.245 + 1.246 + try { 1.247 + let cr = Cc["@mozilla.org/xre/app-info;1"] 1.248 + .getService(Ci.nsICrashReporter); 1.249 + // Dogfood id. We might want to remove it in the future. 1.250 + // see bug 789466 1.251 + try { 1.252 + let dogfoodId = Services.prefs.getCharPref('prerelease.dogfood.id'); 1.253 + if (dogfoodId != "") { 1.254 + cr.annotateCrashReport("Email", dogfoodId); 1.255 + } 1.256 + } 1.257 + catch (e) { } 1.258 + 1.259 +#ifdef MOZ_WIDGET_GONK 1.260 + // Annotate crash report 1.261 + let annotations = [ [ "Android_Hardware", "ro.hardware" ], 1.262 + [ "Android_Device", "ro.product.device" ], 1.263 + [ "Android_CPU_ABI2", "ro.product.cpu.abi2" ], 1.264 + [ "Android_CPU_ABI", "ro.product.cpu.abi" ], 1.265 + [ "Android_Manufacturer", "ro.product.manufacturer" ], 1.266 + [ "Android_Brand", "ro.product.brand" ], 1.267 + [ "Android_Model", "ro.product.model" ], 1.268 + [ "Android_Board", "ro.product.board" ], 1.269 + ]; 1.270 + 1.271 + annotations.forEach(function (element) { 1.272 + cr.annotateCrashReport(element[0], libcutils.property_get(element[1])); 1.273 + }); 1.274 + 1.275 + let androidVersion = libcutils.property_get("ro.build.version.sdk") + 1.276 + "(" + libcutils.property_get("ro.build.version.codename") + ")"; 1.277 + cr.annotateCrashReport("Android_Version", androidVersion); 1.278 + 1.279 + SettingsListener.observe("deviceinfo.os", "", function(value) { 1.280 + try { 1.281 + let cr = Cc["@mozilla.org/xre/app-info;1"] 1.282 + .getService(Ci.nsICrashReporter); 1.283 + cr.annotateCrashReport("B2G_OS_Version", value); 1.284 + } catch(e) { } 1.285 + }); 1.286 +#endif 1.287 + } catch(e) { 1.288 + debugCrashReport('exception: ' + e); 1.289 + } 1.290 + 1.291 + let homeURL = this.homeURL; 1.292 + if (!homeURL) { 1.293 + let msg = 'Fatal error during startup: No homescreen found: try setting B2G_HOMESCREEN'; 1.294 + alert(msg); 1.295 + return; 1.296 + } 1.297 + let manifestURL = this.manifestURL; 1.298 + // <html:iframe id="systemapp" 1.299 + // mozbrowser="true" allowfullscreen="true" 1.300 + // style="overflow: hidden; height: 100%; width: 100%; border: none;" 1.301 + // src="data:text/html;charset=utf-8,%3C!DOCTYPE html>%3Cbody style='background:black;'>"/> 1.302 + let systemAppFrame = 1.303 + document.createElementNS('http://www.w3.org/1999/xhtml', 'html:iframe'); 1.304 + systemAppFrame.setAttribute('id', 'systemapp'); 1.305 + systemAppFrame.setAttribute('mozbrowser', 'true'); 1.306 + systemAppFrame.setAttribute('mozapp', manifestURL); 1.307 + systemAppFrame.setAttribute('allowfullscreen', 'true'); 1.308 + systemAppFrame.setAttribute('style', "overflow: hidden; height: 100%; width: 100%; border: none;"); 1.309 + systemAppFrame.setAttribute('src', "data:text/html;charset=utf-8,%3C!DOCTYPE html>%3Cbody style='background:black;"); 1.310 + let container = document.getElementById('container'); 1.311 +#ifdef MOZ_WIDGET_COCOA 1.312 + // See shell.html 1.313 + let hotfix = document.getElementById('placeholder'); 1.314 + if (hotfix) { 1.315 + container.removeChild(hotfix); 1.316 + } 1.317 +#endif 1.318 + container.appendChild(systemAppFrame); 1.319 + 1.320 + systemAppFrame.contentWindow 1.321 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.322 + .getInterface(Ci.nsIWebNavigation) 1.323 + .sessionHistory = Cc["@mozilla.org/browser/shistory;1"] 1.324 + .createInstance(Ci.nsISHistory); 1.325 + 1.326 + // On firefox mulet, shell.html is loaded in a tab 1.327 + // and we have to listen on the chrome event handler 1.328 + // to catch key events 1.329 + let chromeEventHandler = window.QueryInterface(Ci.nsIInterfaceRequestor) 1.330 + .getInterface(Ci.nsIWebNavigation) 1.331 + .QueryInterface(Ci.nsIDocShell) 1.332 + .chromeEventHandler || window; 1.333 + // Capture all key events so we can filter out hardware buttons 1.334 + // And send them to Gaia via mozChromeEvents. 1.335 + // Ideally, hardware buttons wouldn't generate key events at all, or 1.336 + // if they did, they would use keycodes that conform to DOM 3 Events. 1.337 + // See discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=762362 1.338 + chromeEventHandler.addEventListener('keydown', this, true); 1.339 + chromeEventHandler.addEventListener('keypress', this, true); 1.340 + chromeEventHandler.addEventListener('keyup', this, true); 1.341 + 1.342 + window.addEventListener('MozApplicationManifest', this); 1.343 + window.addEventListener('mozfullscreenchange', this); 1.344 + window.addEventListener('MozAfterPaint', this); 1.345 + window.addEventListener('sizemodechange', this); 1.346 + window.addEventListener('unload', this); 1.347 + this.contentBrowser.addEventListener('mozbrowserloadstart', this, true); 1.348 + 1.349 + SystemAppProxy.registerFrame(this.contentBrowser); 1.350 + 1.351 + CustomEventManager.init(); 1.352 + WebappsHelper.init(); 1.353 + UserAgentOverrides.init(); 1.354 + IndexedDBPromptHelper.init(); 1.355 + CaptivePortalLoginHelper.init(); 1.356 + 1.357 + this.contentBrowser.src = homeURL; 1.358 + this.isHomeLoaded = false; 1.359 + 1.360 + ppmm.addMessageListener("content-handler", this); 1.361 + ppmm.addMessageListener("dial-handler", this); 1.362 + ppmm.addMessageListener("sms-handler", this); 1.363 + ppmm.addMessageListener("mail-handler", this); 1.364 + ppmm.addMessageListener("app-notification-send", AlertsHelper); 1.365 + ppmm.addMessageListener("file-picker", this); 1.366 + ppmm.addMessageListener("getProfD", function(message) { 1.367 + return Services.dirsvc.get("ProfD", Ci.nsIFile).path; 1.368 + }); 1.369 + }, 1.370 + 1.371 + stop: function shell_stop() { 1.372 + window.removeEventListener('unload', this); 1.373 + window.removeEventListener('keydown', this, true); 1.374 + window.removeEventListener('keypress', this, true); 1.375 + window.removeEventListener('keyup', this, true); 1.376 + window.removeEventListener('MozApplicationManifest', this); 1.377 + window.removeEventListener('mozfullscreenchange', this); 1.378 + window.removeEventListener('sizemodechange', this); 1.379 + this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true); 1.380 + ppmm.removeMessageListener("content-handler", this); 1.381 + if (this.timer) { 1.382 + this.timer.cancel(); 1.383 + this.timer = null; 1.384 + } 1.385 + 1.386 + UserAgentOverrides.uninit(); 1.387 + IndexedDBPromptHelper.uninit(); 1.388 + }, 1.389 + 1.390 + // If this key event actually represents a hardware button, filter it here 1.391 + // and send a mozChromeEvent with detail.type set to xxx-button-press or 1.392 + // xxx-button-release instead. 1.393 + filterHardwareKeys: function shell_filterHardwareKeys(evt) { 1.394 + var type; 1.395 + switch (evt.keyCode) { 1.396 + case evt.DOM_VK_HOME: // Home button 1.397 + type = 'home-button'; 1.398 + break; 1.399 + case evt.DOM_VK_SLEEP: // Sleep button 1.400 + case evt.DOM_VK_END: // On desktop we don't have a sleep button 1.401 + type = 'sleep-button'; 1.402 + break; 1.403 + case evt.DOM_VK_PAGE_UP: // Volume up button 1.404 + type = 'volume-up-button'; 1.405 + break; 1.406 + case evt.DOM_VK_PAGE_DOWN: // Volume down button 1.407 + type = 'volume-down-button'; 1.408 + break; 1.409 + case evt.DOM_VK_ESCAPE: // Back button (should be disabled) 1.410 + type = 'back-button'; 1.411 + break; 1.412 + case evt.DOM_VK_CONTEXT_MENU: // Menu button 1.413 + type = 'menu-button'; 1.414 + break; 1.415 + case evt.DOM_VK_F1: // headset button 1.416 + type = 'headset-button'; 1.417 + break; 1.418 + } 1.419 + 1.420 + let mediaKeys = { 1.421 + 'MediaNextTrack': 'media-next-track-button', 1.422 + 'MediaPreviousTrack': 'media-previous-track-button', 1.423 + 'MediaPause': 'media-pause-button', 1.424 + 'MediaPlay': 'media-play-button', 1.425 + 'MediaPlayPause': 'media-play-pause-button', 1.426 + 'MediaStop': 'media-stop-button', 1.427 + 'MediaRewind': 'media-rewind-button', 1.428 + 'FastFwd': 'media-fast-forward-button' 1.429 + }; 1.430 + 1.431 + let isMediaKey = false; 1.432 + if (mediaKeys[evt.key]) { 1.433 + isMediaKey = true; 1.434 + type = mediaKeys[evt.key]; 1.435 + } 1.436 + 1.437 + if (!type) { 1.438 + return; 1.439 + } 1.440 + 1.441 + // If we didn't return, then the key event represents a hardware key 1.442 + // and we need to prevent it from propagating to Gaia 1.443 + evt.stopImmediatePropagation(); 1.444 + evt.preventDefault(); // Prevent keypress events (when #501496 is fixed). 1.445 + 1.446 + // If it is a key down or key up event, we send a chrome event to Gaia. 1.447 + // If it is a keypress event we just ignore it. 1.448 + switch (evt.type) { 1.449 + case 'keydown': 1.450 + type = type + '-press'; 1.451 + break; 1.452 + case 'keyup': 1.453 + type = type + '-release'; 1.454 + break; 1.455 + case 'keypress': 1.456 + return; 1.457 + } 1.458 + 1.459 + // Let applications receive the headset button key press/release event. 1.460 + if (evt.keyCode == evt.DOM_VK_F1 && type !== this.lastHardwareButtonEventType) { 1.461 + this.lastHardwareButtonEventType = type; 1.462 + gSystemMessenger.broadcastMessage('headset-button', type); 1.463 + return; 1.464 + } 1.465 + 1.466 + if (isMediaKey) { 1.467 + this.lastHardwareButtonEventType = type; 1.468 + gSystemMessenger.broadcastMessage('media-button', type); 1.469 + return; 1.470 + } 1.471 + 1.472 + // On my device, the physical hardware buttons (sleep and volume) 1.473 + // send multiple events (press press release release), but the 1.474 + // soft home button just sends one. This hack is to manually 1.475 + // "debounce" the keys. If the type of this event is the same as 1.476 + // the type of the last one, then don't send it. We'll never send 1.477 + // two presses or two releases in a row. 1.478 + // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=761067 1.479 + if (type !== this.lastHardwareButtonEventType) { 1.480 + this.lastHardwareButtonEventType = type; 1.481 + this.sendChromeEvent({type: type}); 1.482 + } 1.483 + }, 1.484 + 1.485 + lastHardwareButtonEventType: null, // property for the hack above 1.486 + needBufferOpenAppReq: true, 1.487 + bufferedOpenAppReqs: [], 1.488 + timer: null, 1.489 + visibleNormalAudioActive: false, 1.490 + 1.491 + handleEvent: function shell_handleEvent(evt) { 1.492 + let content = this.contentBrowser.contentWindow; 1.493 + switch (evt.type) { 1.494 + case 'keydown': 1.495 + case 'keyup': 1.496 + case 'keypress': 1.497 + this.filterHardwareKeys(evt); 1.498 + break; 1.499 + case 'mozfullscreenchange': 1.500 + // When the screen goes fullscreen make sure to set the focus to the 1.501 + // main window so noboby can prevent the ESC key to get out fullscreen 1.502 + // mode 1.503 + if (document.mozFullScreen) 1.504 + Services.fm.focusedWindow = window; 1.505 + break; 1.506 + case 'sizemodechange': 1.507 + if (window.windowState == window.STATE_MINIMIZED && !this.visibleNormalAudioActive) { 1.508 + this.contentBrowser.setVisible(false); 1.509 + } else { 1.510 + this.contentBrowser.setVisible(true); 1.511 + } 1.512 + break; 1.513 + case 'mozbrowserloadstart': 1.514 + if (content.document.location == 'about:blank') { 1.515 + this.contentBrowser.addEventListener('mozbrowserlocationchange', this, true); 1.516 + return; 1.517 + } 1.518 + 1.519 + this.notifyContentStart(); 1.520 + break; 1.521 + case 'mozbrowserlocationchange': 1.522 + if (content.document.location == 'about:blank') { 1.523 + return; 1.524 + } 1.525 + 1.526 + this.notifyContentStart(); 1.527 + break; 1.528 + 1.529 + case 'MozApplicationManifest': 1.530 + try { 1.531 + if (!Services.prefs.getBoolPref('browser.cache.offline.enable')) 1.532 + return; 1.533 + 1.534 + let contentWindow = evt.originalTarget.defaultView; 1.535 + let documentElement = contentWindow.document.documentElement; 1.536 + if (!documentElement) 1.537 + return; 1.538 + 1.539 + let manifest = documentElement.getAttribute('manifest'); 1.540 + if (!manifest) 1.541 + return; 1.542 + 1.543 + let principal = contentWindow.document.nodePrincipal; 1.544 + if (Services.perms.testPermissionFromPrincipal(principal, 'offline-app') == Ci.nsIPermissionManager.UNKNOWN_ACTION) { 1.545 + if (Services.prefs.getBoolPref('browser.offline-apps.notify')) { 1.546 + // FIXME Bug 710729 - Add a UI for offline cache notifications 1.547 + return; 1.548 + } 1.549 + return; 1.550 + } 1.551 + 1.552 + Services.perms.addFromPrincipal(principal, 'offline-app', 1.553 + Ci.nsIPermissionManager.ALLOW_ACTION); 1.554 + 1.555 + let documentURI = Services.io.newURI(contentWindow.document.documentURI, 1.556 + null, 1.557 + null); 1.558 + let manifestURI = Services.io.newURI(manifest, null, documentURI); 1.559 + let updateService = Cc['@mozilla.org/offlinecacheupdate-service;1'] 1.560 + .getService(Ci.nsIOfflineCacheUpdateService); 1.561 + updateService.scheduleUpdate(manifestURI, documentURI, window); 1.562 + } catch (e) { 1.563 + dump('Error while creating offline cache: ' + e + '\n'); 1.564 + } 1.565 + break; 1.566 + case 'MozAfterPaint': 1.567 + window.removeEventListener('MozAfterPaint', this); 1.568 + this.sendChromeEvent({ 1.569 + type: 'system-first-paint' 1.570 + }); 1.571 + break; 1.572 + case 'unload': 1.573 + this.stop(); 1.574 + break; 1.575 + } 1.576 + }, 1.577 + 1.578 + // Send an event to a specific window, document or element. 1.579 + sendEvent: function shell_sendEvent(target, type, details) { 1.580 + let doc = target.document || target.ownerDocument || target; 1.581 + let event = doc.createEvent('CustomEvent'); 1.582 + event.initCustomEvent(type, true, true, details ? details : {}); 1.583 + target.dispatchEvent(event); 1.584 + }, 1.585 + 1.586 + sendCustomEvent: function shell_sendCustomEvent(type, details) { 1.587 + let target = getContentWindow(); 1.588 + let payload = details ? Cu.cloneInto(details, target) : {}; 1.589 + this.sendEvent(target, type, payload); 1.590 + }, 1.591 + 1.592 + sendChromeEvent: function shell_sendChromeEvent(details) { 1.593 + if (!this.isHomeLoaded) { 1.594 + if (!('pendingChromeEvents' in this)) { 1.595 + this.pendingChromeEvents = []; 1.596 + } 1.597 + 1.598 + this.pendingChromeEvents.push(details); 1.599 + return; 1.600 + } 1.601 + 1.602 + this.sendEvent(getContentWindow(), "mozChromeEvent", 1.603 + Cu.cloneInto(details, getContentWindow())); 1.604 + }, 1.605 + 1.606 + openAppForSystemMessage: function shell_openAppForSystemMessage(msg) { 1.607 + let payload = { 1.608 + url: msg.pageURL, 1.609 + manifestURL: msg.manifestURL, 1.610 + isActivity: (msg.type == 'activity'), 1.611 + onlyShowApp: msg.onlyShowApp, 1.612 + showApp: msg.showApp, 1.613 + target: msg.target, 1.614 + expectingSystemMessage: true, 1.615 + extra: msg.extra 1.616 + } 1.617 + this.sendCustomEvent('open-app', payload); 1.618 + }, 1.619 + 1.620 + receiveMessage: function shell_receiveMessage(message) { 1.621 + var activities = { 'content-handler': { name: 'view', response: null }, 1.622 + 'dial-handler': { name: 'dial', response: null }, 1.623 + 'mail-handler': { name: 'new', response: null }, 1.624 + 'sms-handler': { name: 'new', response: null }, 1.625 + 'file-picker': { name: 'pick', response: 'file-picked' } }; 1.626 + 1.627 + if (!(message.name in activities)) 1.628 + return; 1.629 + 1.630 + let data = message.data; 1.631 + let activity = activities[message.name]; 1.632 + 1.633 + let a = new MozActivity({ 1.634 + name: activity.name, 1.635 + data: data 1.636 + }); 1.637 + 1.638 + if (activity.response) { 1.639 + a.onsuccess = function() { 1.640 + let sender = message.target.QueryInterface(Ci.nsIMessageSender); 1.641 + sender.sendAsyncMessage(activity.response, { success: true, 1.642 + result: a.result }); 1.643 + } 1.644 + a.onerror = function() { 1.645 + let sender = message.target.QueryInterface(Ci.nsIMessageSender); 1.646 + sender.sendAsyncMessage(activity.response, { success: false }); 1.647 + } 1.648 + } 1.649 + }, 1.650 + 1.651 + notifyContentStart: function shell_notifyContentStart() { 1.652 + this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true); 1.653 + this.contentBrowser.removeEventListener('mozbrowserlocationchange', this, true); 1.654 + 1.655 + let content = this.contentBrowser.contentWindow; 1.656 + 1.657 + this.reportCrash(true); 1.658 + 1.659 + this.sendEvent(window, 'ContentStart'); 1.660 + 1.661 + Services.obs.notifyObservers(null, 'content-start', null); 1.662 + 1.663 +#ifdef MOZ_WIDGET_GONK 1.664 + Cu.import('resource://gre/modules/OperatorApps.jsm'); 1.665 +#endif 1.666 + 1.667 + content.addEventListener('load', function shell_homeLoaded() { 1.668 + content.removeEventListener('load', shell_homeLoaded); 1.669 + shell.isHomeLoaded = true; 1.670 + 1.671 +#ifdef MOZ_WIDGET_GONK 1.672 + libcutils.property_set('sys.boot_completed', '1'); 1.673 +#endif 1.674 + 1.675 + Services.obs.notifyObservers(null, "browser-ui-startup-complete", ""); 1.676 + 1.677 + SystemAppProxy.setIsReady(); 1.678 + if ('pendingChromeEvents' in shell) { 1.679 + shell.pendingChromeEvents.forEach((shell.sendChromeEvent).bind(shell)); 1.680 + } 1.681 + delete shell.pendingChromeEvents; 1.682 + }); 1.683 + } 1.684 +}; 1.685 + 1.686 +// Listen for the request of opening app and relay them to Gaia. 1.687 +Services.obs.addObserver(function onSystemMessageOpenApp(subject, topic, data) { 1.688 + let msg = JSON.parse(data); 1.689 + // Buffer non-activity request until content starts to load for 10 seconds. 1.690 + // We'll revisit this later if new kind of requests don't need to be cached. 1.691 + if (shell.needBufferOpenAppReq && msg.type !== 'activity') { 1.692 + shell.bufferedOpenAppReqs.push(msg); 1.693 + return; 1.694 + } 1.695 + shell.openAppForSystemMessage(msg); 1.696 +}, 'system-messages-open-app', false); 1.697 + 1.698 +Services.obs.addObserver(function onInterAppCommConnect(subject, topic, data) { 1.699 + data = JSON.parse(data); 1.700 + shell.sendChromeEvent({ type: "inter-app-comm-permission", 1.701 + chromeEventID: data.callerID, 1.702 + manifestURL: data.manifestURL, 1.703 + keyword: data.keyword, 1.704 + peers: data.appsToSelect }); 1.705 +}, 'inter-app-comm-select-app', false); 1.706 + 1.707 +Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data) { 1.708 + shell.sendChromeEvent({ type: "fullscreenoriginchange", 1.709 + fullscreenorigin: data }); 1.710 +}, "fullscreen-origin-change", false); 1.711 + 1.712 +DOMApplicationRegistry.registryStarted.then(function () { 1.713 + shell.sendChromeEvent({ type: 'webapps-registry-start' }); 1.714 +}); 1.715 +DOMApplicationRegistry.registryReady.then(function () { 1.716 + shell.sendChromeEvent({ type: 'webapps-registry-ready' }); 1.717 +}); 1.718 + 1.719 +Services.obs.addObserver(function onBluetoothVolumeChange(subject, topic, data) { 1.720 + shell.sendChromeEvent({ 1.721 + type: "bluetooth-volumeset", 1.722 + value: data 1.723 + }); 1.724 +}, 'bluetooth-volume-change', false); 1.725 + 1.726 +Services.obs.addObserver(function(subject, topic, data) { 1.727 + shell.sendCustomEvent('mozmemorypressure'); 1.728 +}, 'memory-pressure', false); 1.729 + 1.730 +var CustomEventManager = { 1.731 + init: function custevt_init() { 1.732 + window.addEventListener("ContentStart", (function(evt) { 1.733 + let content = shell.contentBrowser.contentWindow; 1.734 + content.addEventListener("mozContentEvent", this, false, true); 1.735 + 1.736 + // After content starts to load for 10 seconds, send and 1.737 + // clean up the buffered open-app requests if there is any. 1.738 + // 1.739 + // TODO: Bug 793420 - Remove the waiting timer for the 'open-app' 1.740 + // mozChromeEvents requested by System Message 1.741 + shell.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.742 + shell.timer.initWithCallback(function timerCallback() { 1.743 + shell.bufferedOpenAppReqs.forEach(function bufferOpenAppReq(msg) { 1.744 + shell.openAppForSystemMessage(msg); 1.745 + }); 1.746 + shell.bufferedOpenAppReqs.length = 0; 1.747 + shell.needBufferOpenAppReq = false; 1.748 + shell.timer = null; 1.749 + }, 10000, Ci.nsITimer.TYPE_ONE_SHOT); 1.750 + }).bind(this), false); 1.751 + }, 1.752 + 1.753 + handleEvent: function custevt_handleEvent(evt) { 1.754 + let detail = evt.detail; 1.755 + dump('XXX FIXME : Got a mozContentEvent: ' + detail.type + "\n"); 1.756 + 1.757 + switch(detail.type) { 1.758 + case 'desktop-notification-show': 1.759 + case 'desktop-notification-click': 1.760 + case 'desktop-notification-close': 1.761 + AlertsHelper.handleEvent(detail); 1.762 + break; 1.763 + case 'webapps-install-granted': 1.764 + case 'webapps-install-denied': 1.765 + WebappsHelper.handleEvent(detail); 1.766 + break; 1.767 + case 'select-choicechange': 1.768 + FormsHelper.handleEvent(detail); 1.769 + break; 1.770 + case 'system-message-listener-ready': 1.771 + Services.obs.notifyObservers(null, 'system-message-listener-ready', null); 1.772 + break; 1.773 + case 'remote-debugger-prompt': 1.774 + RemoteDebugger.handleEvent(detail); 1.775 + break; 1.776 + case 'captive-portal-login-cancel': 1.777 + CaptivePortalLoginHelper.handleEvent(detail); 1.778 + break; 1.779 + case 'inter-app-comm-permission': 1.780 + Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result', 1.781 + JSON.stringify({ callerID: detail.chromeEventID, 1.782 + keyword: detail.keyword, 1.783 + manifestURL: detail.manifestURL, 1.784 + selectedApps: detail.peers })); 1.785 + break; 1.786 + case 'inputmethod-update-layouts': 1.787 + KeyboardHelper.handleEvent(detail); 1.788 + break; 1.789 + } 1.790 + } 1.791 +} 1.792 + 1.793 +var AlertsHelper = { 1.794 + _listeners: {}, 1.795 + _count: 0, 1.796 + 1.797 + handleEvent: function alert_handleEvent(detail) { 1.798 + if (!detail || !detail.id) 1.799 + return; 1.800 + 1.801 + let uid = detail.id; 1.802 + let listener = this._listeners[uid]; 1.803 + if (!listener) 1.804 + return; 1.805 + 1.806 + let topic; 1.807 + if (detail.type == "desktop-notification-click") { 1.808 + topic = "alertclickcallback"; 1.809 + } else if (detail.type == "desktop-notification-show") { 1.810 + topic = "alertshow"; 1.811 + } else { 1.812 + /* desktop-notification-close */ 1.813 + topic = "alertfinished"; 1.814 + } 1.815 + 1.816 + if (uid.startsWith("alert")) { 1.817 + try { 1.818 + listener.observer.observe(null, topic, listener.cookie); 1.819 + } catch (e) { } 1.820 + } else { 1.821 + try { 1.822 + listener.mm.sendAsyncMessage("app-notification-return", { 1.823 + uid: uid, 1.824 + topic: topic, 1.825 + target: listener.target 1.826 + }); 1.827 + } catch (e) { 1.828 + // we get an exception if the app is not launched yet 1.829 + gSystemMessenger.sendMessage("notification", { 1.830 + clicked: (detail.type === "desktop-notification-click"), 1.831 + title: listener.title, 1.832 + body: listener.text, 1.833 + imageURL: listener.imageURL, 1.834 + lang: listener.lang, 1.835 + dir: listener.dir, 1.836 + id: listener.id, 1.837 + tag: listener.tag 1.838 + }, 1.839 + Services.io.newURI(listener.target, null, null), 1.840 + Services.io.newURI(listener.manifestURL, null, null) 1.841 + ); 1.842 + } 1.843 + } 1.844 + 1.845 + // we're done with this notification 1.846 + if (topic === "alertfinished") { 1.847 + delete this._listeners[uid]; 1.848 + } 1.849 + }, 1.850 + 1.851 + registerListener: function alert_registerListener(alertId, cookie, alertListener) { 1.852 + this._listeners[alertId] = { observer: alertListener, cookie: cookie }; 1.853 + }, 1.854 + 1.855 + registerAppListener: function alert_registerAppListener(uid, listener) { 1.856 + this._listeners[uid] = listener; 1.857 + 1.858 + let app = DOMApplicationRegistry.getAppByManifestURL(listener.manifestURL); 1.859 + DOMApplicationRegistry.getManifestFor(app.manifestURL).then((manifest) => { 1.860 + let helper = new ManifestHelper(manifest, app.origin); 1.861 + let getNotificationURLFor = function(messages) { 1.862 + if (!messages) 1.863 + return null; 1.864 + 1.865 + for (let i = 0; i < messages.length; i++) { 1.866 + let message = messages[i]; 1.867 + if (message === "notification") { 1.868 + return helper.fullLaunchPath(); 1.869 + } else if (typeof message == "object" && "notification" in message) { 1.870 + return helper.resolveFromOrigin(message["notification"]); 1.871 + } 1.872 + } 1.873 + 1.874 + // No message found... 1.875 + return null; 1.876 + } 1.877 + 1.878 + listener.target = getNotificationURLFor(manifest.messages); 1.879 + 1.880 + // Bug 816944 - Support notification messages for entry_points. 1.881 + }); 1.882 + }, 1.883 + 1.884 + showNotification: function alert_showNotification(imageURL, 1.885 + title, 1.886 + text, 1.887 + textClickable, 1.888 + cookie, 1.889 + uid, 1.890 + bidi, 1.891 + lang, 1.892 + manifestURL) { 1.893 + function send(appName, appIcon) { 1.894 + shell.sendChromeEvent({ 1.895 + type: "desktop-notification", 1.896 + id: uid, 1.897 + icon: imageURL, 1.898 + title: title, 1.899 + text: text, 1.900 + bidi: bidi, 1.901 + lang: lang, 1.902 + appName: appName, 1.903 + appIcon: appIcon, 1.904 + manifestURL: manifestURL 1.905 + }); 1.906 + } 1.907 + 1.908 + if (!manifestURL || !manifestURL.length) { 1.909 + send(null, null); 1.910 + return; 1.911 + } 1.912 + 1.913 + // If we have a manifest URL, get the icon and title from the manifest 1.914 + // to prevent spoofing. 1.915 + let app = DOMApplicationRegistry.getAppByManifestURL(manifestURL); 1.916 + DOMApplicationRegistry.getManifestFor(manifestURL).then((aManifest) => { 1.917 + let helper = new ManifestHelper(aManifest, app.origin); 1.918 + send(helper.name, helper.iconURLForSize(128)); 1.919 + }); 1.920 + }, 1.921 + 1.922 + showAlertNotification: function alert_showAlertNotification(imageURL, 1.923 + title, 1.924 + text, 1.925 + textClickable, 1.926 + cookie, 1.927 + alertListener, 1.928 + name, 1.929 + bidi, 1.930 + lang) { 1.931 + let currentListener = this._listeners[name]; 1.932 + if (currentListener) { 1.933 + currentListener.observer.observe(null, "alertfinished", currentListener.cookie); 1.934 + } 1.935 + 1.936 + this.registerListener(name, cookie, alertListener); 1.937 + this.showNotification(imageURL, title, text, textClickable, cookie, 1.938 + name, bidi, lang, null); 1.939 + }, 1.940 + 1.941 + closeAlert: function alert_closeAlert(name) { 1.942 + shell.sendChromeEvent({ 1.943 + type: "desktop-notification-close", 1.944 + id: name 1.945 + }); 1.946 + }, 1.947 + 1.948 + receiveMessage: function alert_receiveMessage(aMessage) { 1.949 + if (!aMessage.target.assertAppHasPermission("desktop-notification")) { 1.950 + Cu.reportError("Desktop-notification message " + aMessage.name + 1.951 + " from a content process with no desktop-notification privileges."); 1.952 + return; 1.953 + } 1.954 + 1.955 + let data = aMessage.data; 1.956 + let details = data.details; 1.957 + let listener = { 1.958 + mm: aMessage.target, 1.959 + title: data.title, 1.960 + text: data.text, 1.961 + manifestURL: details.manifestURL, 1.962 + imageURL: data.imageURL, 1.963 + lang: details.lang || undefined, 1.964 + id: details.id || undefined, 1.965 + dir: details.dir || undefined, 1.966 + tag: details.tag || undefined 1.967 + }; 1.968 + this.registerAppListener(data.uid, listener); 1.969 + 1.970 + this.showNotification(data.imageURL, data.title, data.text, 1.971 + details.textClickable, null, 1.972 + data.uid, details.dir, 1.973 + details.lang, details.manifestURL); 1.974 + }, 1.975 +} 1.976 + 1.977 +var WebappsHelper = { 1.978 + _installers: {}, 1.979 + _count: 0, 1.980 + 1.981 + init: function webapps_init() { 1.982 + Services.obs.addObserver(this, "webapps-launch", false); 1.983 + Services.obs.addObserver(this, "webapps-ask-install", false); 1.984 + Services.obs.addObserver(this, "webapps-close", false); 1.985 + }, 1.986 + 1.987 + registerInstaller: function webapps_registerInstaller(data) { 1.988 + let id = "installer" + this._count++; 1.989 + this._installers[id] = data; 1.990 + return id; 1.991 + }, 1.992 + 1.993 + handleEvent: function webapps_handleEvent(detail) { 1.994 + if (!detail || !detail.id) 1.995 + return; 1.996 + 1.997 + let installer = this._installers[detail.id]; 1.998 + delete this._installers[detail.id]; 1.999 + switch (detail.type) { 1.1000 + case "webapps-install-granted": 1.1001 + DOMApplicationRegistry.confirmInstall(installer); 1.1002 + break; 1.1003 + case "webapps-install-denied": 1.1004 + DOMApplicationRegistry.denyInstall(installer); 1.1005 + break; 1.1006 + } 1.1007 + }, 1.1008 + 1.1009 + observe: function webapps_observe(subject, topic, data) { 1.1010 + let json = JSON.parse(data); 1.1011 + json.mm = subject; 1.1012 + 1.1013 + switch(topic) { 1.1014 + case "webapps-launch": 1.1015 + DOMApplicationRegistry.getManifestFor(json.manifestURL).then((aManifest) => { 1.1016 + if (!aManifest) 1.1017 + return; 1.1018 + 1.1019 + let manifest = new ManifestHelper(aManifest, json.origin); 1.1020 + let payload = { 1.1021 + __exposedProps__: { 1.1022 + timestamp: "r", 1.1023 + url: "r", 1.1024 + manifestURL: "r" 1.1025 + }, 1.1026 + timestamp: json.timestamp, 1.1027 + url: manifest.fullLaunchPath(json.startPoint), 1.1028 + manifestURL: json.manifestURL 1.1029 + } 1.1030 + shell.sendEvent(getContentWindow(), "webapps-launch", payload); 1.1031 + }); 1.1032 + break; 1.1033 + case "webapps-ask-install": 1.1034 + let id = this.registerInstaller(json); 1.1035 + shell.sendChromeEvent({ 1.1036 + type: "webapps-ask-install", 1.1037 + id: id, 1.1038 + app: json.app 1.1039 + }); 1.1040 + break; 1.1041 + case "webapps-close": 1.1042 + shell.sendEvent(getContentWindow(), "webapps-close", 1.1043 + { 1.1044 + __exposedProps__: { "manifestURL": "r" }, 1.1045 + "manifestURL": json.manifestURL 1.1046 + }); 1.1047 + break; 1.1048 + } 1.1049 + } 1.1050 +} 1.1051 + 1.1052 +let IndexedDBPromptHelper = { 1.1053 + _quotaPrompt: "indexedDB-quota-prompt", 1.1054 + _quotaResponse: "indexedDB-quota-response", 1.1055 + 1.1056 + init: 1.1057 + function IndexedDBPromptHelper_init() { 1.1058 + Services.obs.addObserver(this, this._quotaPrompt, false); 1.1059 + }, 1.1060 + 1.1061 + uninit: 1.1062 + function IndexedDBPromptHelper_uninit() { 1.1063 + Services.obs.removeObserver(this, this._quotaPrompt); 1.1064 + }, 1.1065 + 1.1066 + observe: 1.1067 + function IndexedDBPromptHelper_observe(subject, topic, data) { 1.1068 + if (topic != this._quotaPrompt) { 1.1069 + throw new Error("Unexpected topic!"); 1.1070 + } 1.1071 + 1.1072 + let observer = subject.QueryInterface(Ci.nsIInterfaceRequestor) 1.1073 + .getInterface(Ci.nsIObserver); 1.1074 + let responseTopic = this._quotaResponse; 1.1075 + 1.1076 + setTimeout(function() { 1.1077 + observer.observe(null, responseTopic, 1.1078 + Ci.nsIPermissionManager.DENY_ACTION); 1.1079 + }, 0); 1.1080 + } 1.1081 +} 1.1082 + 1.1083 +let RemoteDebugger = { 1.1084 + _promptDone: false, 1.1085 + _promptAnswer: false, 1.1086 + _running: false, 1.1087 + 1.1088 + prompt: function debugger_prompt() { 1.1089 + this._promptDone = false; 1.1090 + 1.1091 + shell.sendChromeEvent({ 1.1092 + "type": "remote-debugger-prompt" 1.1093 + }); 1.1094 + 1.1095 + while(!this._promptDone) { 1.1096 + Services.tm.currentThread.processNextEvent(true); 1.1097 + } 1.1098 + 1.1099 + return this._promptAnswer; 1.1100 + }, 1.1101 + 1.1102 + handleEvent: function debugger_handleEvent(detail) { 1.1103 + this._promptAnswer = detail.value; 1.1104 + this._promptDone = true; 1.1105 + }, 1.1106 + 1.1107 + get isDebugging() { 1.1108 + if (!this._running) { 1.1109 + return false; 1.1110 + } 1.1111 + 1.1112 + return DebuggerServer._connections && 1.1113 + Object.keys(DebuggerServer._connections).length > 0; 1.1114 + }, 1.1115 + 1.1116 + // Start the debugger server. 1.1117 + start: function debugger_start() { 1.1118 + if (this._running) { 1.1119 + return; 1.1120 + } 1.1121 + 1.1122 + if (!DebuggerServer.initialized) { 1.1123 + // Ask for remote connections. 1.1124 + DebuggerServer.init(this.prompt.bind(this)); 1.1125 + 1.1126 + // /!\ Be careful when adding a new actor, especially global actors. 1.1127 + // Any new global actor will be exposed and returned by the root actor. 1.1128 + 1.1129 + // Add Firefox-specific actors, but prevent tab actors to be loaded in 1.1130 + // the parent process, unless we enable certified apps debugging. 1.1131 + let restrictPrivileges = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps"); 1.1132 + DebuggerServer.addBrowserActors("navigator:browser", restrictPrivileges); 1.1133 + 1.1134 + /** 1.1135 + * Construct a root actor appropriate for use in a server running in B2G. 1.1136 + * The returned root actor respects the factories registered with 1.1137 + * DebuggerServer.addGlobalActor only if certified apps debugging is on, 1.1138 + * otherwise we used an explicit limited list of global actors 1.1139 + * 1.1140 + * * @param connection DebuggerServerConnection 1.1141 + * The conection to the client. 1.1142 + */ 1.1143 + DebuggerServer.createRootActor = function createRootActor(connection) 1.1144 + { 1.1145 + let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); 1.1146 + let parameters = { 1.1147 + // We do not expose browser tab actors yet, 1.1148 + // but we still have to define tabList.getList(), 1.1149 + // otherwise, client won't be able to fetch global actors 1.1150 + // from listTabs request! 1.1151 + tabList: { 1.1152 + getList: function() { 1.1153 + return promise.resolve([]); 1.1154 + } 1.1155 + }, 1.1156 + // Use an explicit global actor list to prevent exposing 1.1157 + // unexpected actors 1.1158 + globalActorFactories: restrictPrivileges ? { 1.1159 + webappsActor: DebuggerServer.globalActorFactories.webappsActor, 1.1160 + deviceActor: DebuggerServer.globalActorFactories.deviceActor, 1.1161 + } : DebuggerServer.globalActorFactories 1.1162 + }; 1.1163 + let root = new DebuggerServer.RootActor(connection, parameters); 1.1164 + root.applicationType = "operating-system"; 1.1165 + return root; 1.1166 + }; 1.1167 + 1.1168 +#ifdef MOZ_WIDGET_GONK 1.1169 + DebuggerServer.on("connectionchange", function() { 1.1170 + AdbController.updateState(); 1.1171 + }); 1.1172 +#endif 1.1173 + } 1.1174 + 1.1175 + let path = Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") || 1.1176 + "/data/local/debugger-socket"; 1.1177 + try { 1.1178 + DebuggerServer.openListener(path); 1.1179 + // Temporary event, until bug 942756 lands and offers a way to know 1.1180 + // when the server is up and running. 1.1181 + Services.obs.notifyObservers(null, 'debugger-server-started', null); 1.1182 + this._running = true; 1.1183 + } catch (e) { 1.1184 + dump('Unable to start debugger server: ' + e + '\n'); 1.1185 + } 1.1186 + }, 1.1187 + 1.1188 + stop: function debugger_stop() { 1.1189 + if (!this._running) { 1.1190 + return; 1.1191 + } 1.1192 + 1.1193 + if (!DebuggerServer.initialized) { 1.1194 + // Can this really happen if we are running? 1.1195 + this._running = false; 1.1196 + return; 1.1197 + } 1.1198 + 1.1199 + try { 1.1200 + DebuggerServer.closeListener(); 1.1201 + } catch (e) { 1.1202 + dump('Unable to stop debugger server: ' + e + '\n'); 1.1203 + } 1.1204 + this._running = false; 1.1205 + } 1.1206 +} 1.1207 + 1.1208 +let KeyboardHelper = { 1.1209 + handleEvent: function keyboard_handleEvent(detail) { 1.1210 + Keyboard.setLayouts(detail.layouts); 1.1211 + } 1.1212 +}; 1.1213 + 1.1214 +// This is the backend for Gaia's screenshot feature. Gaia requests a 1.1215 +// screenshot by sending a mozContentEvent with detail.type set to 1.1216 +// 'take-screenshot'. Then we take a screenshot and send a 1.1217 +// mozChromeEvent with detail.type set to 'take-screenshot-success' 1.1218 +// and detail.file set to the an image/png blob 1.1219 +window.addEventListener('ContentStart', function ss_onContentStart() { 1.1220 + let content = shell.contentBrowser.contentWindow; 1.1221 + content.addEventListener('mozContentEvent', function ss_onMozContentEvent(e) { 1.1222 + if (e.detail.type !== 'take-screenshot') 1.1223 + return; 1.1224 + 1.1225 + try { 1.1226 + var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 1.1227 + 'canvas'); 1.1228 + var width = window.innerWidth; 1.1229 + var height = window.innerHeight; 1.1230 + var scale = window.devicePixelRatio; 1.1231 + canvas.setAttribute('width', width * scale); 1.1232 + canvas.setAttribute('height', height * scale); 1.1233 + 1.1234 + var context = canvas.getContext('2d'); 1.1235 + var flags = 1.1236 + context.DRAWWINDOW_DRAW_CARET | 1.1237 + context.DRAWWINDOW_DRAW_VIEW | 1.1238 + context.DRAWWINDOW_USE_WIDGET_LAYERS; 1.1239 + context.scale(scale, scale); 1.1240 + context.drawWindow(window, 0, 0, width, height, 1.1241 + 'rgb(255,255,255)', flags); 1.1242 + 1.1243 + // I can't use sendChromeEvent() here because it doesn't wrap 1.1244 + // the blob in the detail object correctly. So I use __exposedProps__ 1.1245 + // instead to safely send the chrome detail object to content. 1.1246 + shell.sendEvent(getContentWindow(), 'mozChromeEvent', { 1.1247 + __exposedProps__: { type: 'r', file: 'r' }, 1.1248 + type: 'take-screenshot-success', 1.1249 + file: canvas.mozGetAsFile('screenshot', 'image/png') 1.1250 + }); 1.1251 + } catch (e) { 1.1252 + dump('exception while creating screenshot: ' + e + '\n'); 1.1253 + shell.sendChromeEvent({ 1.1254 + type: 'take-screenshot-error', 1.1255 + error: String(e) 1.1256 + }); 1.1257 + } 1.1258 + }); 1.1259 +}); 1.1260 + 1.1261 +(function contentCrashTracker() { 1.1262 + Services.obs.addObserver(function(aSubject, aTopic, aData) { 1.1263 + let props = aSubject.QueryInterface(Ci.nsIPropertyBag2); 1.1264 + if (props.hasKey("abnormal") && props.hasKey("dumpID")) { 1.1265 + shell.reportCrash(false, props.getProperty("dumpID")); 1.1266 + } 1.1267 + }, 1.1268 + "ipc:content-shutdown", false); 1.1269 +})(); 1.1270 + 1.1271 +var CaptivePortalLoginHelper = { 1.1272 + init: function init() { 1.1273 + Services.obs.addObserver(this, 'captive-portal-login', false); 1.1274 + Services.obs.addObserver(this, 'captive-portal-login-abort', false); 1.1275 + }, 1.1276 + handleEvent: function handleEvent(detail) { 1.1277 + Services.captivePortalDetector.cancelLogin(detail.id); 1.1278 + }, 1.1279 + observe: function observe(subject, topic, data) { 1.1280 + shell.sendChromeEvent(JSON.parse(data)); 1.1281 + } 1.1282 +} 1.1283 + 1.1284 +// Listen for crashes submitted through the crash reporter UI. 1.1285 +window.addEventListener('ContentStart', function cr_onContentStart() { 1.1286 + let content = shell.contentBrowser.contentWindow; 1.1287 + content.addEventListener("mozContentEvent", function cr_onMozContentEvent(e) { 1.1288 + if (e.detail.type == "submit-crash" && e.detail.crashID) { 1.1289 + debugCrashReport("submitting crash at user request ", e.detail.crashID); 1.1290 + shell.submitCrash(e.detail.crashID); 1.1291 + } else if (e.detail.type == "delete-crash" && e.detail.crashID) { 1.1292 + debugCrashReport("deleting crash at user request ", e.detail.crashID); 1.1293 + shell.deleteCrash(e.detail.crashID); 1.1294 + } 1.1295 + }); 1.1296 +}); 1.1297 + 1.1298 +window.addEventListener('ContentStart', function update_onContentStart() { 1.1299 + Cu.import('resource://gre/modules/WebappsUpdater.jsm'); 1.1300 + WebappsUpdater.handleContentStart(shell); 1.1301 + 1.1302 + let promptCc = Cc["@mozilla.org/updates/update-prompt;1"]; 1.1303 + if (!promptCc) { 1.1304 + return; 1.1305 + } 1.1306 + 1.1307 + let updatePrompt = promptCc.createInstance(Ci.nsIUpdatePrompt); 1.1308 + if (!updatePrompt) { 1.1309 + return; 1.1310 + } 1.1311 + 1.1312 + updatePrompt.wrappedJSObject.handleContentStart(shell); 1.1313 +}); 1.1314 + 1.1315 +(function geolocationStatusTracker() { 1.1316 + let gGeolocationActive = false; 1.1317 + 1.1318 + Services.obs.addObserver(function(aSubject, aTopic, aData) { 1.1319 + let oldState = gGeolocationActive; 1.1320 + if (aData == "starting") { 1.1321 + gGeolocationActive = true; 1.1322 + } else if (aData == "shutdown") { 1.1323 + gGeolocationActive = false; 1.1324 + } 1.1325 + 1.1326 + if (gGeolocationActive != oldState) { 1.1327 + shell.sendChromeEvent({ 1.1328 + type: 'geolocation-status', 1.1329 + active: gGeolocationActive 1.1330 + }); 1.1331 + } 1.1332 +}, "geolocation-device-events", false); 1.1333 +})(); 1.1334 + 1.1335 +(function headphonesStatusTracker() { 1.1336 + Services.obs.addObserver(function(aSubject, aTopic, aData) { 1.1337 + shell.sendChromeEvent({ 1.1338 + type: 'headphones-status-changed', 1.1339 + state: aData 1.1340 + }); 1.1341 +}, "headphones-status-changed", false); 1.1342 +})(); 1.1343 + 1.1344 +(function audioChannelChangedTracker() { 1.1345 + Services.obs.addObserver(function(aSubject, aTopic, aData) { 1.1346 + shell.sendChromeEvent({ 1.1347 + type: 'audio-channel-changed', 1.1348 + channel: aData 1.1349 + }); 1.1350 +}, "audio-channel-changed", false); 1.1351 +})(); 1.1352 + 1.1353 +(function defaultVolumeChannelChangedTracker() { 1.1354 + Services.obs.addObserver(function(aSubject, aTopic, aData) { 1.1355 + shell.sendChromeEvent({ 1.1356 + type: 'default-volume-channel-changed', 1.1357 + channel: aData 1.1358 + }); 1.1359 +}, "default-volume-channel-changed", false); 1.1360 +})(); 1.1361 + 1.1362 +(function visibleAudioChannelChangedTracker() { 1.1363 + Services.obs.addObserver(function(aSubject, aTopic, aData) { 1.1364 + shell.sendChromeEvent({ 1.1365 + type: 'visible-audio-channel-changed', 1.1366 + channel: aData 1.1367 + }); 1.1368 + shell.visibleNormalAudioActive = (aData == 'normal'); 1.1369 +}, "visible-audio-channel-changed", false); 1.1370 +})(); 1.1371 + 1.1372 +(function recordingStatusTracker() { 1.1373 + // Recording status is tracked per process with following data structure: 1.1374 + // {<processId>: {<requestURL>: {isApp: <isApp>, 1.1375 + // count: <N>, 1.1376 + // audioCount: <N>, 1.1377 + // videoCount: <N>}} 1.1378 + let gRecordingActiveProcesses = {}; 1.1379 + 1.1380 + let recordingHandler = function(aSubject, aTopic, aData) { 1.1381 + let props = aSubject.QueryInterface(Ci.nsIPropertyBag2); 1.1382 + let processId = (props.hasKey('childID')) ? props.get('childID') 1.1383 + : 'main'; 1.1384 + if (processId && !gRecordingActiveProcesses.hasOwnProperty(processId)) { 1.1385 + gRecordingActiveProcesses[processId] = {}; 1.1386 + } 1.1387 + 1.1388 + let commandHandler = function (requestURL, command) { 1.1389 + let currentProcess = gRecordingActiveProcesses[processId]; 1.1390 + let currentActive = currentProcess[requestURL]; 1.1391 + let wasActive = (currentActive['count'] > 0); 1.1392 + let wasAudioActive = (currentActive['audioCount'] > 0); 1.1393 + let wasVideoActive = (currentActive['videoCount'] > 0); 1.1394 + 1.1395 + switch (command.type) { 1.1396 + case 'starting': 1.1397 + currentActive['count']++; 1.1398 + currentActive['audioCount'] += (command.isAudio) ? 1 : 0; 1.1399 + currentActive['videoCount'] += (command.isVideo) ? 1 : 0; 1.1400 + break; 1.1401 + case 'shutdown': 1.1402 + currentActive['count']--; 1.1403 + currentActive['audioCount'] -= (command.isAudio) ? 1 : 0; 1.1404 + currentActive['videoCount'] -= (command.isVideo) ? 1 : 0; 1.1405 + break; 1.1406 + case 'content-shutdown': 1.1407 + currentActive['count'] = 0; 1.1408 + currentActive['audioCount'] = 0; 1.1409 + currentActive['videoCount'] = 0; 1.1410 + break; 1.1411 + } 1.1412 + 1.1413 + if (currentActive['count'] > 0) { 1.1414 + currentProcess[requestURL] = currentActive; 1.1415 + } else { 1.1416 + delete currentProcess[requestURL]; 1.1417 + } 1.1418 + 1.1419 + // We need to track changes if any active state is changed. 1.1420 + let isActive = (currentActive['count'] > 0); 1.1421 + let isAudioActive = (currentActive['audioCount'] > 0); 1.1422 + let isVideoActive = (currentActive['videoCount'] > 0); 1.1423 + if ((isActive != wasActive) || 1.1424 + (isAudioActive != wasAudioActive) || 1.1425 + (isVideoActive != wasVideoActive)) { 1.1426 + shell.sendChromeEvent({ 1.1427 + type: 'recording-status', 1.1428 + active: isActive, 1.1429 + requestURL: requestURL, 1.1430 + isApp: currentActive['isApp'], 1.1431 + isAudio: isAudioActive, 1.1432 + isVideo: isVideoActive 1.1433 + }); 1.1434 + } 1.1435 + }; 1.1436 + 1.1437 + switch (aData) { 1.1438 + case 'starting': 1.1439 + case 'shutdown': 1.1440 + // create page record if it is not existed yet. 1.1441 + let requestURL = props.get('requestURL'); 1.1442 + if (requestURL && 1.1443 + !gRecordingActiveProcesses[processId].hasOwnProperty(requestURL)) { 1.1444 + gRecordingActiveProcesses[processId][requestURL] = {isApp: props.get('isApp'), 1.1445 + count: 0, 1.1446 + audioCount: 0, 1.1447 + videoCount: 0}; 1.1448 + } 1.1449 + commandHandler(requestURL, { type: aData, 1.1450 + isAudio: props.get('isAudio'), 1.1451 + isVideo: props.get('isVideo')}); 1.1452 + break; 1.1453 + case 'content-shutdown': 1.1454 + // iterate through all the existing active processes 1.1455 + Object.keys(gRecordingActiveProcesses[processId]).forEach(function(requestURL) { 1.1456 + commandHandler(requestURL, { type: aData, 1.1457 + isAudio: true, 1.1458 + isVideo: true}); 1.1459 + }); 1.1460 + break; 1.1461 + } 1.1462 + 1.1463 + // clean up process record if no page record in it. 1.1464 + if (Object.keys(gRecordingActiveProcesses[processId]).length == 0) { 1.1465 + delete gRecordingActiveProcesses[processId]; 1.1466 + } 1.1467 + }; 1.1468 + Services.obs.addObserver(recordingHandler, 'recording-device-events', false); 1.1469 + Services.obs.addObserver(recordingHandler, 'recording-device-ipc-events', false); 1.1470 + 1.1471 + Services.obs.addObserver(function(aSubject, aTopic, aData) { 1.1472 + // send additional recording events if content process is being killed 1.1473 + let processId = aSubject.QueryInterface(Ci.nsIPropertyBag2).get('childID'); 1.1474 + if (gRecordingActiveProcesses.hasOwnProperty(processId)) { 1.1475 + Services.obs.notifyObservers(aSubject, 'recording-device-ipc-events', 'content-shutdown'); 1.1476 + } 1.1477 + }, 'ipc:content-shutdown', false); 1.1478 +})(); 1.1479 + 1.1480 +(function volumeStateTracker() { 1.1481 + Services.obs.addObserver(function(aSubject, aTopic, aData) { 1.1482 + shell.sendChromeEvent({ 1.1483 + type: 'volume-state-changed', 1.1484 + active: (aData == 'Shared') 1.1485 + }); 1.1486 +}, 'volume-state-changed', false); 1.1487 +})(); 1.1488 + 1.1489 +#ifdef MOZ_WIDGET_GONK 1.1490 +// Devices don't have all the same partition size for /cache where we 1.1491 +// store the http cache. 1.1492 +(function setHTTPCacheSize() { 1.1493 + let path = Services.prefs.getCharPref("browser.cache.disk.parent_directory"); 1.1494 + let volumeService = Cc["@mozilla.org/telephony/volume-service;1"] 1.1495 + .getService(Ci.nsIVolumeService); 1.1496 + 1.1497 + let stats = volumeService.createOrGetVolumeByPath(path).getStats(); 1.1498 + 1.1499 + // We must set the size in KB, and keep a bit of free space. 1.1500 + let size = Math.floor(stats.totalBytes / 1024) - 1024; 1.1501 + Services.prefs.setIntPref("browser.cache.disk.capacity", size); 1.1502 +}) () 1.1503 +#endif 1.1504 + 1.1505 +#ifdef MOZ_WIDGET_GONK 1.1506 +let SensorsListener = { 1.1507 + sensorsListenerDevices: ['crespo'], 1.1508 + device: libcutils.property_get("ro.product.device"), 1.1509 + 1.1510 + deviceNeedsWorkaround: function SensorsListener_deviceNeedsWorkaround() { 1.1511 + return (this.sensorsListenerDevices.indexOf(this.device) != -1); 1.1512 + }, 1.1513 + 1.1514 + handleEvent: function SensorsListener_handleEvent(evt) { 1.1515 + switch(evt.type) { 1.1516 + case 'devicemotion': 1.1517 + // Listener that does nothing, we need this to have the sensor being 1.1518 + // able to report correct values, as explained in bug 753245, comment 6 1.1519 + // and in bug 871916 1.1520 + break; 1.1521 + 1.1522 + default: 1.1523 + break; 1.1524 + } 1.1525 + }, 1.1526 + 1.1527 + observe: function SensorsListener_observe(subject, topic, data) { 1.1528 + // We remove the listener when the screen is off, otherwise sensor will 1.1529 + // continue to bother us with data and we won't be able to get the 1.1530 + // system into suspend state, thus draining battery. 1.1531 + if (data === 'on') { 1.1532 + window.addEventListener('devicemotion', this); 1.1533 + } else { 1.1534 + window.removeEventListener('devicemotion', this); 1.1535 + } 1.1536 + }, 1.1537 + 1.1538 + init: function SensorsListener_init() { 1.1539 + if (this.deviceNeedsWorkaround()) { 1.1540 + // On boot, enable the listener, screen will be on. 1.1541 + window.addEventListener('devicemotion', this); 1.1542 + 1.1543 + // Then listen for further screen state changes 1.1544 + Services.obs.addObserver(this, 'screen-state-changed', false); 1.1545 + } 1.1546 + } 1.1547 +} 1.1548 + 1.1549 +SensorsListener.init(); 1.1550 +#endif 1.1551 + 1.1552 +// Calling this observer will cause a shutdown an a profile reset. 1.1553 +// Use eg. : Services.obs.notifyObservers(null, 'b2g-reset-profile', null); 1.1554 +Services.obs.addObserver(function resetProfile(subject, topic, data) { 1.1555 + Services.obs.removeObserver(resetProfile, topic); 1.1556 + 1.1557 + // Listening for 'profile-before-change2' which is late in the shutdown 1.1558 + // sequence, but still has xpcom access. 1.1559 + Services.obs.addObserver(function clearProfile(subject, topic, data) { 1.1560 + Services.obs.removeObserver(clearProfile, topic); 1.1561 +#ifdef MOZ_WIDGET_GONK 1.1562 + let json = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile); 1.1563 + json.initWithPath('/system/b2g/webapps/webapps.json'); 1.1564 + let toRemove = json.exists() 1.1565 + // This is a user build, just rm -r /data/local /data/b2g/mozilla 1.1566 + ? ['/data/local', '/data/b2g/mozilla'] 1.1567 + // This is an eng build. We clear the profile and a set of files 1.1568 + // under /data/local. 1.1569 + : ['/data/b2g/mozilla', 1.1570 + '/data/local/permissions.sqlite', 1.1571 + '/data/local/storage', 1.1572 + '/data/local/OfflineCache']; 1.1573 + 1.1574 + toRemove.forEach(function(dir) { 1.1575 + try { 1.1576 + let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile); 1.1577 + file.initWithPath(dir); 1.1578 + file.remove(true); 1.1579 + } catch(e) { dump(e); } 1.1580 + }); 1.1581 +#else 1.1582 + // Desktop builds. 1.1583 + let profile = Services.dirsvc.get('ProfD', Ci.nsIFile); 1.1584 + 1.1585 + // We don't want to remove everything from the profile, since this 1.1586 + // would prevent us from starting up. 1.1587 + let whitelist = ['defaults', 'extensions', 'settings.json', 1.1588 + 'user.js', 'webapps']; 1.1589 + let enumerator = profile.directoryEntries; 1.1590 + while (enumerator.hasMoreElements()) { 1.1591 + let file = enumerator.getNext().QueryInterface(Ci.nsIFile); 1.1592 + if (whitelist.indexOf(file.leafName) == -1) { 1.1593 + file.remove(true); 1.1594 + } 1.1595 + } 1.1596 +#endif 1.1597 + }, 1.1598 + 'profile-before-change2', false); 1.1599 + 1.1600 + let appStartup = Cc['@mozilla.org/toolkit/app-startup;1'] 1.1601 + .getService(Ci.nsIAppStartup); 1.1602 + appStartup.quit(Ci.nsIAppStartup.eForceQuit); 1.1603 +}, 'b2g-reset-profile', false); 1.1604 + 1.1605 +/** 1.1606 + * CID of our implementation of nsIDownloadManagerUI. 1.1607 + */ 1.1608 +const kTransferCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}"); 1.1609 + 1.1610 +/** 1.1611 + * Contract ID of the service implementing nsITransfer. 1.1612 + */ 1.1613 +const kTransferContractId = "@mozilla.org/transfer;1"; 1.1614 + 1.1615 +// Override Toolkit's nsITransfer implementation with the one from the 1.1616 +// JavaScript API for downloads. This will eventually be removed when 1.1617 +// nsIDownloadManager will not be available anymore (bug 851471). The 1.1618 +// old code in this module will be removed in bug 899110. 1.1619 +Components.manager.QueryInterface(Ci.nsIComponentRegistrar) 1.1620 + .registerFactory(kTransferCid, "", 1.1621 + kTransferContractId, null);