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