Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / |
michael@0 | 2 | /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 5 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | Cu.import('resource://gre/modules/ContactService.jsm'); |
michael@0 | 8 | Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm'); |
michael@0 | 9 | Cu.import('resource://gre/modules/DataStoreChangeNotifier.jsm'); |
michael@0 | 10 | Cu.import('resource://gre/modules/AlarmService.jsm'); |
michael@0 | 11 | Cu.import('resource://gre/modules/ActivitiesService.jsm'); |
michael@0 | 12 | Cu.import('resource://gre/modules/PermissionPromptHelper.jsm'); |
michael@0 | 13 | Cu.import('resource://gre/modules/NotificationDB.jsm'); |
michael@0 | 14 | Cu.import('resource://gre/modules/Payment.jsm'); |
michael@0 | 15 | Cu.import("resource://gre/modules/AppsUtils.jsm"); |
michael@0 | 16 | Cu.import('resource://gre/modules/UserAgentOverrides.jsm'); |
michael@0 | 17 | Cu.import('resource://gre/modules/Keyboard.jsm'); |
michael@0 | 18 | Cu.import('resource://gre/modules/ErrorPage.jsm'); |
michael@0 | 19 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 20 | Cu.import('resource://gre/modules/NetworkStatsService.jsm'); |
michael@0 | 21 | #endif |
michael@0 | 22 | |
michael@0 | 23 | // Identity |
michael@0 | 24 | Cu.import('resource://gre/modules/SignInToWebsite.jsm'); |
michael@0 | 25 | SignInToWebsiteController.init(); |
michael@0 | 26 | |
michael@0 | 27 | #ifdef MOZ_SERVICES_FXACCOUNTS |
michael@0 | 28 | Cu.import('resource://gre/modules/FxAccountsMgmtService.jsm'); |
michael@0 | 29 | #endif |
michael@0 | 30 | |
michael@0 | 31 | Cu.import('resource://gre/modules/DownloadsAPI.jsm'); |
michael@0 | 32 | |
michael@0 | 33 | XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", |
michael@0 | 34 | "resource://gre/modules/SystemAppProxy.jsm"); |
michael@0 | 35 | |
michael@0 | 36 | Cu.import('resource://gre/modules/Webapps.jsm'); |
michael@0 | 37 | DOMApplicationRegistry.allAppsLaunchable = true; |
michael@0 | 38 | |
michael@0 | 39 | XPCOMUtils.defineLazyServiceGetter(Services, 'env', |
michael@0 | 40 | '@mozilla.org/process/environment;1', |
michael@0 | 41 | 'nsIEnvironment'); |
michael@0 | 42 | |
michael@0 | 43 | XPCOMUtils.defineLazyServiceGetter(Services, 'ss', |
michael@0 | 44 | '@mozilla.org/content/style-sheet-service;1', |
michael@0 | 45 | 'nsIStyleSheetService'); |
michael@0 | 46 | |
michael@0 | 47 | XPCOMUtils.defineLazyServiceGetter(this, 'gSystemMessenger', |
michael@0 | 48 | '@mozilla.org/system-message-internal;1', |
michael@0 | 49 | 'nsISystemMessagesInternal'); |
michael@0 | 50 | |
michael@0 | 51 | XPCOMUtils.defineLazyServiceGetter(Services, 'fm', |
michael@0 | 52 | '@mozilla.org/focus-manager;1', |
michael@0 | 53 | 'nsIFocusManager'); |
michael@0 | 54 | |
michael@0 | 55 | XPCOMUtils.defineLazyGetter(this, 'DebuggerServer', function() { |
michael@0 | 56 | Cu.import('resource://gre/modules/devtools/dbg-server.jsm'); |
michael@0 | 57 | return DebuggerServer; |
michael@0 | 58 | }); |
michael@0 | 59 | |
michael@0 | 60 | XPCOMUtils.defineLazyGetter(this, "ppmm", function() { |
michael@0 | 61 | return Cc["@mozilla.org/parentprocessmessagemanager;1"] |
michael@0 | 62 | .getService(Ci.nsIMessageListenerManager); |
michael@0 | 63 | }); |
michael@0 | 64 | |
michael@0 | 65 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 66 | XPCOMUtils.defineLazyGetter(this, "libcutils", function () { |
michael@0 | 67 | Cu.import("resource://gre/modules/systemlibs.js"); |
michael@0 | 68 | return libcutils; |
michael@0 | 69 | }); |
michael@0 | 70 | #endif |
michael@0 | 71 | |
michael@0 | 72 | #ifdef MOZ_CAPTIVEDETECT |
michael@0 | 73 | XPCOMUtils.defineLazyServiceGetter(Services, 'captivePortalDetector', |
michael@0 | 74 | '@mozilla.org/toolkit/captive-detector;1', |
michael@0 | 75 | 'nsICaptivePortalDetector'); |
michael@0 | 76 | #endif |
michael@0 | 77 | |
michael@0 | 78 | function getContentWindow() { |
michael@0 | 79 | return shell.contentBrowser.contentWindow; |
michael@0 | 80 | } |
michael@0 | 81 | |
michael@0 | 82 | function debug(str) { |
michael@0 | 83 | dump(' -*- Shell.js: ' + str + '\n'); |
michael@0 | 84 | } |
michael@0 | 85 | |
michael@0 | 86 | #ifdef MOZ_CRASHREPORTER |
michael@0 | 87 | function debugCrashReport(aStr) { |
michael@0 | 88 | dump('Crash reporter : ' + aStr); |
michael@0 | 89 | } |
michael@0 | 90 | #else |
michael@0 | 91 | function debugCrashReport(aStr) {} |
michael@0 | 92 | #endif |
michael@0 | 93 | |
michael@0 | 94 | var shell = { |
michael@0 | 95 | |
michael@0 | 96 | get CrashSubmit() { |
michael@0 | 97 | delete this.CrashSubmit; |
michael@0 | 98 | #ifdef MOZ_CRASHREPORTER |
michael@0 | 99 | Cu.import("resource://gre/modules/CrashSubmit.jsm", this); |
michael@0 | 100 | return this.CrashSubmit; |
michael@0 | 101 | #else |
michael@0 | 102 | dump('Crash reporter : disabled at build time.'); |
michael@0 | 103 | return this.CrashSubmit = null; |
michael@0 | 104 | #endif |
michael@0 | 105 | }, |
michael@0 | 106 | |
michael@0 | 107 | onlineForCrashReport: function shell_onlineForCrashReport() { |
michael@0 | 108 | let wifiManager = navigator.mozWifiManager; |
michael@0 | 109 | let onWifi = (wifiManager && |
michael@0 | 110 | (wifiManager.connection.status == 'connected')); |
michael@0 | 111 | return !Services.io.offline && onWifi; |
michael@0 | 112 | }, |
michael@0 | 113 | |
michael@0 | 114 | reportCrash: function shell_reportCrash(isChrome, aCrashID) { |
michael@0 | 115 | let crashID = aCrashID; |
michael@0 | 116 | try { |
michael@0 | 117 | // For chrome crashes, we want to report the lastRunCrashID. |
michael@0 | 118 | if (isChrome) { |
michael@0 | 119 | crashID = Cc["@mozilla.org/xre/app-info;1"] |
michael@0 | 120 | .getService(Ci.nsIXULRuntime).lastRunCrashID; |
michael@0 | 121 | } |
michael@0 | 122 | } catch(e) { |
michael@0 | 123 | debugCrashReport('Failed to fetch crash id. Crash ID is "' + crashID |
michael@0 | 124 | + '" Exception: ' + e); |
michael@0 | 125 | } |
michael@0 | 126 | |
michael@0 | 127 | // Bail if there isn't a valid crashID. |
michael@0 | 128 | if (!this.CrashSubmit || !crashID && !this.CrashSubmit.pendingIDs().length) { |
michael@0 | 129 | return; |
michael@0 | 130 | } |
michael@0 | 131 | |
michael@0 | 132 | // purge the queue. |
michael@0 | 133 | this.CrashSubmit.pruneSavedDumps(); |
michael@0 | 134 | |
michael@0 | 135 | // check for environment affecting crash reporting |
michael@0 | 136 | let env = Cc["@mozilla.org/process/environment;1"] |
michael@0 | 137 | .getService(Ci.nsIEnvironment); |
michael@0 | 138 | let shutdown = env.get("MOZ_CRASHREPORTER_SHUTDOWN"); |
michael@0 | 139 | if (shutdown) { |
michael@0 | 140 | let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"] |
michael@0 | 141 | .getService(Ci.nsIAppStartup); |
michael@0 | 142 | appStartup.quit(Ci.nsIAppStartup.eForceQuit); |
michael@0 | 143 | } |
michael@0 | 144 | |
michael@0 | 145 | let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT"); |
michael@0 | 146 | if (noReport) { |
michael@0 | 147 | return; |
michael@0 | 148 | } |
michael@0 | 149 | |
michael@0 | 150 | try { |
michael@0 | 151 | // Check if we should automatically submit this crash. |
michael@0 | 152 | if (Services.prefs.getBoolPref('app.reportCrashes')) { |
michael@0 | 153 | this.submitCrash(crashID); |
michael@0 | 154 | } else { |
michael@0 | 155 | this.deleteCrash(crashID); |
michael@0 | 156 | } |
michael@0 | 157 | } catch (e) { |
michael@0 | 158 | debugCrashReport('Can\'t fetch app.reportCrashes. Exception: ' + e); |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | // We can get here if we're just submitting old pending crashes. |
michael@0 | 162 | // Check that there's a valid crashID so that we only notify the |
michael@0 | 163 | // user if a crash just happened and not when we OOM. Bug 829477 |
michael@0 | 164 | if (crashID) { |
michael@0 | 165 | this.sendChromeEvent({ |
michael@0 | 166 | type: "handle-crash", |
michael@0 | 167 | crashID: crashID, |
michael@0 | 168 | chrome: isChrome |
michael@0 | 169 | }); |
michael@0 | 170 | } |
michael@0 | 171 | }, |
michael@0 | 172 | |
michael@0 | 173 | deleteCrash: function shell_deleteCrash(aCrashID) { |
michael@0 | 174 | if (aCrashID) { |
michael@0 | 175 | debugCrashReport('Deleting pending crash: ' + aCrashID); |
michael@0 | 176 | shell.CrashSubmit.delete(aCrashID); |
michael@0 | 177 | } |
michael@0 | 178 | }, |
michael@0 | 179 | |
michael@0 | 180 | // this function submit the pending crashes. |
michael@0 | 181 | // make sure you are online. |
michael@0 | 182 | submitQueuedCrashes: function shell_submitQueuedCrashes() { |
michael@0 | 183 | // submit the pending queue. |
michael@0 | 184 | let pending = shell.CrashSubmit.pendingIDs(); |
michael@0 | 185 | for (let crashid of pending) { |
michael@0 | 186 | debugCrashReport('Submitting crash: ' + crashid); |
michael@0 | 187 | shell.CrashSubmit.submit(crashid); |
michael@0 | 188 | } |
michael@0 | 189 | }, |
michael@0 | 190 | |
michael@0 | 191 | // This function submits a crash when we're online. |
michael@0 | 192 | submitCrash: function shell_submitCrash(aCrashID) { |
michael@0 | 193 | if (this.onlineForCrashReport()) { |
michael@0 | 194 | this.submitQueuedCrashes(); |
michael@0 | 195 | return; |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | debugCrashReport('Not online, postponing.'); |
michael@0 | 199 | |
michael@0 | 200 | Services.obs.addObserver(function observer(subject, topic, state) { |
michael@0 | 201 | let network = subject.QueryInterface(Ci.nsINetworkInterface); |
michael@0 | 202 | if (network.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED |
michael@0 | 203 | && network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { |
michael@0 | 204 | shell.submitQueuedCrashes(); |
michael@0 | 205 | |
michael@0 | 206 | Services.obs.removeObserver(observer, topic); |
michael@0 | 207 | } |
michael@0 | 208 | }, "network-connection-state-changed", false); |
michael@0 | 209 | }, |
michael@0 | 210 | |
michael@0 | 211 | get contentBrowser() { |
michael@0 | 212 | delete this.contentBrowser; |
michael@0 | 213 | return this.contentBrowser = document.getElementById('systemapp'); |
michael@0 | 214 | }, |
michael@0 | 215 | |
michael@0 | 216 | get homeURL() { |
michael@0 | 217 | try { |
michael@0 | 218 | let homeSrc = Services.env.get('B2G_HOMESCREEN'); |
michael@0 | 219 | if (homeSrc) |
michael@0 | 220 | return homeSrc; |
michael@0 | 221 | } catch (e) {} |
michael@0 | 222 | |
michael@0 | 223 | return Services.prefs.getCharPref('browser.homescreenURL'); |
michael@0 | 224 | }, |
michael@0 | 225 | |
michael@0 | 226 | get manifestURL() { |
michael@0 | 227 | return Services.prefs.getCharPref('browser.manifestURL'); |
michael@0 | 228 | }, |
michael@0 | 229 | |
michael@0 | 230 | _started: false, |
michael@0 | 231 | hasStarted: function shell_hasStarted() { |
michael@0 | 232 | return this._started; |
michael@0 | 233 | }, |
michael@0 | 234 | |
michael@0 | 235 | start: function shell_start() { |
michael@0 | 236 | this._started = true; |
michael@0 | 237 | |
michael@0 | 238 | // This forces the initialization of the cookie service before we hit the |
michael@0 | 239 | // network. |
michael@0 | 240 | // See bug 810209 |
michael@0 | 241 | let cookies = Cc["@mozilla.org/cookieService;1"]; |
michael@0 | 242 | |
michael@0 | 243 | try { |
michael@0 | 244 | let cr = Cc["@mozilla.org/xre/app-info;1"] |
michael@0 | 245 | .getService(Ci.nsICrashReporter); |
michael@0 | 246 | // Dogfood id. We might want to remove it in the future. |
michael@0 | 247 | // see bug 789466 |
michael@0 | 248 | try { |
michael@0 | 249 | let dogfoodId = Services.prefs.getCharPref('prerelease.dogfood.id'); |
michael@0 | 250 | if (dogfoodId != "") { |
michael@0 | 251 | cr.annotateCrashReport("Email", dogfoodId); |
michael@0 | 252 | } |
michael@0 | 253 | } |
michael@0 | 254 | catch (e) { } |
michael@0 | 255 | |
michael@0 | 256 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 257 | // Annotate crash report |
michael@0 | 258 | let annotations = [ [ "Android_Hardware", "ro.hardware" ], |
michael@0 | 259 | [ "Android_Device", "ro.product.device" ], |
michael@0 | 260 | [ "Android_CPU_ABI2", "ro.product.cpu.abi2" ], |
michael@0 | 261 | [ "Android_CPU_ABI", "ro.product.cpu.abi" ], |
michael@0 | 262 | [ "Android_Manufacturer", "ro.product.manufacturer" ], |
michael@0 | 263 | [ "Android_Brand", "ro.product.brand" ], |
michael@0 | 264 | [ "Android_Model", "ro.product.model" ], |
michael@0 | 265 | [ "Android_Board", "ro.product.board" ], |
michael@0 | 266 | ]; |
michael@0 | 267 | |
michael@0 | 268 | annotations.forEach(function (element) { |
michael@0 | 269 | cr.annotateCrashReport(element[0], libcutils.property_get(element[1])); |
michael@0 | 270 | }); |
michael@0 | 271 | |
michael@0 | 272 | let androidVersion = libcutils.property_get("ro.build.version.sdk") + |
michael@0 | 273 | "(" + libcutils.property_get("ro.build.version.codename") + ")"; |
michael@0 | 274 | cr.annotateCrashReport("Android_Version", androidVersion); |
michael@0 | 275 | |
michael@0 | 276 | SettingsListener.observe("deviceinfo.os", "", function(value) { |
michael@0 | 277 | try { |
michael@0 | 278 | let cr = Cc["@mozilla.org/xre/app-info;1"] |
michael@0 | 279 | .getService(Ci.nsICrashReporter); |
michael@0 | 280 | cr.annotateCrashReport("B2G_OS_Version", value); |
michael@0 | 281 | } catch(e) { } |
michael@0 | 282 | }); |
michael@0 | 283 | #endif |
michael@0 | 284 | } catch(e) { |
michael@0 | 285 | debugCrashReport('exception: ' + e); |
michael@0 | 286 | } |
michael@0 | 287 | |
michael@0 | 288 | let homeURL = this.homeURL; |
michael@0 | 289 | if (!homeURL) { |
michael@0 | 290 | let msg = 'Fatal error during startup: No homescreen found: try setting B2G_HOMESCREEN'; |
michael@0 | 291 | alert(msg); |
michael@0 | 292 | return; |
michael@0 | 293 | } |
michael@0 | 294 | let manifestURL = this.manifestURL; |
michael@0 | 295 | // <html:iframe id="systemapp" |
michael@0 | 296 | // mozbrowser="true" allowfullscreen="true" |
michael@0 | 297 | // style="overflow: hidden; height: 100%; width: 100%; border: none;" |
michael@0 | 298 | // src="data:text/html;charset=utf-8,%3C!DOCTYPE html>%3Cbody style='background:black;'>"/> |
michael@0 | 299 | let systemAppFrame = |
michael@0 | 300 | document.createElementNS('http://www.w3.org/1999/xhtml', 'html:iframe'); |
michael@0 | 301 | systemAppFrame.setAttribute('id', 'systemapp'); |
michael@0 | 302 | systemAppFrame.setAttribute('mozbrowser', 'true'); |
michael@0 | 303 | systemAppFrame.setAttribute('mozapp', manifestURL); |
michael@0 | 304 | systemAppFrame.setAttribute('allowfullscreen', 'true'); |
michael@0 | 305 | systemAppFrame.setAttribute('style', "overflow: hidden; height: 100%; width: 100%; border: none;"); |
michael@0 | 306 | systemAppFrame.setAttribute('src', "data:text/html;charset=utf-8,%3C!DOCTYPE html>%3Cbody style='background:black;"); |
michael@0 | 307 | let container = document.getElementById('container'); |
michael@0 | 308 | #ifdef MOZ_WIDGET_COCOA |
michael@0 | 309 | // See shell.html |
michael@0 | 310 | let hotfix = document.getElementById('placeholder'); |
michael@0 | 311 | if (hotfix) { |
michael@0 | 312 | container.removeChild(hotfix); |
michael@0 | 313 | } |
michael@0 | 314 | #endif |
michael@0 | 315 | container.appendChild(systemAppFrame); |
michael@0 | 316 | |
michael@0 | 317 | systemAppFrame.contentWindow |
michael@0 | 318 | .QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 319 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 320 | .sessionHistory = Cc["@mozilla.org/browser/shistory;1"] |
michael@0 | 321 | .createInstance(Ci.nsISHistory); |
michael@0 | 322 | |
michael@0 | 323 | // On firefox mulet, shell.html is loaded in a tab |
michael@0 | 324 | // and we have to listen on the chrome event handler |
michael@0 | 325 | // to catch key events |
michael@0 | 326 | let chromeEventHandler = window.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 327 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 328 | .QueryInterface(Ci.nsIDocShell) |
michael@0 | 329 | .chromeEventHandler || window; |
michael@0 | 330 | // Capture all key events so we can filter out hardware buttons |
michael@0 | 331 | // And send them to Gaia via mozChromeEvents. |
michael@0 | 332 | // Ideally, hardware buttons wouldn't generate key events at all, or |
michael@0 | 333 | // if they did, they would use keycodes that conform to DOM 3 Events. |
michael@0 | 334 | // See discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=762362 |
michael@0 | 335 | chromeEventHandler.addEventListener('keydown', this, true); |
michael@0 | 336 | chromeEventHandler.addEventListener('keypress', this, true); |
michael@0 | 337 | chromeEventHandler.addEventListener('keyup', this, true); |
michael@0 | 338 | |
michael@0 | 339 | window.addEventListener('MozApplicationManifest', this); |
michael@0 | 340 | window.addEventListener('mozfullscreenchange', this); |
michael@0 | 341 | window.addEventListener('MozAfterPaint', this); |
michael@0 | 342 | window.addEventListener('sizemodechange', this); |
michael@0 | 343 | window.addEventListener('unload', this); |
michael@0 | 344 | this.contentBrowser.addEventListener('mozbrowserloadstart', this, true); |
michael@0 | 345 | |
michael@0 | 346 | SystemAppProxy.registerFrame(this.contentBrowser); |
michael@0 | 347 | |
michael@0 | 348 | CustomEventManager.init(); |
michael@0 | 349 | WebappsHelper.init(); |
michael@0 | 350 | UserAgentOverrides.init(); |
michael@0 | 351 | IndexedDBPromptHelper.init(); |
michael@0 | 352 | CaptivePortalLoginHelper.init(); |
michael@0 | 353 | |
michael@0 | 354 | this.contentBrowser.src = homeURL; |
michael@0 | 355 | this.isHomeLoaded = false; |
michael@0 | 356 | |
michael@0 | 357 | ppmm.addMessageListener("content-handler", this); |
michael@0 | 358 | ppmm.addMessageListener("dial-handler", this); |
michael@0 | 359 | ppmm.addMessageListener("sms-handler", this); |
michael@0 | 360 | ppmm.addMessageListener("mail-handler", this); |
michael@0 | 361 | ppmm.addMessageListener("app-notification-send", AlertsHelper); |
michael@0 | 362 | ppmm.addMessageListener("file-picker", this); |
michael@0 | 363 | ppmm.addMessageListener("getProfD", function(message) { |
michael@0 | 364 | return Services.dirsvc.get("ProfD", Ci.nsIFile).path; |
michael@0 | 365 | }); |
michael@0 | 366 | }, |
michael@0 | 367 | |
michael@0 | 368 | stop: function shell_stop() { |
michael@0 | 369 | window.removeEventListener('unload', this); |
michael@0 | 370 | window.removeEventListener('keydown', this, true); |
michael@0 | 371 | window.removeEventListener('keypress', this, true); |
michael@0 | 372 | window.removeEventListener('keyup', this, true); |
michael@0 | 373 | window.removeEventListener('MozApplicationManifest', this); |
michael@0 | 374 | window.removeEventListener('mozfullscreenchange', this); |
michael@0 | 375 | window.removeEventListener('sizemodechange', this); |
michael@0 | 376 | this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true); |
michael@0 | 377 | ppmm.removeMessageListener("content-handler", this); |
michael@0 | 378 | if (this.timer) { |
michael@0 | 379 | this.timer.cancel(); |
michael@0 | 380 | this.timer = null; |
michael@0 | 381 | } |
michael@0 | 382 | |
michael@0 | 383 | UserAgentOverrides.uninit(); |
michael@0 | 384 | IndexedDBPromptHelper.uninit(); |
michael@0 | 385 | }, |
michael@0 | 386 | |
michael@0 | 387 | // If this key event actually represents a hardware button, filter it here |
michael@0 | 388 | // and send a mozChromeEvent with detail.type set to xxx-button-press or |
michael@0 | 389 | // xxx-button-release instead. |
michael@0 | 390 | filterHardwareKeys: function shell_filterHardwareKeys(evt) { |
michael@0 | 391 | var type; |
michael@0 | 392 | switch (evt.keyCode) { |
michael@0 | 393 | case evt.DOM_VK_HOME: // Home button |
michael@0 | 394 | type = 'home-button'; |
michael@0 | 395 | break; |
michael@0 | 396 | case evt.DOM_VK_SLEEP: // Sleep button |
michael@0 | 397 | case evt.DOM_VK_END: // On desktop we don't have a sleep button |
michael@0 | 398 | type = 'sleep-button'; |
michael@0 | 399 | break; |
michael@0 | 400 | case evt.DOM_VK_PAGE_UP: // Volume up button |
michael@0 | 401 | type = 'volume-up-button'; |
michael@0 | 402 | break; |
michael@0 | 403 | case evt.DOM_VK_PAGE_DOWN: // Volume down button |
michael@0 | 404 | type = 'volume-down-button'; |
michael@0 | 405 | break; |
michael@0 | 406 | case evt.DOM_VK_ESCAPE: // Back button (should be disabled) |
michael@0 | 407 | type = 'back-button'; |
michael@0 | 408 | break; |
michael@0 | 409 | case evt.DOM_VK_CONTEXT_MENU: // Menu button |
michael@0 | 410 | type = 'menu-button'; |
michael@0 | 411 | break; |
michael@0 | 412 | case evt.DOM_VK_F1: // headset button |
michael@0 | 413 | type = 'headset-button'; |
michael@0 | 414 | break; |
michael@0 | 415 | } |
michael@0 | 416 | |
michael@0 | 417 | let mediaKeys = { |
michael@0 | 418 | 'MediaNextTrack': 'media-next-track-button', |
michael@0 | 419 | 'MediaPreviousTrack': 'media-previous-track-button', |
michael@0 | 420 | 'MediaPause': 'media-pause-button', |
michael@0 | 421 | 'MediaPlay': 'media-play-button', |
michael@0 | 422 | 'MediaPlayPause': 'media-play-pause-button', |
michael@0 | 423 | 'MediaStop': 'media-stop-button', |
michael@0 | 424 | 'MediaRewind': 'media-rewind-button', |
michael@0 | 425 | 'FastFwd': 'media-fast-forward-button' |
michael@0 | 426 | }; |
michael@0 | 427 | |
michael@0 | 428 | let isMediaKey = false; |
michael@0 | 429 | if (mediaKeys[evt.key]) { |
michael@0 | 430 | isMediaKey = true; |
michael@0 | 431 | type = mediaKeys[evt.key]; |
michael@0 | 432 | } |
michael@0 | 433 | |
michael@0 | 434 | if (!type) { |
michael@0 | 435 | return; |
michael@0 | 436 | } |
michael@0 | 437 | |
michael@0 | 438 | // If we didn't return, then the key event represents a hardware key |
michael@0 | 439 | // and we need to prevent it from propagating to Gaia |
michael@0 | 440 | evt.stopImmediatePropagation(); |
michael@0 | 441 | evt.preventDefault(); // Prevent keypress events (when #501496 is fixed). |
michael@0 | 442 | |
michael@0 | 443 | // If it is a key down or key up event, we send a chrome event to Gaia. |
michael@0 | 444 | // If it is a keypress event we just ignore it. |
michael@0 | 445 | switch (evt.type) { |
michael@0 | 446 | case 'keydown': |
michael@0 | 447 | type = type + '-press'; |
michael@0 | 448 | break; |
michael@0 | 449 | case 'keyup': |
michael@0 | 450 | type = type + '-release'; |
michael@0 | 451 | break; |
michael@0 | 452 | case 'keypress': |
michael@0 | 453 | return; |
michael@0 | 454 | } |
michael@0 | 455 | |
michael@0 | 456 | // Let applications receive the headset button key press/release event. |
michael@0 | 457 | if (evt.keyCode == evt.DOM_VK_F1 && type !== this.lastHardwareButtonEventType) { |
michael@0 | 458 | this.lastHardwareButtonEventType = type; |
michael@0 | 459 | gSystemMessenger.broadcastMessage('headset-button', type); |
michael@0 | 460 | return; |
michael@0 | 461 | } |
michael@0 | 462 | |
michael@0 | 463 | if (isMediaKey) { |
michael@0 | 464 | this.lastHardwareButtonEventType = type; |
michael@0 | 465 | gSystemMessenger.broadcastMessage('media-button', type); |
michael@0 | 466 | return; |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | // On my device, the physical hardware buttons (sleep and volume) |
michael@0 | 470 | // send multiple events (press press release release), but the |
michael@0 | 471 | // soft home button just sends one. This hack is to manually |
michael@0 | 472 | // "debounce" the keys. If the type of this event is the same as |
michael@0 | 473 | // the type of the last one, then don't send it. We'll never send |
michael@0 | 474 | // two presses or two releases in a row. |
michael@0 | 475 | // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=761067 |
michael@0 | 476 | if (type !== this.lastHardwareButtonEventType) { |
michael@0 | 477 | this.lastHardwareButtonEventType = type; |
michael@0 | 478 | this.sendChromeEvent({type: type}); |
michael@0 | 479 | } |
michael@0 | 480 | }, |
michael@0 | 481 | |
michael@0 | 482 | lastHardwareButtonEventType: null, // property for the hack above |
michael@0 | 483 | needBufferOpenAppReq: true, |
michael@0 | 484 | bufferedOpenAppReqs: [], |
michael@0 | 485 | timer: null, |
michael@0 | 486 | visibleNormalAudioActive: false, |
michael@0 | 487 | |
michael@0 | 488 | handleEvent: function shell_handleEvent(evt) { |
michael@0 | 489 | let content = this.contentBrowser.contentWindow; |
michael@0 | 490 | switch (evt.type) { |
michael@0 | 491 | case 'keydown': |
michael@0 | 492 | case 'keyup': |
michael@0 | 493 | case 'keypress': |
michael@0 | 494 | this.filterHardwareKeys(evt); |
michael@0 | 495 | break; |
michael@0 | 496 | case 'mozfullscreenchange': |
michael@0 | 497 | // When the screen goes fullscreen make sure to set the focus to the |
michael@0 | 498 | // main window so noboby can prevent the ESC key to get out fullscreen |
michael@0 | 499 | // mode |
michael@0 | 500 | if (document.mozFullScreen) |
michael@0 | 501 | Services.fm.focusedWindow = window; |
michael@0 | 502 | break; |
michael@0 | 503 | case 'sizemodechange': |
michael@0 | 504 | if (window.windowState == window.STATE_MINIMIZED && !this.visibleNormalAudioActive) { |
michael@0 | 505 | this.contentBrowser.setVisible(false); |
michael@0 | 506 | } else { |
michael@0 | 507 | this.contentBrowser.setVisible(true); |
michael@0 | 508 | } |
michael@0 | 509 | break; |
michael@0 | 510 | case 'mozbrowserloadstart': |
michael@0 | 511 | if (content.document.location == 'about:blank') { |
michael@0 | 512 | this.contentBrowser.addEventListener('mozbrowserlocationchange', this, true); |
michael@0 | 513 | return; |
michael@0 | 514 | } |
michael@0 | 515 | |
michael@0 | 516 | this.notifyContentStart(); |
michael@0 | 517 | break; |
michael@0 | 518 | case 'mozbrowserlocationchange': |
michael@0 | 519 | if (content.document.location == 'about:blank') { |
michael@0 | 520 | return; |
michael@0 | 521 | } |
michael@0 | 522 | |
michael@0 | 523 | this.notifyContentStart(); |
michael@0 | 524 | break; |
michael@0 | 525 | |
michael@0 | 526 | case 'MozApplicationManifest': |
michael@0 | 527 | try { |
michael@0 | 528 | if (!Services.prefs.getBoolPref('browser.cache.offline.enable')) |
michael@0 | 529 | return; |
michael@0 | 530 | |
michael@0 | 531 | let contentWindow = evt.originalTarget.defaultView; |
michael@0 | 532 | let documentElement = contentWindow.document.documentElement; |
michael@0 | 533 | if (!documentElement) |
michael@0 | 534 | return; |
michael@0 | 535 | |
michael@0 | 536 | let manifest = documentElement.getAttribute('manifest'); |
michael@0 | 537 | if (!manifest) |
michael@0 | 538 | return; |
michael@0 | 539 | |
michael@0 | 540 | let principal = contentWindow.document.nodePrincipal; |
michael@0 | 541 | if (Services.perms.testPermissionFromPrincipal(principal, 'offline-app') == Ci.nsIPermissionManager.UNKNOWN_ACTION) { |
michael@0 | 542 | if (Services.prefs.getBoolPref('browser.offline-apps.notify')) { |
michael@0 | 543 | // FIXME Bug 710729 - Add a UI for offline cache notifications |
michael@0 | 544 | return; |
michael@0 | 545 | } |
michael@0 | 546 | return; |
michael@0 | 547 | } |
michael@0 | 548 | |
michael@0 | 549 | Services.perms.addFromPrincipal(principal, 'offline-app', |
michael@0 | 550 | Ci.nsIPermissionManager.ALLOW_ACTION); |
michael@0 | 551 | |
michael@0 | 552 | let documentURI = Services.io.newURI(contentWindow.document.documentURI, |
michael@0 | 553 | null, |
michael@0 | 554 | null); |
michael@0 | 555 | let manifestURI = Services.io.newURI(manifest, null, documentURI); |
michael@0 | 556 | let updateService = Cc['@mozilla.org/offlinecacheupdate-service;1'] |
michael@0 | 557 | .getService(Ci.nsIOfflineCacheUpdateService); |
michael@0 | 558 | updateService.scheduleUpdate(manifestURI, documentURI, window); |
michael@0 | 559 | } catch (e) { |
michael@0 | 560 | dump('Error while creating offline cache: ' + e + '\n'); |
michael@0 | 561 | } |
michael@0 | 562 | break; |
michael@0 | 563 | case 'MozAfterPaint': |
michael@0 | 564 | window.removeEventListener('MozAfterPaint', this); |
michael@0 | 565 | this.sendChromeEvent({ |
michael@0 | 566 | type: 'system-first-paint' |
michael@0 | 567 | }); |
michael@0 | 568 | break; |
michael@0 | 569 | case 'unload': |
michael@0 | 570 | this.stop(); |
michael@0 | 571 | break; |
michael@0 | 572 | } |
michael@0 | 573 | }, |
michael@0 | 574 | |
michael@0 | 575 | // Send an event to a specific window, document or element. |
michael@0 | 576 | sendEvent: function shell_sendEvent(target, type, details) { |
michael@0 | 577 | let doc = target.document || target.ownerDocument || target; |
michael@0 | 578 | let event = doc.createEvent('CustomEvent'); |
michael@0 | 579 | event.initCustomEvent(type, true, true, details ? details : {}); |
michael@0 | 580 | target.dispatchEvent(event); |
michael@0 | 581 | }, |
michael@0 | 582 | |
michael@0 | 583 | sendCustomEvent: function shell_sendCustomEvent(type, details) { |
michael@0 | 584 | let target = getContentWindow(); |
michael@0 | 585 | let payload = details ? Cu.cloneInto(details, target) : {}; |
michael@0 | 586 | this.sendEvent(target, type, payload); |
michael@0 | 587 | }, |
michael@0 | 588 | |
michael@0 | 589 | sendChromeEvent: function shell_sendChromeEvent(details) { |
michael@0 | 590 | if (!this.isHomeLoaded) { |
michael@0 | 591 | if (!('pendingChromeEvents' in this)) { |
michael@0 | 592 | this.pendingChromeEvents = []; |
michael@0 | 593 | } |
michael@0 | 594 | |
michael@0 | 595 | this.pendingChromeEvents.push(details); |
michael@0 | 596 | return; |
michael@0 | 597 | } |
michael@0 | 598 | |
michael@0 | 599 | this.sendEvent(getContentWindow(), "mozChromeEvent", |
michael@0 | 600 | Cu.cloneInto(details, getContentWindow())); |
michael@0 | 601 | }, |
michael@0 | 602 | |
michael@0 | 603 | openAppForSystemMessage: function shell_openAppForSystemMessage(msg) { |
michael@0 | 604 | let payload = { |
michael@0 | 605 | url: msg.pageURL, |
michael@0 | 606 | manifestURL: msg.manifestURL, |
michael@0 | 607 | isActivity: (msg.type == 'activity'), |
michael@0 | 608 | onlyShowApp: msg.onlyShowApp, |
michael@0 | 609 | showApp: msg.showApp, |
michael@0 | 610 | target: msg.target, |
michael@0 | 611 | expectingSystemMessage: true, |
michael@0 | 612 | extra: msg.extra |
michael@0 | 613 | } |
michael@0 | 614 | this.sendCustomEvent('open-app', payload); |
michael@0 | 615 | }, |
michael@0 | 616 | |
michael@0 | 617 | receiveMessage: function shell_receiveMessage(message) { |
michael@0 | 618 | var activities = { 'content-handler': { name: 'view', response: null }, |
michael@0 | 619 | 'dial-handler': { name: 'dial', response: null }, |
michael@0 | 620 | 'mail-handler': { name: 'new', response: null }, |
michael@0 | 621 | 'sms-handler': { name: 'new', response: null }, |
michael@0 | 622 | 'file-picker': { name: 'pick', response: 'file-picked' } }; |
michael@0 | 623 | |
michael@0 | 624 | if (!(message.name in activities)) |
michael@0 | 625 | return; |
michael@0 | 626 | |
michael@0 | 627 | let data = message.data; |
michael@0 | 628 | let activity = activities[message.name]; |
michael@0 | 629 | |
michael@0 | 630 | let a = new MozActivity({ |
michael@0 | 631 | name: activity.name, |
michael@0 | 632 | data: data |
michael@0 | 633 | }); |
michael@0 | 634 | |
michael@0 | 635 | if (activity.response) { |
michael@0 | 636 | a.onsuccess = function() { |
michael@0 | 637 | let sender = message.target.QueryInterface(Ci.nsIMessageSender); |
michael@0 | 638 | sender.sendAsyncMessage(activity.response, { success: true, |
michael@0 | 639 | result: a.result }); |
michael@0 | 640 | } |
michael@0 | 641 | a.onerror = function() { |
michael@0 | 642 | let sender = message.target.QueryInterface(Ci.nsIMessageSender); |
michael@0 | 643 | sender.sendAsyncMessage(activity.response, { success: false }); |
michael@0 | 644 | } |
michael@0 | 645 | } |
michael@0 | 646 | }, |
michael@0 | 647 | |
michael@0 | 648 | notifyContentStart: function shell_notifyContentStart() { |
michael@0 | 649 | this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true); |
michael@0 | 650 | this.contentBrowser.removeEventListener('mozbrowserlocationchange', this, true); |
michael@0 | 651 | |
michael@0 | 652 | let content = this.contentBrowser.contentWindow; |
michael@0 | 653 | |
michael@0 | 654 | this.reportCrash(true); |
michael@0 | 655 | |
michael@0 | 656 | this.sendEvent(window, 'ContentStart'); |
michael@0 | 657 | |
michael@0 | 658 | Services.obs.notifyObservers(null, 'content-start', null); |
michael@0 | 659 | |
michael@0 | 660 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 661 | Cu.import('resource://gre/modules/OperatorApps.jsm'); |
michael@0 | 662 | #endif |
michael@0 | 663 | |
michael@0 | 664 | content.addEventListener('load', function shell_homeLoaded() { |
michael@0 | 665 | content.removeEventListener('load', shell_homeLoaded); |
michael@0 | 666 | shell.isHomeLoaded = true; |
michael@0 | 667 | |
michael@0 | 668 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 669 | libcutils.property_set('sys.boot_completed', '1'); |
michael@0 | 670 | #endif |
michael@0 | 671 | |
michael@0 | 672 | Services.obs.notifyObservers(null, "browser-ui-startup-complete", ""); |
michael@0 | 673 | |
michael@0 | 674 | SystemAppProxy.setIsReady(); |
michael@0 | 675 | if ('pendingChromeEvents' in shell) { |
michael@0 | 676 | shell.pendingChromeEvents.forEach((shell.sendChromeEvent).bind(shell)); |
michael@0 | 677 | } |
michael@0 | 678 | delete shell.pendingChromeEvents; |
michael@0 | 679 | }); |
michael@0 | 680 | } |
michael@0 | 681 | }; |
michael@0 | 682 | |
michael@0 | 683 | // Listen for the request of opening app and relay them to Gaia. |
michael@0 | 684 | Services.obs.addObserver(function onSystemMessageOpenApp(subject, topic, data) { |
michael@0 | 685 | let msg = JSON.parse(data); |
michael@0 | 686 | // Buffer non-activity request until content starts to load for 10 seconds. |
michael@0 | 687 | // We'll revisit this later if new kind of requests don't need to be cached. |
michael@0 | 688 | if (shell.needBufferOpenAppReq && msg.type !== 'activity') { |
michael@0 | 689 | shell.bufferedOpenAppReqs.push(msg); |
michael@0 | 690 | return; |
michael@0 | 691 | } |
michael@0 | 692 | shell.openAppForSystemMessage(msg); |
michael@0 | 693 | }, 'system-messages-open-app', false); |
michael@0 | 694 | |
michael@0 | 695 | Services.obs.addObserver(function onInterAppCommConnect(subject, topic, data) { |
michael@0 | 696 | data = JSON.parse(data); |
michael@0 | 697 | shell.sendChromeEvent({ type: "inter-app-comm-permission", |
michael@0 | 698 | chromeEventID: data.callerID, |
michael@0 | 699 | manifestURL: data.manifestURL, |
michael@0 | 700 | keyword: data.keyword, |
michael@0 | 701 | peers: data.appsToSelect }); |
michael@0 | 702 | }, 'inter-app-comm-select-app', false); |
michael@0 | 703 | |
michael@0 | 704 | Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data) { |
michael@0 | 705 | shell.sendChromeEvent({ type: "fullscreenoriginchange", |
michael@0 | 706 | fullscreenorigin: data }); |
michael@0 | 707 | }, "fullscreen-origin-change", false); |
michael@0 | 708 | |
michael@0 | 709 | DOMApplicationRegistry.registryStarted.then(function () { |
michael@0 | 710 | shell.sendChromeEvent({ type: 'webapps-registry-start' }); |
michael@0 | 711 | }); |
michael@0 | 712 | DOMApplicationRegistry.registryReady.then(function () { |
michael@0 | 713 | shell.sendChromeEvent({ type: 'webapps-registry-ready' }); |
michael@0 | 714 | }); |
michael@0 | 715 | |
michael@0 | 716 | Services.obs.addObserver(function onBluetoothVolumeChange(subject, topic, data) { |
michael@0 | 717 | shell.sendChromeEvent({ |
michael@0 | 718 | type: "bluetooth-volumeset", |
michael@0 | 719 | value: data |
michael@0 | 720 | }); |
michael@0 | 721 | }, 'bluetooth-volume-change', false); |
michael@0 | 722 | |
michael@0 | 723 | Services.obs.addObserver(function(subject, topic, data) { |
michael@0 | 724 | shell.sendCustomEvent('mozmemorypressure'); |
michael@0 | 725 | }, 'memory-pressure', false); |
michael@0 | 726 | |
michael@0 | 727 | var CustomEventManager = { |
michael@0 | 728 | init: function custevt_init() { |
michael@0 | 729 | window.addEventListener("ContentStart", (function(evt) { |
michael@0 | 730 | let content = shell.contentBrowser.contentWindow; |
michael@0 | 731 | content.addEventListener("mozContentEvent", this, false, true); |
michael@0 | 732 | |
michael@0 | 733 | // After content starts to load for 10 seconds, send and |
michael@0 | 734 | // clean up the buffered open-app requests if there is any. |
michael@0 | 735 | // |
michael@0 | 736 | // TODO: Bug 793420 - Remove the waiting timer for the 'open-app' |
michael@0 | 737 | // mozChromeEvents requested by System Message |
michael@0 | 738 | shell.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
michael@0 | 739 | shell.timer.initWithCallback(function timerCallback() { |
michael@0 | 740 | shell.bufferedOpenAppReqs.forEach(function bufferOpenAppReq(msg) { |
michael@0 | 741 | shell.openAppForSystemMessage(msg); |
michael@0 | 742 | }); |
michael@0 | 743 | shell.bufferedOpenAppReqs.length = 0; |
michael@0 | 744 | shell.needBufferOpenAppReq = false; |
michael@0 | 745 | shell.timer = null; |
michael@0 | 746 | }, 10000, Ci.nsITimer.TYPE_ONE_SHOT); |
michael@0 | 747 | }).bind(this), false); |
michael@0 | 748 | }, |
michael@0 | 749 | |
michael@0 | 750 | handleEvent: function custevt_handleEvent(evt) { |
michael@0 | 751 | let detail = evt.detail; |
michael@0 | 752 | dump('XXX FIXME : Got a mozContentEvent: ' + detail.type + "\n"); |
michael@0 | 753 | |
michael@0 | 754 | switch(detail.type) { |
michael@0 | 755 | case 'desktop-notification-show': |
michael@0 | 756 | case 'desktop-notification-click': |
michael@0 | 757 | case 'desktop-notification-close': |
michael@0 | 758 | AlertsHelper.handleEvent(detail); |
michael@0 | 759 | break; |
michael@0 | 760 | case 'webapps-install-granted': |
michael@0 | 761 | case 'webapps-install-denied': |
michael@0 | 762 | WebappsHelper.handleEvent(detail); |
michael@0 | 763 | break; |
michael@0 | 764 | case 'select-choicechange': |
michael@0 | 765 | FormsHelper.handleEvent(detail); |
michael@0 | 766 | break; |
michael@0 | 767 | case 'system-message-listener-ready': |
michael@0 | 768 | Services.obs.notifyObservers(null, 'system-message-listener-ready', null); |
michael@0 | 769 | break; |
michael@0 | 770 | case 'remote-debugger-prompt': |
michael@0 | 771 | RemoteDebugger.handleEvent(detail); |
michael@0 | 772 | break; |
michael@0 | 773 | case 'captive-portal-login-cancel': |
michael@0 | 774 | CaptivePortalLoginHelper.handleEvent(detail); |
michael@0 | 775 | break; |
michael@0 | 776 | case 'inter-app-comm-permission': |
michael@0 | 777 | Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result', |
michael@0 | 778 | JSON.stringify({ callerID: detail.chromeEventID, |
michael@0 | 779 | keyword: detail.keyword, |
michael@0 | 780 | manifestURL: detail.manifestURL, |
michael@0 | 781 | selectedApps: detail.peers })); |
michael@0 | 782 | break; |
michael@0 | 783 | case 'inputmethod-update-layouts': |
michael@0 | 784 | KeyboardHelper.handleEvent(detail); |
michael@0 | 785 | break; |
michael@0 | 786 | } |
michael@0 | 787 | } |
michael@0 | 788 | } |
michael@0 | 789 | |
michael@0 | 790 | var AlertsHelper = { |
michael@0 | 791 | _listeners: {}, |
michael@0 | 792 | _count: 0, |
michael@0 | 793 | |
michael@0 | 794 | handleEvent: function alert_handleEvent(detail) { |
michael@0 | 795 | if (!detail || !detail.id) |
michael@0 | 796 | return; |
michael@0 | 797 | |
michael@0 | 798 | let uid = detail.id; |
michael@0 | 799 | let listener = this._listeners[uid]; |
michael@0 | 800 | if (!listener) |
michael@0 | 801 | return; |
michael@0 | 802 | |
michael@0 | 803 | let topic; |
michael@0 | 804 | if (detail.type == "desktop-notification-click") { |
michael@0 | 805 | topic = "alertclickcallback"; |
michael@0 | 806 | } else if (detail.type == "desktop-notification-show") { |
michael@0 | 807 | topic = "alertshow"; |
michael@0 | 808 | } else { |
michael@0 | 809 | /* desktop-notification-close */ |
michael@0 | 810 | topic = "alertfinished"; |
michael@0 | 811 | } |
michael@0 | 812 | |
michael@0 | 813 | if (uid.startsWith("alert")) { |
michael@0 | 814 | try { |
michael@0 | 815 | listener.observer.observe(null, topic, listener.cookie); |
michael@0 | 816 | } catch (e) { } |
michael@0 | 817 | } else { |
michael@0 | 818 | try { |
michael@0 | 819 | listener.mm.sendAsyncMessage("app-notification-return", { |
michael@0 | 820 | uid: uid, |
michael@0 | 821 | topic: topic, |
michael@0 | 822 | target: listener.target |
michael@0 | 823 | }); |
michael@0 | 824 | } catch (e) { |
michael@0 | 825 | // we get an exception if the app is not launched yet |
michael@0 | 826 | gSystemMessenger.sendMessage("notification", { |
michael@0 | 827 | clicked: (detail.type === "desktop-notification-click"), |
michael@0 | 828 | title: listener.title, |
michael@0 | 829 | body: listener.text, |
michael@0 | 830 | imageURL: listener.imageURL, |
michael@0 | 831 | lang: listener.lang, |
michael@0 | 832 | dir: listener.dir, |
michael@0 | 833 | id: listener.id, |
michael@0 | 834 | tag: listener.tag |
michael@0 | 835 | }, |
michael@0 | 836 | Services.io.newURI(listener.target, null, null), |
michael@0 | 837 | Services.io.newURI(listener.manifestURL, null, null) |
michael@0 | 838 | ); |
michael@0 | 839 | } |
michael@0 | 840 | } |
michael@0 | 841 | |
michael@0 | 842 | // we're done with this notification |
michael@0 | 843 | if (topic === "alertfinished") { |
michael@0 | 844 | delete this._listeners[uid]; |
michael@0 | 845 | } |
michael@0 | 846 | }, |
michael@0 | 847 | |
michael@0 | 848 | registerListener: function alert_registerListener(alertId, cookie, alertListener) { |
michael@0 | 849 | this._listeners[alertId] = { observer: alertListener, cookie: cookie }; |
michael@0 | 850 | }, |
michael@0 | 851 | |
michael@0 | 852 | registerAppListener: function alert_registerAppListener(uid, listener) { |
michael@0 | 853 | this._listeners[uid] = listener; |
michael@0 | 854 | |
michael@0 | 855 | let app = DOMApplicationRegistry.getAppByManifestURL(listener.manifestURL); |
michael@0 | 856 | DOMApplicationRegistry.getManifestFor(app.manifestURL).then((manifest) => { |
michael@0 | 857 | let helper = new ManifestHelper(manifest, app.origin); |
michael@0 | 858 | let getNotificationURLFor = function(messages) { |
michael@0 | 859 | if (!messages) |
michael@0 | 860 | return null; |
michael@0 | 861 | |
michael@0 | 862 | for (let i = 0; i < messages.length; i++) { |
michael@0 | 863 | let message = messages[i]; |
michael@0 | 864 | if (message === "notification") { |
michael@0 | 865 | return helper.fullLaunchPath(); |
michael@0 | 866 | } else if (typeof message == "object" && "notification" in message) { |
michael@0 | 867 | return helper.resolveFromOrigin(message["notification"]); |
michael@0 | 868 | } |
michael@0 | 869 | } |
michael@0 | 870 | |
michael@0 | 871 | // No message found... |
michael@0 | 872 | return null; |
michael@0 | 873 | } |
michael@0 | 874 | |
michael@0 | 875 | listener.target = getNotificationURLFor(manifest.messages); |
michael@0 | 876 | |
michael@0 | 877 | // Bug 816944 - Support notification messages for entry_points. |
michael@0 | 878 | }); |
michael@0 | 879 | }, |
michael@0 | 880 | |
michael@0 | 881 | showNotification: function alert_showNotification(imageURL, |
michael@0 | 882 | title, |
michael@0 | 883 | text, |
michael@0 | 884 | textClickable, |
michael@0 | 885 | cookie, |
michael@0 | 886 | uid, |
michael@0 | 887 | bidi, |
michael@0 | 888 | lang, |
michael@0 | 889 | manifestURL) { |
michael@0 | 890 | function send(appName, appIcon) { |
michael@0 | 891 | shell.sendChromeEvent({ |
michael@0 | 892 | type: "desktop-notification", |
michael@0 | 893 | id: uid, |
michael@0 | 894 | icon: imageURL, |
michael@0 | 895 | title: title, |
michael@0 | 896 | text: text, |
michael@0 | 897 | bidi: bidi, |
michael@0 | 898 | lang: lang, |
michael@0 | 899 | appName: appName, |
michael@0 | 900 | appIcon: appIcon, |
michael@0 | 901 | manifestURL: manifestURL |
michael@0 | 902 | }); |
michael@0 | 903 | } |
michael@0 | 904 | |
michael@0 | 905 | if (!manifestURL || !manifestURL.length) { |
michael@0 | 906 | send(null, null); |
michael@0 | 907 | return; |
michael@0 | 908 | } |
michael@0 | 909 | |
michael@0 | 910 | // If we have a manifest URL, get the icon and title from the manifest |
michael@0 | 911 | // to prevent spoofing. |
michael@0 | 912 | let app = DOMApplicationRegistry.getAppByManifestURL(manifestURL); |
michael@0 | 913 | DOMApplicationRegistry.getManifestFor(manifestURL).then((aManifest) => { |
michael@0 | 914 | let helper = new ManifestHelper(aManifest, app.origin); |
michael@0 | 915 | send(helper.name, helper.iconURLForSize(128)); |
michael@0 | 916 | }); |
michael@0 | 917 | }, |
michael@0 | 918 | |
michael@0 | 919 | showAlertNotification: function alert_showAlertNotification(imageURL, |
michael@0 | 920 | title, |
michael@0 | 921 | text, |
michael@0 | 922 | textClickable, |
michael@0 | 923 | cookie, |
michael@0 | 924 | alertListener, |
michael@0 | 925 | name, |
michael@0 | 926 | bidi, |
michael@0 | 927 | lang) { |
michael@0 | 928 | let currentListener = this._listeners[name]; |
michael@0 | 929 | if (currentListener) { |
michael@0 | 930 | currentListener.observer.observe(null, "alertfinished", currentListener.cookie); |
michael@0 | 931 | } |
michael@0 | 932 | |
michael@0 | 933 | this.registerListener(name, cookie, alertListener); |
michael@0 | 934 | this.showNotification(imageURL, title, text, textClickable, cookie, |
michael@0 | 935 | name, bidi, lang, null); |
michael@0 | 936 | }, |
michael@0 | 937 | |
michael@0 | 938 | closeAlert: function alert_closeAlert(name) { |
michael@0 | 939 | shell.sendChromeEvent({ |
michael@0 | 940 | type: "desktop-notification-close", |
michael@0 | 941 | id: name |
michael@0 | 942 | }); |
michael@0 | 943 | }, |
michael@0 | 944 | |
michael@0 | 945 | receiveMessage: function alert_receiveMessage(aMessage) { |
michael@0 | 946 | if (!aMessage.target.assertAppHasPermission("desktop-notification")) { |
michael@0 | 947 | Cu.reportError("Desktop-notification message " + aMessage.name + |
michael@0 | 948 | " from a content process with no desktop-notification privileges."); |
michael@0 | 949 | return; |
michael@0 | 950 | } |
michael@0 | 951 | |
michael@0 | 952 | let data = aMessage.data; |
michael@0 | 953 | let details = data.details; |
michael@0 | 954 | let listener = { |
michael@0 | 955 | mm: aMessage.target, |
michael@0 | 956 | title: data.title, |
michael@0 | 957 | text: data.text, |
michael@0 | 958 | manifestURL: details.manifestURL, |
michael@0 | 959 | imageURL: data.imageURL, |
michael@0 | 960 | lang: details.lang || undefined, |
michael@0 | 961 | id: details.id || undefined, |
michael@0 | 962 | dir: details.dir || undefined, |
michael@0 | 963 | tag: details.tag || undefined |
michael@0 | 964 | }; |
michael@0 | 965 | this.registerAppListener(data.uid, listener); |
michael@0 | 966 | |
michael@0 | 967 | this.showNotification(data.imageURL, data.title, data.text, |
michael@0 | 968 | details.textClickable, null, |
michael@0 | 969 | data.uid, details.dir, |
michael@0 | 970 | details.lang, details.manifestURL); |
michael@0 | 971 | }, |
michael@0 | 972 | } |
michael@0 | 973 | |
michael@0 | 974 | var WebappsHelper = { |
michael@0 | 975 | _installers: {}, |
michael@0 | 976 | _count: 0, |
michael@0 | 977 | |
michael@0 | 978 | init: function webapps_init() { |
michael@0 | 979 | Services.obs.addObserver(this, "webapps-launch", false); |
michael@0 | 980 | Services.obs.addObserver(this, "webapps-ask-install", false); |
michael@0 | 981 | Services.obs.addObserver(this, "webapps-close", false); |
michael@0 | 982 | }, |
michael@0 | 983 | |
michael@0 | 984 | registerInstaller: function webapps_registerInstaller(data) { |
michael@0 | 985 | let id = "installer" + this._count++; |
michael@0 | 986 | this._installers[id] = data; |
michael@0 | 987 | return id; |
michael@0 | 988 | }, |
michael@0 | 989 | |
michael@0 | 990 | handleEvent: function webapps_handleEvent(detail) { |
michael@0 | 991 | if (!detail || !detail.id) |
michael@0 | 992 | return; |
michael@0 | 993 | |
michael@0 | 994 | let installer = this._installers[detail.id]; |
michael@0 | 995 | delete this._installers[detail.id]; |
michael@0 | 996 | switch (detail.type) { |
michael@0 | 997 | case "webapps-install-granted": |
michael@0 | 998 | DOMApplicationRegistry.confirmInstall(installer); |
michael@0 | 999 | break; |
michael@0 | 1000 | case "webapps-install-denied": |
michael@0 | 1001 | DOMApplicationRegistry.denyInstall(installer); |
michael@0 | 1002 | break; |
michael@0 | 1003 | } |
michael@0 | 1004 | }, |
michael@0 | 1005 | |
michael@0 | 1006 | observe: function webapps_observe(subject, topic, data) { |
michael@0 | 1007 | let json = JSON.parse(data); |
michael@0 | 1008 | json.mm = subject; |
michael@0 | 1009 | |
michael@0 | 1010 | switch(topic) { |
michael@0 | 1011 | case "webapps-launch": |
michael@0 | 1012 | DOMApplicationRegistry.getManifestFor(json.manifestURL).then((aManifest) => { |
michael@0 | 1013 | if (!aManifest) |
michael@0 | 1014 | return; |
michael@0 | 1015 | |
michael@0 | 1016 | let manifest = new ManifestHelper(aManifest, json.origin); |
michael@0 | 1017 | let payload = { |
michael@0 | 1018 | __exposedProps__: { |
michael@0 | 1019 | timestamp: "r", |
michael@0 | 1020 | url: "r", |
michael@0 | 1021 | manifestURL: "r" |
michael@0 | 1022 | }, |
michael@0 | 1023 | timestamp: json.timestamp, |
michael@0 | 1024 | url: manifest.fullLaunchPath(json.startPoint), |
michael@0 | 1025 | manifestURL: json.manifestURL |
michael@0 | 1026 | } |
michael@0 | 1027 | shell.sendEvent(getContentWindow(), "webapps-launch", payload); |
michael@0 | 1028 | }); |
michael@0 | 1029 | break; |
michael@0 | 1030 | case "webapps-ask-install": |
michael@0 | 1031 | let id = this.registerInstaller(json); |
michael@0 | 1032 | shell.sendChromeEvent({ |
michael@0 | 1033 | type: "webapps-ask-install", |
michael@0 | 1034 | id: id, |
michael@0 | 1035 | app: json.app |
michael@0 | 1036 | }); |
michael@0 | 1037 | break; |
michael@0 | 1038 | case "webapps-close": |
michael@0 | 1039 | shell.sendEvent(getContentWindow(), "webapps-close", |
michael@0 | 1040 | { |
michael@0 | 1041 | __exposedProps__: { "manifestURL": "r" }, |
michael@0 | 1042 | "manifestURL": json.manifestURL |
michael@0 | 1043 | }); |
michael@0 | 1044 | break; |
michael@0 | 1045 | } |
michael@0 | 1046 | } |
michael@0 | 1047 | } |
michael@0 | 1048 | |
michael@0 | 1049 | let IndexedDBPromptHelper = { |
michael@0 | 1050 | _quotaPrompt: "indexedDB-quota-prompt", |
michael@0 | 1051 | _quotaResponse: "indexedDB-quota-response", |
michael@0 | 1052 | |
michael@0 | 1053 | init: |
michael@0 | 1054 | function IndexedDBPromptHelper_init() { |
michael@0 | 1055 | Services.obs.addObserver(this, this._quotaPrompt, false); |
michael@0 | 1056 | }, |
michael@0 | 1057 | |
michael@0 | 1058 | uninit: |
michael@0 | 1059 | function IndexedDBPromptHelper_uninit() { |
michael@0 | 1060 | Services.obs.removeObserver(this, this._quotaPrompt); |
michael@0 | 1061 | }, |
michael@0 | 1062 | |
michael@0 | 1063 | observe: |
michael@0 | 1064 | function IndexedDBPromptHelper_observe(subject, topic, data) { |
michael@0 | 1065 | if (topic != this._quotaPrompt) { |
michael@0 | 1066 | throw new Error("Unexpected topic!"); |
michael@0 | 1067 | } |
michael@0 | 1068 | |
michael@0 | 1069 | let observer = subject.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 1070 | .getInterface(Ci.nsIObserver); |
michael@0 | 1071 | let responseTopic = this._quotaResponse; |
michael@0 | 1072 | |
michael@0 | 1073 | setTimeout(function() { |
michael@0 | 1074 | observer.observe(null, responseTopic, |
michael@0 | 1075 | Ci.nsIPermissionManager.DENY_ACTION); |
michael@0 | 1076 | }, 0); |
michael@0 | 1077 | } |
michael@0 | 1078 | } |
michael@0 | 1079 | |
michael@0 | 1080 | let RemoteDebugger = { |
michael@0 | 1081 | _promptDone: false, |
michael@0 | 1082 | _promptAnswer: false, |
michael@0 | 1083 | _running: false, |
michael@0 | 1084 | |
michael@0 | 1085 | prompt: function debugger_prompt() { |
michael@0 | 1086 | this._promptDone = false; |
michael@0 | 1087 | |
michael@0 | 1088 | shell.sendChromeEvent({ |
michael@0 | 1089 | "type": "remote-debugger-prompt" |
michael@0 | 1090 | }); |
michael@0 | 1091 | |
michael@0 | 1092 | while(!this._promptDone) { |
michael@0 | 1093 | Services.tm.currentThread.processNextEvent(true); |
michael@0 | 1094 | } |
michael@0 | 1095 | |
michael@0 | 1096 | return this._promptAnswer; |
michael@0 | 1097 | }, |
michael@0 | 1098 | |
michael@0 | 1099 | handleEvent: function debugger_handleEvent(detail) { |
michael@0 | 1100 | this._promptAnswer = detail.value; |
michael@0 | 1101 | this._promptDone = true; |
michael@0 | 1102 | }, |
michael@0 | 1103 | |
michael@0 | 1104 | get isDebugging() { |
michael@0 | 1105 | if (!this._running) { |
michael@0 | 1106 | return false; |
michael@0 | 1107 | } |
michael@0 | 1108 | |
michael@0 | 1109 | return DebuggerServer._connections && |
michael@0 | 1110 | Object.keys(DebuggerServer._connections).length > 0; |
michael@0 | 1111 | }, |
michael@0 | 1112 | |
michael@0 | 1113 | // Start the debugger server. |
michael@0 | 1114 | start: function debugger_start() { |
michael@0 | 1115 | if (this._running) { |
michael@0 | 1116 | return; |
michael@0 | 1117 | } |
michael@0 | 1118 | |
michael@0 | 1119 | if (!DebuggerServer.initialized) { |
michael@0 | 1120 | // Ask for remote connections. |
michael@0 | 1121 | DebuggerServer.init(this.prompt.bind(this)); |
michael@0 | 1122 | |
michael@0 | 1123 | // /!\ Be careful when adding a new actor, especially global actors. |
michael@0 | 1124 | // Any new global actor will be exposed and returned by the root actor. |
michael@0 | 1125 | |
michael@0 | 1126 | // Add Firefox-specific actors, but prevent tab actors to be loaded in |
michael@0 | 1127 | // the parent process, unless we enable certified apps debugging. |
michael@0 | 1128 | let restrictPrivileges = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps"); |
michael@0 | 1129 | DebuggerServer.addBrowserActors("navigator:browser", restrictPrivileges); |
michael@0 | 1130 | |
michael@0 | 1131 | /** |
michael@0 | 1132 | * Construct a root actor appropriate for use in a server running in B2G. |
michael@0 | 1133 | * The returned root actor respects the factories registered with |
michael@0 | 1134 | * DebuggerServer.addGlobalActor only if certified apps debugging is on, |
michael@0 | 1135 | * otherwise we used an explicit limited list of global actors |
michael@0 | 1136 | * |
michael@0 | 1137 | * * @param connection DebuggerServerConnection |
michael@0 | 1138 | * The conection to the client. |
michael@0 | 1139 | */ |
michael@0 | 1140 | DebuggerServer.createRootActor = function createRootActor(connection) |
michael@0 | 1141 | { |
michael@0 | 1142 | let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); |
michael@0 | 1143 | let parameters = { |
michael@0 | 1144 | // We do not expose browser tab actors yet, |
michael@0 | 1145 | // but we still have to define tabList.getList(), |
michael@0 | 1146 | // otherwise, client won't be able to fetch global actors |
michael@0 | 1147 | // from listTabs request! |
michael@0 | 1148 | tabList: { |
michael@0 | 1149 | getList: function() { |
michael@0 | 1150 | return promise.resolve([]); |
michael@0 | 1151 | } |
michael@0 | 1152 | }, |
michael@0 | 1153 | // Use an explicit global actor list to prevent exposing |
michael@0 | 1154 | // unexpected actors |
michael@0 | 1155 | globalActorFactories: restrictPrivileges ? { |
michael@0 | 1156 | webappsActor: DebuggerServer.globalActorFactories.webappsActor, |
michael@0 | 1157 | deviceActor: DebuggerServer.globalActorFactories.deviceActor, |
michael@0 | 1158 | } : DebuggerServer.globalActorFactories |
michael@0 | 1159 | }; |
michael@0 | 1160 | let root = new DebuggerServer.RootActor(connection, parameters); |
michael@0 | 1161 | root.applicationType = "operating-system"; |
michael@0 | 1162 | return root; |
michael@0 | 1163 | }; |
michael@0 | 1164 | |
michael@0 | 1165 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 1166 | DebuggerServer.on("connectionchange", function() { |
michael@0 | 1167 | AdbController.updateState(); |
michael@0 | 1168 | }); |
michael@0 | 1169 | #endif |
michael@0 | 1170 | } |
michael@0 | 1171 | |
michael@0 | 1172 | let path = Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") || |
michael@0 | 1173 | "/data/local/debugger-socket"; |
michael@0 | 1174 | try { |
michael@0 | 1175 | DebuggerServer.openListener(path); |
michael@0 | 1176 | // Temporary event, until bug 942756 lands and offers a way to know |
michael@0 | 1177 | // when the server is up and running. |
michael@0 | 1178 | Services.obs.notifyObservers(null, 'debugger-server-started', null); |
michael@0 | 1179 | this._running = true; |
michael@0 | 1180 | } catch (e) { |
michael@0 | 1181 | dump('Unable to start debugger server: ' + e + '\n'); |
michael@0 | 1182 | } |
michael@0 | 1183 | }, |
michael@0 | 1184 | |
michael@0 | 1185 | stop: function debugger_stop() { |
michael@0 | 1186 | if (!this._running) { |
michael@0 | 1187 | return; |
michael@0 | 1188 | } |
michael@0 | 1189 | |
michael@0 | 1190 | if (!DebuggerServer.initialized) { |
michael@0 | 1191 | // Can this really happen if we are running? |
michael@0 | 1192 | this._running = false; |
michael@0 | 1193 | return; |
michael@0 | 1194 | } |
michael@0 | 1195 | |
michael@0 | 1196 | try { |
michael@0 | 1197 | DebuggerServer.closeListener(); |
michael@0 | 1198 | } catch (e) { |
michael@0 | 1199 | dump('Unable to stop debugger server: ' + e + '\n'); |
michael@0 | 1200 | } |
michael@0 | 1201 | this._running = false; |
michael@0 | 1202 | } |
michael@0 | 1203 | } |
michael@0 | 1204 | |
michael@0 | 1205 | let KeyboardHelper = { |
michael@0 | 1206 | handleEvent: function keyboard_handleEvent(detail) { |
michael@0 | 1207 | Keyboard.setLayouts(detail.layouts); |
michael@0 | 1208 | } |
michael@0 | 1209 | }; |
michael@0 | 1210 | |
michael@0 | 1211 | // This is the backend for Gaia's screenshot feature. Gaia requests a |
michael@0 | 1212 | // screenshot by sending a mozContentEvent with detail.type set to |
michael@0 | 1213 | // 'take-screenshot'. Then we take a screenshot and send a |
michael@0 | 1214 | // mozChromeEvent with detail.type set to 'take-screenshot-success' |
michael@0 | 1215 | // and detail.file set to the an image/png blob |
michael@0 | 1216 | window.addEventListener('ContentStart', function ss_onContentStart() { |
michael@0 | 1217 | let content = shell.contentBrowser.contentWindow; |
michael@0 | 1218 | content.addEventListener('mozContentEvent', function ss_onMozContentEvent(e) { |
michael@0 | 1219 | if (e.detail.type !== 'take-screenshot') |
michael@0 | 1220 | return; |
michael@0 | 1221 | |
michael@0 | 1222 | try { |
michael@0 | 1223 | var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', |
michael@0 | 1224 | 'canvas'); |
michael@0 | 1225 | var width = window.innerWidth; |
michael@0 | 1226 | var height = window.innerHeight; |
michael@0 | 1227 | var scale = window.devicePixelRatio; |
michael@0 | 1228 | canvas.setAttribute('width', width * scale); |
michael@0 | 1229 | canvas.setAttribute('height', height * scale); |
michael@0 | 1230 | |
michael@0 | 1231 | var context = canvas.getContext('2d'); |
michael@0 | 1232 | var flags = |
michael@0 | 1233 | context.DRAWWINDOW_DRAW_CARET | |
michael@0 | 1234 | context.DRAWWINDOW_DRAW_VIEW | |
michael@0 | 1235 | context.DRAWWINDOW_USE_WIDGET_LAYERS; |
michael@0 | 1236 | context.scale(scale, scale); |
michael@0 | 1237 | context.drawWindow(window, 0, 0, width, height, |
michael@0 | 1238 | 'rgb(255,255,255)', flags); |
michael@0 | 1239 | |
michael@0 | 1240 | // I can't use sendChromeEvent() here because it doesn't wrap |
michael@0 | 1241 | // the blob in the detail object correctly. So I use __exposedProps__ |
michael@0 | 1242 | // instead to safely send the chrome detail object to content. |
michael@0 | 1243 | shell.sendEvent(getContentWindow(), 'mozChromeEvent', { |
michael@0 | 1244 | __exposedProps__: { type: 'r', file: 'r' }, |
michael@0 | 1245 | type: 'take-screenshot-success', |
michael@0 | 1246 | file: canvas.mozGetAsFile('screenshot', 'image/png') |
michael@0 | 1247 | }); |
michael@0 | 1248 | } catch (e) { |
michael@0 | 1249 | dump('exception while creating screenshot: ' + e + '\n'); |
michael@0 | 1250 | shell.sendChromeEvent({ |
michael@0 | 1251 | type: 'take-screenshot-error', |
michael@0 | 1252 | error: String(e) |
michael@0 | 1253 | }); |
michael@0 | 1254 | } |
michael@0 | 1255 | }); |
michael@0 | 1256 | }); |
michael@0 | 1257 | |
michael@0 | 1258 | (function contentCrashTracker() { |
michael@0 | 1259 | Services.obs.addObserver(function(aSubject, aTopic, aData) { |
michael@0 | 1260 | let props = aSubject.QueryInterface(Ci.nsIPropertyBag2); |
michael@0 | 1261 | if (props.hasKey("abnormal") && props.hasKey("dumpID")) { |
michael@0 | 1262 | shell.reportCrash(false, props.getProperty("dumpID")); |
michael@0 | 1263 | } |
michael@0 | 1264 | }, |
michael@0 | 1265 | "ipc:content-shutdown", false); |
michael@0 | 1266 | })(); |
michael@0 | 1267 | |
michael@0 | 1268 | var CaptivePortalLoginHelper = { |
michael@0 | 1269 | init: function init() { |
michael@0 | 1270 | Services.obs.addObserver(this, 'captive-portal-login', false); |
michael@0 | 1271 | Services.obs.addObserver(this, 'captive-portal-login-abort', false); |
michael@0 | 1272 | }, |
michael@0 | 1273 | handleEvent: function handleEvent(detail) { |
michael@0 | 1274 | Services.captivePortalDetector.cancelLogin(detail.id); |
michael@0 | 1275 | }, |
michael@0 | 1276 | observe: function observe(subject, topic, data) { |
michael@0 | 1277 | shell.sendChromeEvent(JSON.parse(data)); |
michael@0 | 1278 | } |
michael@0 | 1279 | } |
michael@0 | 1280 | |
michael@0 | 1281 | // Listen for crashes submitted through the crash reporter UI. |
michael@0 | 1282 | window.addEventListener('ContentStart', function cr_onContentStart() { |
michael@0 | 1283 | let content = shell.contentBrowser.contentWindow; |
michael@0 | 1284 | content.addEventListener("mozContentEvent", function cr_onMozContentEvent(e) { |
michael@0 | 1285 | if (e.detail.type == "submit-crash" && e.detail.crashID) { |
michael@0 | 1286 | debugCrashReport("submitting crash at user request ", e.detail.crashID); |
michael@0 | 1287 | shell.submitCrash(e.detail.crashID); |
michael@0 | 1288 | } else if (e.detail.type == "delete-crash" && e.detail.crashID) { |
michael@0 | 1289 | debugCrashReport("deleting crash at user request ", e.detail.crashID); |
michael@0 | 1290 | shell.deleteCrash(e.detail.crashID); |
michael@0 | 1291 | } |
michael@0 | 1292 | }); |
michael@0 | 1293 | }); |
michael@0 | 1294 | |
michael@0 | 1295 | window.addEventListener('ContentStart', function update_onContentStart() { |
michael@0 | 1296 | Cu.import('resource://gre/modules/WebappsUpdater.jsm'); |
michael@0 | 1297 | WebappsUpdater.handleContentStart(shell); |
michael@0 | 1298 | |
michael@0 | 1299 | let promptCc = Cc["@mozilla.org/updates/update-prompt;1"]; |
michael@0 | 1300 | if (!promptCc) { |
michael@0 | 1301 | return; |
michael@0 | 1302 | } |
michael@0 | 1303 | |
michael@0 | 1304 | let updatePrompt = promptCc.createInstance(Ci.nsIUpdatePrompt); |
michael@0 | 1305 | if (!updatePrompt) { |
michael@0 | 1306 | return; |
michael@0 | 1307 | } |
michael@0 | 1308 | |
michael@0 | 1309 | updatePrompt.wrappedJSObject.handleContentStart(shell); |
michael@0 | 1310 | }); |
michael@0 | 1311 | |
michael@0 | 1312 | (function geolocationStatusTracker() { |
michael@0 | 1313 | let gGeolocationActive = false; |
michael@0 | 1314 | |
michael@0 | 1315 | Services.obs.addObserver(function(aSubject, aTopic, aData) { |
michael@0 | 1316 | let oldState = gGeolocationActive; |
michael@0 | 1317 | if (aData == "starting") { |
michael@0 | 1318 | gGeolocationActive = true; |
michael@0 | 1319 | } else if (aData == "shutdown") { |
michael@0 | 1320 | gGeolocationActive = false; |
michael@0 | 1321 | } |
michael@0 | 1322 | |
michael@0 | 1323 | if (gGeolocationActive != oldState) { |
michael@0 | 1324 | shell.sendChromeEvent({ |
michael@0 | 1325 | type: 'geolocation-status', |
michael@0 | 1326 | active: gGeolocationActive |
michael@0 | 1327 | }); |
michael@0 | 1328 | } |
michael@0 | 1329 | }, "geolocation-device-events", false); |
michael@0 | 1330 | })(); |
michael@0 | 1331 | |
michael@0 | 1332 | (function headphonesStatusTracker() { |
michael@0 | 1333 | Services.obs.addObserver(function(aSubject, aTopic, aData) { |
michael@0 | 1334 | shell.sendChromeEvent({ |
michael@0 | 1335 | type: 'headphones-status-changed', |
michael@0 | 1336 | state: aData |
michael@0 | 1337 | }); |
michael@0 | 1338 | }, "headphones-status-changed", false); |
michael@0 | 1339 | })(); |
michael@0 | 1340 | |
michael@0 | 1341 | (function audioChannelChangedTracker() { |
michael@0 | 1342 | Services.obs.addObserver(function(aSubject, aTopic, aData) { |
michael@0 | 1343 | shell.sendChromeEvent({ |
michael@0 | 1344 | type: 'audio-channel-changed', |
michael@0 | 1345 | channel: aData |
michael@0 | 1346 | }); |
michael@0 | 1347 | }, "audio-channel-changed", false); |
michael@0 | 1348 | })(); |
michael@0 | 1349 | |
michael@0 | 1350 | (function defaultVolumeChannelChangedTracker() { |
michael@0 | 1351 | Services.obs.addObserver(function(aSubject, aTopic, aData) { |
michael@0 | 1352 | shell.sendChromeEvent({ |
michael@0 | 1353 | type: 'default-volume-channel-changed', |
michael@0 | 1354 | channel: aData |
michael@0 | 1355 | }); |
michael@0 | 1356 | }, "default-volume-channel-changed", false); |
michael@0 | 1357 | })(); |
michael@0 | 1358 | |
michael@0 | 1359 | (function visibleAudioChannelChangedTracker() { |
michael@0 | 1360 | Services.obs.addObserver(function(aSubject, aTopic, aData) { |
michael@0 | 1361 | shell.sendChromeEvent({ |
michael@0 | 1362 | type: 'visible-audio-channel-changed', |
michael@0 | 1363 | channel: aData |
michael@0 | 1364 | }); |
michael@0 | 1365 | shell.visibleNormalAudioActive = (aData == 'normal'); |
michael@0 | 1366 | }, "visible-audio-channel-changed", false); |
michael@0 | 1367 | })(); |
michael@0 | 1368 | |
michael@0 | 1369 | (function recordingStatusTracker() { |
michael@0 | 1370 | // Recording status is tracked per process with following data structure: |
michael@0 | 1371 | // {<processId>: {<requestURL>: {isApp: <isApp>, |
michael@0 | 1372 | // count: <N>, |
michael@0 | 1373 | // audioCount: <N>, |
michael@0 | 1374 | // videoCount: <N>}} |
michael@0 | 1375 | let gRecordingActiveProcesses = {}; |
michael@0 | 1376 | |
michael@0 | 1377 | let recordingHandler = function(aSubject, aTopic, aData) { |
michael@0 | 1378 | let props = aSubject.QueryInterface(Ci.nsIPropertyBag2); |
michael@0 | 1379 | let processId = (props.hasKey('childID')) ? props.get('childID') |
michael@0 | 1380 | : 'main'; |
michael@0 | 1381 | if (processId && !gRecordingActiveProcesses.hasOwnProperty(processId)) { |
michael@0 | 1382 | gRecordingActiveProcesses[processId] = {}; |
michael@0 | 1383 | } |
michael@0 | 1384 | |
michael@0 | 1385 | let commandHandler = function (requestURL, command) { |
michael@0 | 1386 | let currentProcess = gRecordingActiveProcesses[processId]; |
michael@0 | 1387 | let currentActive = currentProcess[requestURL]; |
michael@0 | 1388 | let wasActive = (currentActive['count'] > 0); |
michael@0 | 1389 | let wasAudioActive = (currentActive['audioCount'] > 0); |
michael@0 | 1390 | let wasVideoActive = (currentActive['videoCount'] > 0); |
michael@0 | 1391 | |
michael@0 | 1392 | switch (command.type) { |
michael@0 | 1393 | case 'starting': |
michael@0 | 1394 | currentActive['count']++; |
michael@0 | 1395 | currentActive['audioCount'] += (command.isAudio) ? 1 : 0; |
michael@0 | 1396 | currentActive['videoCount'] += (command.isVideo) ? 1 : 0; |
michael@0 | 1397 | break; |
michael@0 | 1398 | case 'shutdown': |
michael@0 | 1399 | currentActive['count']--; |
michael@0 | 1400 | currentActive['audioCount'] -= (command.isAudio) ? 1 : 0; |
michael@0 | 1401 | currentActive['videoCount'] -= (command.isVideo) ? 1 : 0; |
michael@0 | 1402 | break; |
michael@0 | 1403 | case 'content-shutdown': |
michael@0 | 1404 | currentActive['count'] = 0; |
michael@0 | 1405 | currentActive['audioCount'] = 0; |
michael@0 | 1406 | currentActive['videoCount'] = 0; |
michael@0 | 1407 | break; |
michael@0 | 1408 | } |
michael@0 | 1409 | |
michael@0 | 1410 | if (currentActive['count'] > 0) { |
michael@0 | 1411 | currentProcess[requestURL] = currentActive; |
michael@0 | 1412 | } else { |
michael@0 | 1413 | delete currentProcess[requestURL]; |
michael@0 | 1414 | } |
michael@0 | 1415 | |
michael@0 | 1416 | // We need to track changes if any active state is changed. |
michael@0 | 1417 | let isActive = (currentActive['count'] > 0); |
michael@0 | 1418 | let isAudioActive = (currentActive['audioCount'] > 0); |
michael@0 | 1419 | let isVideoActive = (currentActive['videoCount'] > 0); |
michael@0 | 1420 | if ((isActive != wasActive) || |
michael@0 | 1421 | (isAudioActive != wasAudioActive) || |
michael@0 | 1422 | (isVideoActive != wasVideoActive)) { |
michael@0 | 1423 | shell.sendChromeEvent({ |
michael@0 | 1424 | type: 'recording-status', |
michael@0 | 1425 | active: isActive, |
michael@0 | 1426 | requestURL: requestURL, |
michael@0 | 1427 | isApp: currentActive['isApp'], |
michael@0 | 1428 | isAudio: isAudioActive, |
michael@0 | 1429 | isVideo: isVideoActive |
michael@0 | 1430 | }); |
michael@0 | 1431 | } |
michael@0 | 1432 | }; |
michael@0 | 1433 | |
michael@0 | 1434 | switch (aData) { |
michael@0 | 1435 | case 'starting': |
michael@0 | 1436 | case 'shutdown': |
michael@0 | 1437 | // create page record if it is not existed yet. |
michael@0 | 1438 | let requestURL = props.get('requestURL'); |
michael@0 | 1439 | if (requestURL && |
michael@0 | 1440 | !gRecordingActiveProcesses[processId].hasOwnProperty(requestURL)) { |
michael@0 | 1441 | gRecordingActiveProcesses[processId][requestURL] = {isApp: props.get('isApp'), |
michael@0 | 1442 | count: 0, |
michael@0 | 1443 | audioCount: 0, |
michael@0 | 1444 | videoCount: 0}; |
michael@0 | 1445 | } |
michael@0 | 1446 | commandHandler(requestURL, { type: aData, |
michael@0 | 1447 | isAudio: props.get('isAudio'), |
michael@0 | 1448 | isVideo: props.get('isVideo')}); |
michael@0 | 1449 | break; |
michael@0 | 1450 | case 'content-shutdown': |
michael@0 | 1451 | // iterate through all the existing active processes |
michael@0 | 1452 | Object.keys(gRecordingActiveProcesses[processId]).forEach(function(requestURL) { |
michael@0 | 1453 | commandHandler(requestURL, { type: aData, |
michael@0 | 1454 | isAudio: true, |
michael@0 | 1455 | isVideo: true}); |
michael@0 | 1456 | }); |
michael@0 | 1457 | break; |
michael@0 | 1458 | } |
michael@0 | 1459 | |
michael@0 | 1460 | // clean up process record if no page record in it. |
michael@0 | 1461 | if (Object.keys(gRecordingActiveProcesses[processId]).length == 0) { |
michael@0 | 1462 | delete gRecordingActiveProcesses[processId]; |
michael@0 | 1463 | } |
michael@0 | 1464 | }; |
michael@0 | 1465 | Services.obs.addObserver(recordingHandler, 'recording-device-events', false); |
michael@0 | 1466 | Services.obs.addObserver(recordingHandler, 'recording-device-ipc-events', false); |
michael@0 | 1467 | |
michael@0 | 1468 | Services.obs.addObserver(function(aSubject, aTopic, aData) { |
michael@0 | 1469 | // send additional recording events if content process is being killed |
michael@0 | 1470 | let processId = aSubject.QueryInterface(Ci.nsIPropertyBag2).get('childID'); |
michael@0 | 1471 | if (gRecordingActiveProcesses.hasOwnProperty(processId)) { |
michael@0 | 1472 | Services.obs.notifyObservers(aSubject, 'recording-device-ipc-events', 'content-shutdown'); |
michael@0 | 1473 | } |
michael@0 | 1474 | }, 'ipc:content-shutdown', false); |
michael@0 | 1475 | })(); |
michael@0 | 1476 | |
michael@0 | 1477 | (function volumeStateTracker() { |
michael@0 | 1478 | Services.obs.addObserver(function(aSubject, aTopic, aData) { |
michael@0 | 1479 | shell.sendChromeEvent({ |
michael@0 | 1480 | type: 'volume-state-changed', |
michael@0 | 1481 | active: (aData == 'Shared') |
michael@0 | 1482 | }); |
michael@0 | 1483 | }, 'volume-state-changed', false); |
michael@0 | 1484 | })(); |
michael@0 | 1485 | |
michael@0 | 1486 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 1487 | // Devices don't have all the same partition size for /cache where we |
michael@0 | 1488 | // store the http cache. |
michael@0 | 1489 | (function setHTTPCacheSize() { |
michael@0 | 1490 | let path = Services.prefs.getCharPref("browser.cache.disk.parent_directory"); |
michael@0 | 1491 | let volumeService = Cc["@mozilla.org/telephony/volume-service;1"] |
michael@0 | 1492 | .getService(Ci.nsIVolumeService); |
michael@0 | 1493 | |
michael@0 | 1494 | let stats = volumeService.createOrGetVolumeByPath(path).getStats(); |
michael@0 | 1495 | |
michael@0 | 1496 | // We must set the size in KB, and keep a bit of free space. |
michael@0 | 1497 | let size = Math.floor(stats.totalBytes / 1024) - 1024; |
michael@0 | 1498 | Services.prefs.setIntPref("browser.cache.disk.capacity", size); |
michael@0 | 1499 | }) () |
michael@0 | 1500 | #endif |
michael@0 | 1501 | |
michael@0 | 1502 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 1503 | let SensorsListener = { |
michael@0 | 1504 | sensorsListenerDevices: ['crespo'], |
michael@0 | 1505 | device: libcutils.property_get("ro.product.device"), |
michael@0 | 1506 | |
michael@0 | 1507 | deviceNeedsWorkaround: function SensorsListener_deviceNeedsWorkaround() { |
michael@0 | 1508 | return (this.sensorsListenerDevices.indexOf(this.device) != -1); |
michael@0 | 1509 | }, |
michael@0 | 1510 | |
michael@0 | 1511 | handleEvent: function SensorsListener_handleEvent(evt) { |
michael@0 | 1512 | switch(evt.type) { |
michael@0 | 1513 | case 'devicemotion': |
michael@0 | 1514 | // Listener that does nothing, we need this to have the sensor being |
michael@0 | 1515 | // able to report correct values, as explained in bug 753245, comment 6 |
michael@0 | 1516 | // and in bug 871916 |
michael@0 | 1517 | break; |
michael@0 | 1518 | |
michael@0 | 1519 | default: |
michael@0 | 1520 | break; |
michael@0 | 1521 | } |
michael@0 | 1522 | }, |
michael@0 | 1523 | |
michael@0 | 1524 | observe: function SensorsListener_observe(subject, topic, data) { |
michael@0 | 1525 | // We remove the listener when the screen is off, otherwise sensor will |
michael@0 | 1526 | // continue to bother us with data and we won't be able to get the |
michael@0 | 1527 | // system into suspend state, thus draining battery. |
michael@0 | 1528 | if (data === 'on') { |
michael@0 | 1529 | window.addEventListener('devicemotion', this); |
michael@0 | 1530 | } else { |
michael@0 | 1531 | window.removeEventListener('devicemotion', this); |
michael@0 | 1532 | } |
michael@0 | 1533 | }, |
michael@0 | 1534 | |
michael@0 | 1535 | init: function SensorsListener_init() { |
michael@0 | 1536 | if (this.deviceNeedsWorkaround()) { |
michael@0 | 1537 | // On boot, enable the listener, screen will be on. |
michael@0 | 1538 | window.addEventListener('devicemotion', this); |
michael@0 | 1539 | |
michael@0 | 1540 | // Then listen for further screen state changes |
michael@0 | 1541 | Services.obs.addObserver(this, 'screen-state-changed', false); |
michael@0 | 1542 | } |
michael@0 | 1543 | } |
michael@0 | 1544 | } |
michael@0 | 1545 | |
michael@0 | 1546 | SensorsListener.init(); |
michael@0 | 1547 | #endif |
michael@0 | 1548 | |
michael@0 | 1549 | // Calling this observer will cause a shutdown an a profile reset. |
michael@0 | 1550 | // Use eg. : Services.obs.notifyObservers(null, 'b2g-reset-profile', null); |
michael@0 | 1551 | Services.obs.addObserver(function resetProfile(subject, topic, data) { |
michael@0 | 1552 | Services.obs.removeObserver(resetProfile, topic); |
michael@0 | 1553 | |
michael@0 | 1554 | // Listening for 'profile-before-change2' which is late in the shutdown |
michael@0 | 1555 | // sequence, but still has xpcom access. |
michael@0 | 1556 | Services.obs.addObserver(function clearProfile(subject, topic, data) { |
michael@0 | 1557 | Services.obs.removeObserver(clearProfile, topic); |
michael@0 | 1558 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 1559 | let json = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile); |
michael@0 | 1560 | json.initWithPath('/system/b2g/webapps/webapps.json'); |
michael@0 | 1561 | let toRemove = json.exists() |
michael@0 | 1562 | // This is a user build, just rm -r /data/local /data/b2g/mozilla |
michael@0 | 1563 | ? ['/data/local', '/data/b2g/mozilla'] |
michael@0 | 1564 | // This is an eng build. We clear the profile and a set of files |
michael@0 | 1565 | // under /data/local. |
michael@0 | 1566 | : ['/data/b2g/mozilla', |
michael@0 | 1567 | '/data/local/permissions.sqlite', |
michael@0 | 1568 | '/data/local/storage', |
michael@0 | 1569 | '/data/local/OfflineCache']; |
michael@0 | 1570 | |
michael@0 | 1571 | toRemove.forEach(function(dir) { |
michael@0 | 1572 | try { |
michael@0 | 1573 | let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile); |
michael@0 | 1574 | file.initWithPath(dir); |
michael@0 | 1575 | file.remove(true); |
michael@0 | 1576 | } catch(e) { dump(e); } |
michael@0 | 1577 | }); |
michael@0 | 1578 | #else |
michael@0 | 1579 | // Desktop builds. |
michael@0 | 1580 | let profile = Services.dirsvc.get('ProfD', Ci.nsIFile); |
michael@0 | 1581 | |
michael@0 | 1582 | // We don't want to remove everything from the profile, since this |
michael@0 | 1583 | // would prevent us from starting up. |
michael@0 | 1584 | let whitelist = ['defaults', 'extensions', 'settings.json', |
michael@0 | 1585 | 'user.js', 'webapps']; |
michael@0 | 1586 | let enumerator = profile.directoryEntries; |
michael@0 | 1587 | while (enumerator.hasMoreElements()) { |
michael@0 | 1588 | let file = enumerator.getNext().QueryInterface(Ci.nsIFile); |
michael@0 | 1589 | if (whitelist.indexOf(file.leafName) == -1) { |
michael@0 | 1590 | file.remove(true); |
michael@0 | 1591 | } |
michael@0 | 1592 | } |
michael@0 | 1593 | #endif |
michael@0 | 1594 | }, |
michael@0 | 1595 | 'profile-before-change2', false); |
michael@0 | 1596 | |
michael@0 | 1597 | let appStartup = Cc['@mozilla.org/toolkit/app-startup;1'] |
michael@0 | 1598 | .getService(Ci.nsIAppStartup); |
michael@0 | 1599 | appStartup.quit(Ci.nsIAppStartup.eForceQuit); |
michael@0 | 1600 | }, 'b2g-reset-profile', false); |
michael@0 | 1601 | |
michael@0 | 1602 | /** |
michael@0 | 1603 | * CID of our implementation of nsIDownloadManagerUI. |
michael@0 | 1604 | */ |
michael@0 | 1605 | const kTransferCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}"); |
michael@0 | 1606 | |
michael@0 | 1607 | /** |
michael@0 | 1608 | * Contract ID of the service implementing nsITransfer. |
michael@0 | 1609 | */ |
michael@0 | 1610 | const kTransferContractId = "@mozilla.org/transfer;1"; |
michael@0 | 1611 | |
michael@0 | 1612 | // Override Toolkit's nsITransfer implementation with the one from the |
michael@0 | 1613 | // JavaScript API for downloads. This will eventually be removed when |
michael@0 | 1614 | // nsIDownloadManager will not be available anymore (bug 851471). The |
michael@0 | 1615 | // old code in this module will be removed in bug 899110. |
michael@0 | 1616 | Components.manager.QueryInterface(Ci.nsIComponentRegistrar) |
michael@0 | 1617 | .registerFactory(kTransferCid, "", |
michael@0 | 1618 | kTransferContractId, null); |