b2g/chrome/content/shell.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:fe7caf7855b2
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);

mercurial