Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 27 Feb 2014 15:39:48 +0100
changeset 171324 8c7cf76ab17da5da3928c3a7d134141c617fd4ea
parent 171291 9b0bf77d6f244185cff30fcfe2a3ba2718df9cf9 (current diff)
parent 171323 4cfb6c61b13789061152cd9b04950b0430ddf81d (diff)
child 171325 2c0ffb315be38cfbe05fb697f0877c07c3be24c9
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
milestone30.0a1
Merge mozilla-central to mozilla-inbound
toolkit/mozapps/extensions/service/VersionCheck.php
--- a/b2g/chrome/content/devtools.js
+++ b/b2g/chrome/content/devtools.js
@@ -1,37 +1,44 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 'use strict';
 
-const WIDGET_PANEL_LOG_PREFIX = 'WidgetPanel';
+const DEVELOPER_HUD_LOG_PREFIX = 'DeveloperHUD';
+
+XPCOMUtils.defineLazyGetter(this, 'devtools', function() {
+  const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+  return devtools;
+});
 
 XPCOMUtils.defineLazyGetter(this, 'DebuggerClient', function() {
   return Cu.import('resource://gre/modules/devtools/dbg-client.jsm', {}).DebuggerClient;
 });
 
 XPCOMUtils.defineLazyGetter(this, 'WebConsoleUtils', function() {
-  let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
   return devtools.require("devtools/toolkit/webconsole/utils").Utils;
 });
 
 XPCOMUtils.defineLazyGetter(this, 'EventLoopLagFront', function() {
-  const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
   return devtools.require("devtools/server/actors/eventlooplag").EventLoopLagFront;
 });
 
+XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() {
+  return devtools.require("devtools/server/actors/memory").MemoryFront;
+});
+
 
 /**
- * The Widget Panel is an on-device developer tool that displays widgets,
+ * The Developer HUD is an on-device developer tool that displays widgets,
  * showing visual debug information about apps. Each widget corresponds to a
  * metric as tracked by a metric watcher (e.g. consoleWatcher).
  */
-let devtoolsWidgetPanel = {
+let developerHUD = {
 
   _apps: new Map(),
   _urls: new Map(),
   _client: null,
   _webappsActor: null,
   _watchers: [],
 
   /**
@@ -102,17 +109,17 @@ let devtoolsWidgetPanel = {
   /**
    * This method will ask all registered watchers to track and update metrics
    * on an app.
    */
   trackApp: function dwp_trackApp(manifestURL) {
     if (this._apps.has(manifestURL))
       return;
 
-    // FIXME(Bug 962577) Factor getAppActor and watchApps out of webappsActor.
+    // FIXME(Bug 962577) Factor getAppActor out of webappsActor.
     this._client.request({
       to: this._webappsActor,
       type: 'getAppActor',
       manifestURL: manifestURL
     }, (res) => {
       if (res.error) {
         return;
       }
@@ -174,17 +181,17 @@ let devtoolsWidgetPanel = {
           return;
         this.untrackApp(manifestURL);
         this._urls.delete(mm);
         break;
     }
   },
 
   log: function dwp_log(message) {
-    dump(WIDGET_PANEL_LOG_PREFIX + ': ' + message + '\n');
+    dump(DEVELOPER_HUD_LOG_PREFIX + ': ' + message + '\n');
   }
 
 };
 
 
 /**
  * An App object represents all there is to know about a Firefox OS app that is
  * being tracked, e.g. its manifest information, current values of watched
@@ -203,17 +210,17 @@ App.prototype = {
     let metrics = this.metrics;
 
     if (metrics && metrics.size > 0) {
       for (let name of metrics.keys()) {
         data.metrics.push({name: name, value: metrics.get(name)});
       }
     }
 
-    shell.sendCustomEvent('widget-panel-update', data);
+    shell.sendCustomEvent('developer-hud-update', data);
     // FIXME(after bug 963239 lands) return event.isDefaultPrevented();
     return false;
   }
 
 };
 
 
 /**
@@ -233,19 +240,19 @@ let consoleWatcher = {
   init: function cw_init(client) {
     this._client = client;
     this.consoleListener = this.consoleListener.bind(this);
 
     let watching = this._watching;
 
     for (let key in watching) {
       let metric = key;
-      SettingsListener.observe('devtools.hud.' + metric, false, value => {
+      SettingsListener.observe('hud.' + metric, false, watch => {
         // Watch or unwatch the metric.
-        if (watching[metric] = value) {
+        if (watching[metric] = watch) {
           return;
         }
 
         // If unwatched, remove any existing widgets for that metric.
         for (let app of this._apps.values()) {
           app.metrics.set(metric, 0);
           app.display();
         }
@@ -351,46 +358,44 @@ let consoleWatcher = {
         if (sourceURL) {
           output += ' ' + this.formatSourceURL(packet);
         }
         break;
     }
 
     if (!app.display()) {
       // If the information was not displayed, log it.
-      devtoolsWidgetPanel.log(output);
+      developerHUD.log(output);
     }
   },
 
   formatSourceURL: function cw_formatSourceURL(packet) {
     // Abbreviate source URL
     let source = WebConsoleUtils.abbreviateSourceURL(packet.sourceURL);
 
     // Add function name and line number
     let {functionName, sourceLine} = packet;
     source = 'in ' + (functionName || '<anonymousFunction>') +
       ', ' + source + ':' + sourceLine;
 
     return source;
   }
 };
-
-devtoolsWidgetPanel.registerWatcher(consoleWatcher);
+developerHUD.registerWatcher(consoleWatcher);
 
 
-let jankWatcher = {
+let eventLoopLagWatcher = {
   _client: null,
   _fronts: new Map(),
   _active: false,
 
   init: function(client) {
     this._client = client;
 
-    SettingsListener.observe('devtools.hud.jank', false,
-      this.settingsListener.bind(this));
+    SettingsListener.observe('hud.jank', false, this.settingsListener.bind(this));
   },
 
   settingsListener: function(value) {
     if (this._active == value) {
       return;
     }
     this._active = value;
 
@@ -412,27 +417,130 @@ let jankWatcher = {
 
     let front = new EventLoopLagFront(this._client, app.actor);
     this._fronts.set(app, front);
 
     front.on('event-loop-lag', time => {
       app.metrics.set('jank', time);
 
       if (!app.display()) {
-        devtoolsWidgetPanel.log('jank: ' + time + 'ms');
+        developerHUD.log('jank: ' + time + 'ms');
       }
     });
 
     if (this._active) {
       front.start();
     }
   },
 
   untrackApp: function(app) {
     let fronts = this._fronts;
     if (fronts.has(app)) {
       fronts.get(app).destroy();
       fronts.delete(app);
     }
   }
 };
+developerHUD.registerWatcher(eventLoopLagWatcher);
 
-devtoolsWidgetPanel.registerWatcher(jankWatcher);
+
+/**
+ * The Memory Watcher uses devtools actors to track memory usage.
+ */
+let memoryWatcher = {
+
+  _client: null,
+  _fronts: new Map(),
+  _timers: new Map(),
+  _watching: {
+    jsobjects: false,
+    jsstrings: false,
+    jsother: false,
+    dom: false,
+    style: false,
+    other: false
+  },
+  _active: false,
+
+  init: function mw_init(client) {
+    this._client = client;
+    let watching = this._watching;
+
+    for (let key in watching) {
+      let category = key;
+      SettingsListener.observe('hud.' + category, false, watch => {
+        watching[category] = watch;
+      });
+    }
+
+    SettingsListener.observe('hud.appmemory', false, enabled => {
+      if (this._active = enabled) {
+        for (let app of this._fronts.keys()) {
+          this.measure(app);
+        }
+      } else {
+        for (let timer of this._timers.values()) {
+          clearTimeout(this._timers.get(app));
+        }
+      }
+    });
+  },
+
+  measure: function mw_measure(app) {
+
+    // TODO Also track USS (bug #976024).
+
+    let watch = this._watching;
+    let front = this._fronts.get(app);
+
+    front.measure().then((data) => {
+
+      let total = 0;
+      if (watch.jsobjects) {
+        total += parseInt(data.jsObjectsSize);
+      }
+      if (watch.jsstrings) {
+        total += parseInt(data.jsStringsSize);
+      }
+      if (watch.jsother) {
+        total += parseInt(data.jsOtherSize);
+      }
+      if (watch.dom) {
+        total += parseInt(data.domSize);
+      }
+      if (watch.style) {
+        total += parseInt(data.styleSize);
+      }
+      if (watch.other) {
+        total += parseInt(data.otherSize);
+      }
+      // TODO Also count images size (bug #976007).
+
+      app.metrics.set('memory', total);
+      app.display();
+      let duration = parseInt(data.jsMilliseconds) + parseInt(data.nonJSMilliseconds);
+      let timer = setTimeout(() => this.measure(app), 100 * duration);
+      this._timers.set(app, timer);
+    }, (err) => {
+      console.error(err);
+    });
+  },
+
+  trackApp: function mw_trackApp(app) {
+    app.metrics.set('uss', 0);
+    app.metrics.set('memory', 0);
+    this._fronts.set(app, MemoryFront(this._client, app.actor));
+    if (this._active) {
+      this.measure(app);
+    }
+  },
+
+  untrackApp: function mw_untrackApp(app) {
+    let front = this._fronts.get(app);
+    if (front) {
+      front.destroy();
+      clearTimeout(this._timers.get(app));
+      this._fronts.delete(app);
+      this._timers.delete(app);
+    }
+  }
+};
+developerHUD.registerWatcher(memoryWatcher);
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -214,28 +214,28 @@ Components.utils.import('resource://gre/
     'deviceinfo.firmware_revision': firmware_revision,
     'deviceinfo.product_model': product_model
   }
   window.navigator.mozSettings.createLock().set(setting);
 })();
 
 // =================== DevTools ====================
 
-let devtoolsWidgetPanel;
+let developerHUD;
 SettingsListener.observe('devtools.overlay', false, (value) => {
   if (value) {
-    if (!devtoolsWidgetPanel) {
+    if (!developerHUD) {
       let scope = {};
       Services.scriptloader.loadSubScript('chrome://b2g/content/devtools.js', scope);
-      devtoolsWidgetPanel = scope.devtoolsWidgetPanel;
+      developerHUD = scope.developerHUD;
     }
-    devtoolsWidgetPanel.init();
+    developerHUD.init();
   } else {
-    if (devtoolsWidgetPanel) {
-      devtoolsWidgetPanel.uninit();
+    if (developerHUD) {
+      developerHUD.uninit();
     }
   }
 });
 
 SettingsListener.observe('devtools.eventlooplag.threshold', 100, function(value) {
   Services.prefs.setIntPref('devtools.eventlooplag.threshold', value);
 });
 
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="22d48b62df7901ad45044f66e15e7d8943884a06"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a9fdec737cdfd41206a332970fea833ec4ca13a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="022eadd5917615ff00c47eaaafa792b45e9c8a28"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="52ca41d9fa6ef88e65d9da52e375716c68d48646"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="97a5b461686757dbb8ecab2aac5903e41d2e1afe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="22d48b62df7901ad45044f66e15e7d8943884a06"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3a9fdec737cdfd41206a332970fea833ec4ca13a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="52ca41d9fa6ef88e65d9da52e375716c68d48646"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="905bfa3548eb75cf1792d0d8412b92113bbd4318"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="c3d7efc45414f1b44cd9c479bb2758c91c4707c0"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="22d48b62df7901ad45044f66e15e7d8943884a06"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a9fdec737cdfd41206a332970fea833ec4ca13a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="022eadd5917615ff00c47eaaafa792b45e9c8a28"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="52ca41d9fa6ef88e65d9da52e375716c68d48646"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "remote": "", 
         "branch": "", 
         "revision": ""
     }, 
-    "revision": "2761bef239957844e6126ef252b82b5f63b304e7", 
+    "revision": "cba6294f97cb705143dc4581410518901aa4aa59", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="22d48b62df7901ad45044f66e15e7d8943884a06"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a9fdec737cdfd41206a332970fea833ec4ca13a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="52ca41d9fa6ef88e65d9da52e375716c68d48646"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="22d48b62df7901ad45044f66e15e7d8943884a06"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a9fdec737cdfd41206a332970fea833ec4ca13a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="22d48b62df7901ad45044f66e15e7d8943884a06"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a9fdec737cdfd41206a332970fea833ec4ca13a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="52ca41d9fa6ef88e65d9da52e375716c68d48646"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="22d48b62df7901ad45044f66e15e7d8943884a06"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a9fdec737cdfd41206a332970fea833ec4ca13a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="52ca41d9fa6ef88e65d9da52e375716c68d48646"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="97a5b461686757dbb8ecab2aac5903e41d2e1afe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="22d48b62df7901ad45044f66e15e7d8943884a06"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3a9fdec737cdfd41206a332970fea833ec4ca13a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="52ca41d9fa6ef88e65d9da52e375716c68d48646"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="905bfa3548eb75cf1792d0d8412b92113bbd4318"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="c3d7efc45414f1b44cd9c479bb2758c91c4707c0"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="22d48b62df7901ad45044f66e15e7d8943884a06"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a9fdec737cdfd41206a332970fea833ec4ca13a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="52ca41d9fa6ef88e65d9da52e375716c68d48646"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/base/content/test/general/browser_get_user_media.js
+++ b/browser/base/content/test/general/browser_get_user_media.js
@@ -72,23 +72,30 @@ function promiseMessage(aMessage, aActio
   });
 
   if (aAction)
     aAction();
 
   return deferred.promise;
 }
 
-function promisePopupNotification(aName) {
+
+function promisePopupNotification(aName, aShown) {
   let deferred = Promise.defer();
 
-  waitForCondition(() => PopupNotifications.getNotification(aName),
+  // If aShown is true, the notification is expected to be opened by
+  // default and we wait for the panel to be populated; for dismissed
+  // notifications, we are happy as soon as we find the icon.
+  waitForCondition(() => PopupNotifications.getNotification(aName) &&
+                         (!aShown || PopupNotifications.panel.firstChild),
                    () => {
     ok(!!PopupNotifications.getNotification(aName),
        aName + " notification appeared");
+    if (aShown)
+      ok(PopupNotifications.panel.firstChild, "notification panel populated");
     deferred.resolve();
   }, "timeout waiting for popup notification " + aName);
 
   return deferred.promise;
 }
 
 function promiseNoPopupNotification(aName) {
   let deferred = Promise.defer();
@@ -200,17 +207,17 @@ let gTests = [
 {
   desc: "getUserMedia audio+video",
   run: function checkAudioVideo() {
     yield promiseNotification("getUserMedia:request", () => {
       info("requesting devices");
       content.wrappedJSObject.requestDevice(true, true);
     });
 
-    yield promisePopupNotification("webRTC-shareDevices");
+    yield promisePopupNotification("webRTC-shareDevices", true);
     checkDeviceSelectors(true, true);
 
     yield promiseMessage("ok", () => {
       PopupNotifications.panel.firstChild.button.click();
     });
     expectNotification("getUserMedia:response:allow");
     expectNotification("recording-device-events");
     is(getMediaCaptureState(), "CameraAndMicrophone",
@@ -224,17 +231,17 @@ let gTests = [
 {
   desc: "getUserMedia audio only",
   run: function checkAudioOnly() {
     yield promiseNotification("getUserMedia:request", () => {
       info("requesting devices");
       content.wrappedJSObject.requestDevice(true);
     });
 
-    yield promisePopupNotification("webRTC-shareDevices");
+    yield promisePopupNotification("webRTC-shareDevices", true);
     checkDeviceSelectors(true);
 
     yield promiseMessage("ok", () => {
       PopupNotifications.panel.firstChild.button.click();
     });
     expectNotification("getUserMedia:response:allow");
     expectNotification("recording-device-events");
     is(getMediaCaptureState(), "Microphone", "expected microphone to be shared");
@@ -247,17 +254,17 @@ let gTests = [
 {
   desc: "getUserMedia video only",
   run: function checkVideoOnly() {
     yield promiseNotification("getUserMedia:request", () => {
       info("requesting devices");
       content.wrappedJSObject.requestDevice(false, true);
     });
 
-    yield promisePopupNotification("webRTC-shareDevices");
+    yield promisePopupNotification("webRTC-shareDevices", true);
     checkDeviceSelectors(false, true);
 
     yield promiseMessage("ok", () => {
       PopupNotifications.panel.firstChild.button.click();
     });
     expectNotification("getUserMedia:response:allow");
     expectNotification("recording-device-events");
     is(getMediaCaptureState(), "Camera", "expected camera to be shared");
@@ -270,17 +277,17 @@ let gTests = [
 {
   desc: "getUserMedia audio+video, user disables video",
   run: function checkDisableVideo() {
     yield promiseNotification("getUserMedia:request", () => {
       info("requesting devices");
       content.wrappedJSObject.requestDevice(true, true);
     });
 
-    yield promisePopupNotification("webRTC-shareDevices");
+    yield promisePopupNotification("webRTC-shareDevices", true);
     checkDeviceSelectors(true, true);
 
     // disable the camera
     document.getElementById("webRTC-selectCamera-menulist").value = -1;
 
     yield promiseMessage("ok", () => {
       PopupNotifications.panel.firstChild.button.click();
     });
@@ -301,17 +308,17 @@ let gTests = [
 {
   desc: "getUserMedia audio+video, user disables audio",
   run: function checkDisableAudio() {
     yield promiseNotification("getUserMedia:request", () => {
       info("requesting devices");
       content.wrappedJSObject.requestDevice(true, true);
     });
 
-    yield promisePopupNotification("webRTC-shareDevices");
+    yield promisePopupNotification("webRTC-shareDevices", true);
     checkDeviceSelectors(true, true);
 
     // disable the microphone
     document.getElementById("webRTC-selectMicrophone-menulist").value = -1;
 
     yield promiseMessage("ok", () => {
       PopupNotifications.panel.firstChild.button.click();
     });
@@ -332,17 +339,17 @@ let gTests = [
 {
   desc: "getUserMedia audio+video, user disables both audio and video",
   run: function checkDisableAudioVideo() {
     yield promiseNotification("getUserMedia:request", () => {
       info("requesting devices");
       content.wrappedJSObject.requestDevice(true, true);
     });
 
-    yield promisePopupNotification("webRTC-shareDevices");
+    yield promisePopupNotification("webRTC-shareDevices", true);
     checkDeviceSelectors(true, true);
 
     // disable the camera and microphone
     document.getElementById("webRTC-selectCamera-menulist").value = -1;
     document.getElementById("webRTC-selectMicrophone-menulist").value = -1;
 
     yield promiseMessage("error: PERMISSION_DENIED", () => {
       PopupNotifications.panel.firstChild.button.click();
@@ -361,17 +368,17 @@ let gTests = [
 {
   desc: "getUserMedia audio+video, user clicks \"Don't Share\"",
   run: function checkDontShare() {
     yield promiseNotification("getUserMedia:request", () => {
       info("requesting devices");
       content.wrappedJSObject.requestDevice(true, true);
     });
 
-    yield promisePopupNotification("webRTC-shareDevices");
+    yield promisePopupNotification("webRTC-shareDevices", true);
     checkDeviceSelectors(true, true);
 
     yield promiseMessage("error: PERMISSION_DENIED", () => {
       activateSecondaryAction(kActionDeny);
     });
 
     expectNotification("getUserMedia:response:deny");
     expectNotification("recording-window-ended");
@@ -382,17 +389,17 @@ let gTests = [
 {
   desc: "getUserMedia audio+video: stop sharing",
   run: function checkStopSharing() {
     yield promiseNotification("getUserMedia:request", () => {
       info("requesting devices");
       content.wrappedJSObject.requestDevice(true, true);
     });
 
-    yield promisePopupNotification("webRTC-shareDevices");
+    yield promisePopupNotification("webRTC-shareDevices", true);
     checkDeviceSelectors(true, true);
 
     yield promiseMessage("ok", () => {
       PopupNotifications.panel.firstChild.button.click();
     });
     expectNotification("getUserMedia:response:allow");
     expectNotification("recording-device-events");
     is(getMediaCaptureState(), "CameraAndMicrophone",
@@ -419,25 +426,25 @@ let gTests = [
     // the stream is already closed, but this will do some cleanup anyway
     yield closeStream(true);
   }
 },
 
 {
   desc: "getUserMedia prompt: Always/Never Share",
   run: function checkRememberCheckbox() {
+    let elt = id => document.getElementById(id);
+
     function checkPerm(aRequestAudio, aRequestVideo, aAllowAudio, aAllowVideo,
                        aExpectedAudioPerm, aExpectedVideoPerm, aNever) {
       yield promiseNotification("getUserMedia:request", () => {
         content.wrappedJSObject.requestDevice(aRequestAudio, aRequestVideo);
       });
 
-      yield promisePopupNotification("webRTC-shareDevices");
-
-      let elt = id => document.getElementById(id);
+      yield promisePopupNotification("webRTC-shareDevices", true);
 
       let noAudio = aAllowAudio === undefined;
       is(elt("webRTC-selectMicrophone").hidden, noAudio,
          "microphone selector expected to be " + (noAudio ? "hidden" : "visible"));
       if (!noAudio)
         elt("webRTC-selectMicrophone-menulist").value = (aAllowAudio || aNever) ? 0 : -1;
 
       let noVideo = aAllowVideo === undefined;
@@ -514,16 +521,20 @@ let gTests = [
 
     // 2 cases where the user allows half of what's requested.
     info("audio+video, user denies video, grants audio, " +
          "expect video perm set to deny, audio perm set to allow.");
     yield checkPerm(true, true, true, false, true, false);
     info("audio+video, user denies audio, grants video, " +
          "expect video perm set to allow, audio perm set to deny.");
     yield checkPerm(true, true, false, true, false, true);
+
+    // reset the menuitems to have no impact on the following tests.
+    elt("webRTC-selectMicrophone-menulist").value = 0;
+    elt("webRTC-selectCamera-menulist").value = 0;
   }
 },
 
 {
   desc: "getUserMedia without prompt: use persistent permissions",
   run: function checkUsePersistentPermissions() {
     function usePerm(aAllowAudio, aAllowVideo, aRequestAudio, aRequestVideo,
                      aExpectStream) {
@@ -540,17 +551,17 @@ let gTests = [
 
       let gum = function() {
         content.wrappedJSObject.requestDevice(aRequestAudio, aRequestVideo);
       };
 
       if (aExpectStream === undefined) {
         // Check that we get a prompt.
         yield promiseNotification("getUserMedia:request", gum);
-        yield promisePopupNotification("webRTC-shareDevices");
+        yield promisePopupNotification("webRTC-shareDevices", true);
 
         // Deny the request to cleanup...
         yield promiseMessage("error: PERMISSION_DENIED", () => {
           activateSecondaryAction(kActionDeny);
         });
         expectNotification("getUserMedia:response:deny");
         expectNotification("recording-window-ended");
       }
--- a/browser/components/preferences/sync.js
+++ b/browser/components/preferences/sync.js
@@ -80,17 +80,17 @@ let gSyncPane = {
     window.addEventListener("unload", onUnload, false);
 
     xps.ensureLoaded();
   },
 
   _init: function () {
     let topics = ["weave:service:login:error",
                   "weave:service:login:finish",
-                  "weave:service:start-over",
+                  "weave:service:start-over:finish",
                   "weave:service:setup-complete",
                   "weave:service:logout:finish",
                   FxAccountsCommon.ONVERIFIED_NOTIFICATION];
 
     // Add the observers now and remove them on unload
     //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
     //        of `this`. Fix in a followup. (bug 583347)
     topics.forEach(function (topic) {
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -3135,22 +3135,18 @@ let SessionStoreInternal = {
    * to restore the previous session (publicly exposed as restoreLastSession).
    *
    * @param state
    *        The state, presumably from nsISessionStartup.state
    * @returns [defaultState, state]
    */
   _prepDataForDeferredRestore: function ssi_prepDataForDeferredRestore(state) {
     // Make sure that we don't modify the global state as provided by
-    // nsSessionStartup.state. Converting the object to a JSON string and
-    // parsing it again is the easiest way to do that, although not the most
-    // efficient one. Deferred sessions that don't have automatic session
-    // restore enabled tend to be a lot smaller though so that this shouldn't
-    // be a big perf hit.
-    state = JSON.parse(JSON.stringify(state));
+    // nsSessionStartup.state.
+    state = Cu.cloneInto(state, {});
 
     let defaultState = { windows: [], selectedWindow: 1 };
 
     state.selectedWindow = state.selectedWindow || 1;
 
     // Look at each window, remove pinned tabs, adjust selectedindex,
     // remove window if necessary.
     for (let wIndex = 0; wIndex < state.windows.length;) {
--- a/browser/components/sessionstore/src/TabState.jsm
+++ b/browser/components/sessionstore/src/TabState.jsm
@@ -15,16 +15,18 @@ Cu.import("resource://gre/modules/Task.j
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
   "resource:///modules/sessionstore/PrivacyFilter.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
   "resource:///modules/sessionstore/TabStateCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
   "resource:///modules/sessionstore/TabAttributes.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Utils",
+  "resource:///modules/sessionstore/Utils.jsm");
 
 /**
  * Module that contains tab state collection methods.
  */
 this.TabState = Object.freeze({
   setSyncHandler: function (browser, handler) {
     TabStateInternal.setSyncHandler(browser, handler);
   },
@@ -146,17 +148,17 @@ let TabStateInternal = {
     if (!browser || !browser.currentURI) {
       // can happen when calling this function right after .addTab()
       return tabData;
     }
     if (browser.__SS_data) {
       // Use the data to be restored when the tab hasn't been
       // completely loaded. We clone the data, since we're updating it
       // here and the caller may update it further.
-      tabData = JSON.parse(JSON.stringify(browser.__SS_data));
+      tabData = Utils.shallowCopy(browser.__SS_data);
       if (tab.pinned)
         tabData.pinned = true;
       else
         delete tabData.pinned;
       tabData.hidden = tab.hidden;
 
       // If __SS_extdata is set then we'll use that since it might be newer.
       if (tab.__SS_extdata)
--- a/browser/components/sessionstore/src/Utils.jsm
+++ b/browser/components/sessionstore/src/Utils.jsm
@@ -36,10 +36,20 @@ this.Utils = Object.freeze({
       return false;
 
     if (host == domain)
       return true;
 
     let prevChar = host[index - 1];
     return (index == (host.length - domain.length)) &&
            (prevChar == "." || prevChar == "/");
+  },
+
+  shallowCopy: function (obj) {
+    let retval = {};
+
+    for (let key of Object.keys(obj)) {
+      retval[key] = obj[key];
+    }
+
+    return retval;
   }
 });
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -2124,26 +2124,16 @@ Breakpoints.prototype = {
 
     // Update the breakpoints pane if required.
     if (!aOptions.noPaneUpdate) {
       DebuggerView.Sources.removeBreakpoint(aLocation);
     }
   },
 
   /**
-   * Gets all Promises for the BreakpointActor client objects that are
-   * either enabled (added to the server) or disabled (removed from the server,
-   * but for which some details are preserved).
-   */
-  get _addedOrDisabled() {
-    for (let [, value] of this._added) yield value;
-    for (let [, value] of this._disabled) yield value;
-  },
-
-  /**
    * Get a Promise for the BreakpointActor client object which is already added
    * or currently being added at the given location.
    *
    * @param object aLocation
    *        @see DebuggerController.Breakpoints.addBreakpoint
    * @return object | null
    *         A promise that is resolved after the breakpoint is added, or
    *         null if no breakpoint was found.
@@ -2176,16 +2166,32 @@ Breakpoints.prototype = {
    *         The identifier string.
    */
   getIdentifier: function(aLocation) {
     return aLocation.url + ":" + aLocation.line;
   }
 };
 
 /**
+ * Gets all Promises for the BreakpointActor client objects that are
+ * either enabled (added to the server) or disabled (removed from the server,
+ * but for which some details are preserved).
+ */
+Object.defineProperty(Breakpoints.prototype, "_addedOrDisabled", {
+  get: function* () {
+    for (let [, value] of this._added) {
+      yield value;
+    }
+    for (let [, value] of this._disabled) {
+      yield value;
+    }
+  }
+});
+
+/**
  * Localization convenience methods.
  */
 let L10N = new ViewHelpers.L10N(DBG_STRINGS_URI);
 
 /**
  * Shortcuts for accessing various debugger preferences.
  */
 let Prefs = new ViewHelpers.Prefs("devtools", {
--- a/browser/devtools/sourceeditor/codemirror/README
+++ b/browser/devtools/sourceeditor/codemirror/README
@@ -17,21 +17,23 @@ To confirm the functionality run mochite
  * styleditor
  * netmonitor
 
 The sourceeditor component contains imported CodeMirror tests [3].
 
  * Some tests were commented out because we don't use that functionality
    within Firefox (for example Ruby editing mode). Be careful when updating
    files test/codemirror.html and test/vimemacs.html; they were modified to
-   co-exist with Mozilla's testing infrastructure.
+   co-exist with Mozilla's testing infrastructure. Basically, vimemacs.html
+   is a copy of codemirror.html but only with VIM and Emacs mode tests
+   enabled.
  * In cm_comment_test.js comment out fallbackToBlock and fallbackToLine
    tests.
  * The search addon (search.js) was slightly modified to make search
-   UI localizable.
+   UI localizable (see patch below).
 
 Other than that, we don't have any Mozilla-specific patches applied to
 CodeMirror itself.
 
 # Addons
 
 To install a new CodeMirror addon add it to the codemirror directory,
 jar.mn [4] file and editor.js [5]. Also, add it to the License section
@@ -71,15 +73,47 @@ in the LICENSE file:
  * test/cm_driver.js
  * test/cm_mode_javascript_test.js
  * test/cm_mode_test.css
  * test/cm_mode_test.js
  * test/cm_vim_test.js
  * test/cm_emacs_test.js
  * test/cm_test.js
 
+# Localization patches
+
+diff --git a/browser/devtools/sourceeditor/codemirror/search/search.js b/browser/devtools/sourceeditor/codemirror/sea
+index 049f72f..df4d95e 100644
+--- a/browser/devtools/sourceeditor/codemirror/search/search.js
++++ b/browser/devtools/sourceeditor/codemirror/search/search.js
+@@ -58,9 +58,22 @@
+     var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
+     return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
+   }
+-  var queryDialog =
+-    'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)<
++  var queryDialog;
+   function doSearch(cm, rev) {
++    if (!queryDialog) {
++      let doc = cm.getWrapperElement().ownerDocument;
++      let inp = doc.createElement("input");
++      let txt = doc.createTextNode(cm.l10n("findCmd.promptMessage"));
++
++      inp.type = "text";
++      inp.style.width = "10em";
++      inp.style.MozMarginStart = "1em";
++
++      queryDialog = doc.createElement("div");
++      queryDialog.appendChild(txt);
++      queryDialog.appendChild(inp);
++    }
++
+     var state = getSearchState(cm);
+     if (state.query) return findNext(cm, rev);
+     dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
+
 # Footnotes
 
 [1] http://codemirror.net
 [2] browser/devtools/sourceeditor/codemirror
 [3] browser/devtools/sourceeditor/test/browser_codemirror.js
 [4] browser/devtools/jar.mn
 [5] browser/devtools/sourceeditor/editor.js
--- a/browser/devtools/sourceeditor/codemirror/search/search.js
+++ b/browser/devtools/sourceeditor/codemirror/search/search.js
@@ -53,19 +53,32 @@
   function confirmDialog(cm, text, shortText, fs) {
     if (cm.openConfirm) cm.openConfirm(text, fs);
     else if (confirm(shortText)) fs[0]();
   }
   function parseQuery(query) {
     var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
     return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
   }
-  var queryDialog =
-    'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
+  var queryDialog;
   function doSearch(cm, rev) {
+    if (!queryDialog) {
+      let doc = cm.getWrapperElement().ownerDocument;
+      let inp = doc.createElement("input");
+      let txt = doc.createTextNode(cm.l10n("findCmd.promptMessage"));
+
+      inp.type = "text";
+      inp.style.width = "10em";
+      inp.style.MozMarginStart = "1em";
+
+      queryDialog = doc.createElement("div");
+      queryDialog.appendChild(txt);
+      queryDialog.appendChild(inp);
+    }
+
     var state = getSearchState(cm);
     if (state.query) return findNext(cm, rev);
     dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
       cm.operation(function() {
         if (!query || state.query) return;
         state.query = parseQuery(query);
         cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
         state.overlay = searchOverlay(state.query);
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -770,16 +770,17 @@ Editor.prototype = {
   },
 
   /**
    * Sets font size for the editor area.
    */
   setFontSize: function (size) {
     let cm = editors.get(this);
     cm.getWrapperElement().style.fontSize = parseInt(size, 10) + "px";
+    cm.refresh();
   },
 
   /**
    * Extends an instance of the Editor object with additional
    * functions. Each function will be called with context as
    * the first argument. Context is a {ed, cm} object where
    * 'ed' is an instance of the Editor object and 'cm' is an
    * instance of the CodeMirror object. Example:
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -21,30 +21,33 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
   "resource://gre/modules/UITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
   "resource:///modules/BrowserUITelemetry.jsm");
 
 
 const UITOUR_PERMISSION   = "uitour";
 const PREF_PERM_BRANCH    = "browser.uitour.";
+const PREF_SEENPAGEIDS    = "browser.uitour.seenPageIDs";
 const MAX_BUTTONS         = 4;
 
 const BUCKET_NAME         = "UITour";
 const BUCKET_TIMESTEPS    = [
   1 * 60 * 1000, // Until 1 minute after tab is closed/inactive.
   3 * 60 * 1000, // Until 3 minutes after tab is closed/inactive.
   10 * 60 * 1000, // Until 10 minutes after tab is closed/inactive.
   60 * 60 * 1000, // Until 1 hour after tab is closed/inactive.
 ];
 
+// Time after which seen Page IDs expire.
+const SEENPAGEID_EXPIRY  = 2 * 7 * 24 * 60 * 60 * 1000; // 2 weeks.
 
 
 this.UITour = {
-  seenPageIDs: new Set(),
+  seenPageIDs: null,
   pageIDSourceTabs: new WeakMap(),
   pageIDSourceWindows: new WeakMap(),
   /* Map from browser windows to a set of tabs in which a tour is open */
   originTabs: new WeakMap(),
   /* Map from browser windows to a set of pinned tabs opened by (a) tour(s) */
   pinnedTabs: new WeakMap(),
   urlbarCapture: new WeakMap(),
   appMenuOpenForAnnotation: new Set(),
@@ -110,20 +113,82 @@ this.UITour = {
     }],
     ["urlbar",      {
       query: "#urlbar",
       widgetName: "urlbar-container",
     }],
   ]),
 
   init: function() {
+    // Lazy getter is initialized here so it can be replicated any time
+    // in a test.
+    delete this.seenPageIDs;
+    Object.defineProperty(this, "seenPageIDs", {
+      get: this.restoreSeenPageIDs.bind(this),
+      configurable: true,
+    });
+
     UITelemetry.addSimpleMeasureFunction("UITour",
                                          this.getTelemetry.bind(this));
   },
 
+  restoreSeenPageIDs: function() {
+    delete this.seenPageIDs;
+
+    if (UITelemetry.enabled) {
+      let dateThreshold = Date.now() - SEENPAGEID_EXPIRY;
+
+      try {
+        let data = Services.prefs.getCharPref(PREF_SEENPAGEIDS);
+        data = new Map(JSON.parse(data));
+
+        for (let [pageID, details] of data) {
+
+          if (typeof pageID != "string" ||
+              typeof details != "object" ||
+              typeof details.lastSeen != "number" ||
+              details.lastSeen < dateThreshold) {
+
+            data.delete(pageID);
+          }
+        }
+
+        this.seenPageIDs = data;
+      } catch (e) {}
+    }
+
+    if (!this.seenPageIDs)
+      this.seenPageIDs = new Map();
+
+    this.persistSeenIDs();
+
+    return this.seenPageIDs;
+  },
+
+  addSeenPageID: function(aPageID) {
+    if (!UITelemetry.enabled)
+      return;
+
+    this.seenPageIDs.set(aPageID, {
+      lastSeen: Date.now(),
+    });
+
+    this.persistSeenIDs();
+  },
+
+  persistSeenIDs: function() {
+    if (this.seenPageIDs.size === 0) {
+      Services.prefs.clearUserPref(PREF_SEENPAGEIDS);
+      return;
+    }
+
+    Services.prefs.setCharPref(PREF_SEENPAGEIDS,
+                               JSON.stringify([...this.seenPageIDs]));
+  },
+
   onPageEvent: function(aEvent) {
     let contentDocument = null;
     if (aEvent.target instanceof Ci.nsIDOMHTMLDocument)
       contentDocument = aEvent.target;
     else if (aEvent.target instanceof Ci.nsIDOMHTMLElement)
       contentDocument = aEvent.target.ownerDocument;
     else
       return false;
@@ -156,21 +221,25 @@ this.UITour = {
       }
       Cu.reportError("Discarding tabless UITour event (" + action + ") while not detaching a tab." +
                      "This shouldn't happen!");
       return;
     }
 
     switch (action) {
       case "registerPageID": {
+        // This is only relevant if Telemtry is enabled.
+        if (!UITelemetry.enabled)
+          break;
+
         // We don't want to allow BrowserUITelemetry.BUCKET_SEPARATOR in the
         // pageID, as it could make parsing the telemetry bucket name difficult.
         if (typeof data.pageID == "string" &&
             !data.pageID.contains(BrowserUITelemetry.BUCKET_SEPARATOR)) {
-          this.seenPageIDs.add(data.pageID);
+          this.addSeenPageID(data.pageID);
 
           // Store tabs and windows separately so we don't need to loop over all
           // tabs when a window is closed.
           this.pageIDSourceTabs.set(tab, data.pageID);
           this.pageIDSourceWindows.set(window, data.pageID);
 
           this.setTelemetryBucket(data.pageID);
         }
@@ -439,17 +508,17 @@ this.UITour = {
                  BrowserUITelemetry.BUCKET_SEPARATOR + aType;
 
     BrowserUITelemetry.setExpiringBucket(bucket,
                                          BUCKET_TIMESTEPS);
   },
 
   getTelemetry: function() {
     return {
-      seenPageIDs: [...this.seenPageIDs],
+      seenPageIDs: [...this.seenPageIDs.keys()],
     };
   },
 
   teardownTour: function(aWindow, aWindowClosing = false) {
     aWindow.gBrowser.tabContainer.removeEventListener("TabSelect", this);
     aWindow.PanelUI.panel.removeEventListener("popuphiding", this.hidePanelAnnotations);
     aWindow.PanelUI.panel.removeEventListener("ViewShowing", this.hidePanelAnnotations);
     aWindow.removeEventListener("SSWindowClosing", this);
--- a/browser/modules/test/browser_UITour_registerPageID.js
+++ b/browser/modules/test/browser_UITour_registerPageID.js
@@ -3,33 +3,78 @@
 
 "use strict";
 
 let gTestTab;
 let gContentAPI;
 let gContentWindow;
 
 Components.utils.import("resource:///modules/UITour.jsm");
+Components.utils.import("resource://gre/modules/UITelemetry.jsm");
 Components.utils.import("resource:///modules/BrowserUITelemetry.jsm");
 
 function test() {
+  UITelemetry._enabled = true;
+
   registerCleanupFunction(function() {
-    UITour.seenPageIDs.clear();
+    Services.prefs.clearUserPref("browser.uitour.seenPageIDs");
+    resetSeenPageIDsLazyGetter();
+    UITelemetry._enabled = undefined;
     BrowserUITelemetry.setBucket(null);
+    delete window.UITelemetry;
     delete window.BrowserUITelemetry;
   });
   UITourTest();
 }
 
+function resetSeenPageIDsLazyGetter() {
+  delete UITour.seenPageIDs;
+  // This should be kept in sync with how UITour.init() sets this.
+  Object.defineProperty(UITour, "seenPageIDs", {
+    get: UITour.restoreSeenPageIDs.bind(UITour),
+    configurable: true,
+  });
+}
+
+function checkExpectedSeenPageIDs(expected) {
+  is(UITour.seenPageIDs.size, expected.length, "Should be " + expected.length + " total seen page IDs");
+
+  for (let id of expected)
+    ok(UITour.seenPageIDs.has(id), "Should have seen '" + id + "' page ID");
+
+  let prefData = Services.prefs.getCharPref("browser.uitour.seenPageIDs");
+  prefData = new Map(JSON.parse(prefData));
+
+  is(prefData.size, expected.length, "Should be " + expected.length + " total seen page IDs persisted");
+
+  for (let id of expected)
+    ok(prefData.has(id), "Should have seen '" + id + "' page ID persisted");
+}
+
 let tests = [
-  function test_seenPageIDs_1(done) {
+  function test_seenPageIDs_restore(done) {
+    info("Setting up seenPageIDs to be restored from pref");
+    let data = JSON.stringify([
+      ["savedID1", { lastSeen: Date.now() }],
+      ["savedID2", { lastSeen: Date.now() }],
+      // 3 weeks ago, should auto expire.
+      ["savedID3", { lastSeen: Date.now() - 3 * 7 * 24 * 60 * 60 * 1000 }],
+    ]);
+    Services.prefs.setCharPref("browser.uitour.seenPageIDs",
+                               data);
+
+    resetSeenPageIDsLazyGetter();
+    checkExpectedSeenPageIDs(["savedID1", "savedID2"]);
+
+    done();
+  },
+  function test_seenPageIDs_set_1(done) {
     gContentAPI.registerPageID("testpage1");
 
-    is(UITour.seenPageIDs.size, 1, "Should be 1 seen page ID");
-    ok(UITour.seenPageIDs.has("testpage1"), "Should have seen 'testpage1' page ID");
+    checkExpectedSeenPageIDs(["savedID1", "savedID2", "testpage1"]);
 
     const PREFIX = BrowserUITelemetry.BUCKET_PREFIX;
     const SEP = BrowserUITelemetry.BUCKET_SEPARATOR;
 
     let bucket = PREFIX + "UITour" + SEP + "testpage1";
     is(BrowserUITelemetry.currentBucket, bucket, "Bucket should have correct name");
 
     gBrowser.selectedTab = gBrowser.addTab("about:blank");
@@ -37,22 +82,20 @@ let tests = [
     is(BrowserUITelemetry.currentBucket, bucket,
        "After switching tabs, bucket should be expiring");
 
     gBrowser.removeTab(gBrowser.selectedTab);
     gBrowser.selectedTab = gTestTab;
     BrowserUITelemetry.setBucket(null);
     done();
   },
-  function test_seenPageIDs_2(done) {
+  function test_seenPageIDs_set_2(done) {
     gContentAPI.registerPageID("testpage2");
 
-    is(UITour.seenPageIDs.size, 2, "Should be 2 seen page IDs");
-    ok(UITour.seenPageIDs.has("testpage1"), "Should have seen 'testpage1' page ID");
-    ok(UITour.seenPageIDs.has("testpage2"), "Should have seen 'testpage2' page ID");
+    checkExpectedSeenPageIDs(["savedID1", "savedID2", "testpage1", "testpage2"]);
 
     const PREFIX = BrowserUITelemetry.BUCKET_PREFIX;
     const SEP = BrowserUITelemetry.BUCKET_SEPARATOR;
 
     let bucket = PREFIX + "UITour" + SEP + "testpage2";
     is(BrowserUITelemetry.currentBucket, bucket, "Bucket should have correct name");
 
     gBrowser.removeTab(gTestTab);
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -11812,17 +11812,19 @@ ICCIOHelperObject.prototype = {
                      " EF id = " + options.fileId.toString(16) +
                      " command = " + options.command.toString(16);
       if (options.sw1 && options.sw2) {
         errorMsg += "(" + options.sw1.toString(16) +
                     "/" + options.sw2.toString(16) + ")";
       }
       this.context.debug(errorMsg);
     }
-    onerror(requestError);
+    if (options.onerror) {
+      options.onerror(requestError);
+    }
   },
 };
 ICCIOHelperObject.prototype[ICC_COMMAND_SEEK] = null;
 ICCIOHelperObject.prototype[ICC_COMMAND_READ_BINARY] = function ICC_COMMAND_READ_BINARY(options) {
   this.processICCIOReadBinary(options);
 };
 ICCIOHelperObject.prototype[ICC_COMMAND_READ_RECORD] = function ICC_COMMAND_READ_RECORD(options) {
   this.processICCIOReadRecord(options);
@@ -12200,17 +12202,17 @@ ICCRecordHelperObject.prototype = {
 
     this.context.ICCIOHelper.updateLinearFixedEF({
       fileId: fileId,
       recordNumber: recordNumber,
       dataWriter: dataWriter,
       callback: onsuccess,
       onerror: onerror
     });
- },
+  },
 
   /**
    * Cache EF_ANR record size.
    */
   _anrRecordSize: null,
 
   /**
    * Read USIM/RUIM Phonebook EF_ANR.
--- a/dom/telephony/Telephony.cpp
+++ b/dom/telephony/Telephony.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Telephony.h"
 #include "mozilla/dom/TelephonyBinding.h"
+#include "mozilla/dom/Promise.h"
 
 #include "nsIURI.h"
 #include "nsPIDOMWindow.h"
 #include "nsIPermissionManager.h"
 
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/Preferences.h"
 #include "nsCharSeparatedTokenizer.h"
@@ -56,16 +57,54 @@ public:
   void
   Disconnect()
   {
     MOZ_ASSERT(mTelephony);
     mTelephony = nullptr;
   }
 };
 
+class Telephony::Callback : public nsITelephonyCallback
+{
+  nsRefPtr<Telephony> mTelephony;
+  nsRefPtr<Promise> mPromise;
+  uint32_t mServiceId;
+  nsString mNumber;
+
+public:
+  NS_DECL_ISUPPORTS
+
+  Callback(Telephony* aTelephony, Promise* aPromise, uint32_t aServiceId,
+           const nsAString& aNumber)
+    : mTelephony(aTelephony), mPromise(aPromise), mServiceId(aServiceId),
+      mNumber(aNumber)
+  {
+    MOZ_ASSERT(mTelephony);
+  }
+
+  virtual ~Callback() {}
+
+  NS_IMETHODIMP
+  NotifyDialError(const nsAString& aError)
+  {
+    mPromise->MaybeReject(aError);
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  NotifyDialSuccess()
+  {
+    nsRefPtr<TelephonyCall> call =
+      mTelephony->CreateNewDialingCall(mServiceId, mNumber);
+
+    mPromise->MaybeResolve(call);
+    return NS_OK;
+  }
+};
+
 class Telephony::EnumerationAck : public nsRunnable
 {
   nsRefPtr<Telephony> mTelephony;
 
 public:
   EnumerationAck(Telephony* aTelephony)
   : mTelephony(aTelephony)
   {
@@ -75,18 +114,17 @@ public:
   NS_IMETHOD Run()
   {
     mTelephony->NotifyCallsChanged(nullptr);
     return NS_OK;
   }
 };
 
 Telephony::Telephony(nsPIDOMWindow* aOwner)
-  : nsDOMEventTargetHelper(aOwner),
-    mActiveCall(nullptr), mEnumerated(false)
+  : nsDOMEventTargetHelper(aOwner), mActiveCall(nullptr), mEnumerated(false)
 {
   if (!gTelephonyList) {
     gTelephonyList = new TelephonyList();
   }
 
   gTelephonyList->AppendElement(this);
 }
 
@@ -230,73 +268,62 @@ Telephony::HasDialingCall()
 bool
 Telephony::MatchActiveCall(TelephonyCall* aCall)
 {
   return (mActiveCall &&
           mActiveCall->CallIndex() == aCall->CallIndex() &&
           mActiveCall->ServiceId() == aCall->ServiceId());
 }
 
-already_AddRefed<TelephonyCall>
+already_AddRefed<Promise>
 Telephony::DialInternal(uint32_t aServiceId, const nsAString& aNumber,
-                        bool aIsEmergency, ErrorResult& aRv)
+                        bool aIsEmergency)
 {
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  if (!global) {
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = new Promise(global);
+
   if (!IsValidNumber(aNumber) || !IsValidServiceId(aServiceId)) {
-    aRv.Throw(NS_ERROR_INVALID_ARG);
-    return nullptr;
+    promise->MaybeReject(NS_LITERAL_STRING("InvalidAccessError"));
+    return promise.forget();
   }
 
   // We only support one outgoing call at a time.
   if (HasDialingCall()) {
-    NS_WARNING("Only permitted to dial one call at a time!");
-    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
-    return nullptr;
-  }
-
-  nsresult rv = mProvider->Dial(aServiceId, aNumber, aIsEmergency);
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-    return nullptr;
+    promise->MaybeReject(NS_LITERAL_STRING("InvalidStateError"));
+    return promise.forget();
   }
 
-  nsRefPtr<TelephonyCall> call = CreateNewDialingCall(aServiceId, aNumber);
-
-  // Notify other telephony objects that we just dialed.
-  for (uint32_t i = 0; i < gTelephonyList->Length(); i++) {
-    Telephony*& telephony = gTelephonyList->ElementAt(i);
-    if (telephony != this) {
-      nsRefPtr<Telephony> kungFuDeathGrip = telephony;
-      telephony->NoteDialedCallFromOtherInstance(aServiceId, aNumber);
-    }
+  nsCOMPtr<nsITelephonyCallback> callback =
+    new Callback(this, promise, aServiceId, aNumber);
+  nsresult rv = mProvider->Dial(aServiceId, aNumber, aIsEmergency, callback);
+  if (NS_FAILED(rv)) {
+    promise->MaybeReject(NS_LITERAL_STRING("InvalidStateError"));
+    return promise.forget();
   }
 
-  return call.forget();
+  return promise.forget();
 }
 
 already_AddRefed<TelephonyCall>
 Telephony::CreateNewDialingCall(uint32_t aServiceId, const nsAString& aNumber)
 {
   nsRefPtr<TelephonyCall> call =
     TelephonyCall::Create(this, aServiceId, aNumber,
                           nsITelephonyProvider::CALL_STATE_DIALING);
   NS_ASSERTION(call, "This should never fail!");
 
   NS_ASSERTION(mCalls.Contains(call), "Should have auto-added new call!");
 
   return call.forget();
 }
 
-void
-Telephony::NoteDialedCallFromOtherInstance(uint32_t aServiceId,
-                                           const nsAString& aNumber)
-{
-  // We don't need to hang on to this call object, it is held alive by mCalls.
-  nsRefPtr<TelephonyCall> call = CreateNewDialingCall(aServiceId, aNumber);
-}
-
 nsresult
 Telephony::NotifyCallsChanged(TelephonyCall* aCall)
 {
   return DispatchCallEvent(NS_LITERAL_STRING("callschanged"), aCall);
 }
 
 void
 Telephony::UpdateActiveCall(TelephonyCall* aCall, bool aIsActive)
@@ -377,36 +404,35 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Telephony)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(Telephony, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(Telephony, nsDOMEventTargetHelper)
 
 NS_IMPL_ISUPPORTS1(Telephony::Listener, nsITelephonyListener)
+NS_IMPL_ISUPPORTS1(Telephony::Callback, nsITelephonyCallback)
 
 // Telephony WebIDL
 
-already_AddRefed<TelephonyCall>
-Telephony::Dial(const nsAString& aNumber, const Optional<uint32_t>& aServiceId,
-                ErrorResult& aRv)
+already_AddRefed<Promise>
+Telephony::Dial(const nsAString& aNumber, const Optional<uint32_t>& aServiceId)
 {
   uint32_t serviceId = ProvidedOrDefaultServiceId(aServiceId);
-  nsRefPtr<TelephonyCall> call = DialInternal(serviceId, aNumber, false, aRv);
-  return call.forget();
+  nsRefPtr<Promise> promise = DialInternal(serviceId, aNumber, false);
+  return promise.forget();
 }
 
-already_AddRefed<TelephonyCall>
+already_AddRefed<Promise>
 Telephony::DialEmergency(const nsAString& aNumber,
-                         const Optional<uint32_t>& aServiceId,
-                         ErrorResult& aRv)
+                         const Optional<uint32_t>& aServiceId)
 {
   uint32_t serviceId = ProvidedOrDefaultServiceId(aServiceId);
-  nsRefPtr<TelephonyCall> call = DialInternal(serviceId, aNumber, true, aRv);
-  return call.forget();
+  nsRefPtr<Promise> promise = DialInternal(serviceId, aNumber, true);
+  return promise.forget();
 }
 
 void
 Telephony::StartTone(const nsAString& aDTMFChar,
                      const Optional<uint32_t>& aServiceId,
                      ErrorResult& aRv)
 {
   uint32_t serviceId = ProvidedOrDefaultServiceId(aServiceId);
@@ -639,17 +665,17 @@ Telephony::EnumerateCallState(uint32_t a
 }
 
 NS_IMETHODIMP
 Telephony::SupplementaryServiceNotification(uint32_t aServiceId,
                                             int32_t aCallIndex,
                                             uint16_t aNotification)
 {
   nsRefPtr<TelephonyCall> associatedCall;
-  if (!mCalls.IsEmpty() && aCallIndex != -1) {
+  if (!mCalls.IsEmpty()) {
     associatedCall = GetCall(aServiceId, aCallIndex);
   }
 
   nsresult rv;
   switch (aNotification) {
     case nsITelephonyProvider::NOTIFICATION_REMOTE_HELD:
       rv = DispatchCallEvent(NS_LITERAL_STRING("remoteheld"), associatedCall);
       break;
@@ -670,21 +696,17 @@ Telephony::NotifyError(uint32_t aService
                        int32_t aCallIndex,
                        const nsAString& aError)
 {
   if (mCalls.IsEmpty()) {
     NS_ERROR("No existing call!");
     return NS_ERROR_UNEXPECTED;
   }
 
-  nsRefPtr<TelephonyCall> callToNotify;
-
-  callToNotify = (aCallIndex == -1) ? GetOutgoingCall()
-                                    : GetCall(aServiceId, aCallIndex);
-
+  nsRefPtr<TelephonyCall> callToNotify = GetCall(aServiceId, aCallIndex);
   if (!callToNotify) {
     NS_ERROR("Don't call me with a bad call index!");
     return NS_ERROR_UNEXPECTED;
   }
 
   UpdateActiveCall(callToNotify, false);
 
   // Set the call state to 'disconnected' and remove it from the calls list.
--- a/dom/telephony/Telephony.h
+++ b/dom/telephony/Telephony.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_telephony_telephony_h__
 #define mozilla_dom_telephony_telephony_h__
 
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/dom/telephony/TelephonyCommon.h"
 
 #include "nsITelephonyProvider.h"
 
 // Need to include TelephonyCall.h because we have inline methods that
 // assume they see the definition of TelephonyCall.
 #include "TelephonyCall.h"
 
@@ -29,16 +30,19 @@ class Telephony MOZ_FINAL : public nsDOM
    * Class Telephony doesn't actually inherit nsITelephonyListener.
    * Instead, it owns an nsITelephonyListener derived instance mListener
    * and passes it to nsITelephonyProvider. The onreceived events are first
    * delivered to mListener and then forwarded to its owner, Telephony. See
    * also bug 775997 comment #51.
    */
   class Listener;
 
+  class Callback;
+  friend class Callback;
+
   class EnumerationAck;
   friend class EnumerationAck;
 
   nsCOMPtr<nsITelephonyProvider> mProvider;
   nsRefPtr<Listener> mListener;
 
   TelephonyCall* mActiveCall;
   nsTArray<nsRefPtr<TelephonyCall> > mCalls;
@@ -61,23 +65,21 @@ public:
     return GetOwner();
   }
 
   // WrapperCache
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   // WebIDL
-  already_AddRefed<TelephonyCall>
-  Dial(const nsAString& aNumber, const Optional<uint32_t>& aServiceId,
-       ErrorResult& aRv);
+  already_AddRefed<Promise>
+  Dial(const nsAString& aNumber, const Optional<uint32_t>& aServiceId);
 
-  already_AddRefed<TelephonyCall>
-  DialEmergency(const nsAString& aNumber, const Optional<uint32_t>& aServiceId,
-                ErrorResult& aRv);
+  already_AddRefed<Promise>
+  DialEmergency(const nsAString& aNumber, const Optional<uint32_t>& aServiceId);
 
   void
   StartTone(const nsAString& aDTMFChar, const Optional<uint32_t>& aServiceId,
             ErrorResult& aRv);
 
   void
   StopTone(const Optional<uint32_t>& aServiceId, ErrorResult& aRv);
 
@@ -165,27 +167,22 @@ private:
   ProvidedOrDefaultServiceId(const Optional<uint32_t>& aServiceId);
 
   bool
   HasDialingCall();
 
   bool
   MatchActiveCall(TelephonyCall* aCall);
 
-  already_AddRefed<TelephonyCall>
-  DialInternal(uint32_t aServiceId, const nsAString& aNumber,
-               bool isEmergency, ErrorResult& aRv);
+  already_AddRefed<Promise>
+  DialInternal(uint32_t aServiceId, const nsAString& aNumber, bool isEmergency);
 
   already_AddRefed<TelephonyCall>
   CreateNewDialingCall(uint32_t aServiceId, const nsAString& aNumber);
 
-  void
-  NoteDialedCallFromOtherInstance(uint32_t aServiceId,
-                                  const nsAString& aNumber);
-
   nsresult
   NotifyCallsChanged(TelephonyCall* aCall);
 
   nsresult
   DispatchCallEvent(const nsAString& aType, TelephonyCall* aCall);
 
   void
   EnqueueEnumerationAck();
--- a/dom/telephony/gonk/TelephonyProvider.js
+++ b/dom/telephony/gonk/TelephonyProvider.js
@@ -395,44 +395,54 @@ TelephonyProvider.prototype = {
     for (let i = 0; i < this._numClients; ++i) {
       promise = promise.then(this._enumerateCallsForClient.bind(this, i, aListener));
     }
     promise.then(function() {
       aListener.enumerateCallStateComplete();
     });
   },
 
-  dial: function(aClientId, aNumber, aIsEmergency) {
+  isDialing: false,
+  dial: function(aClientId, aNumber, aIsEmergency, aTelephonyCallback) {
     if (DEBUG) debug("Dialing " + (aIsEmergency ? "emergency " : "") + aNumber);
 
+    if (this.isDialing) {
+      if (DEBUG) debug("Already has a dialing call. Drop.");
+      aTelephonyCallback.notifyDialError("InvalidStateError");
+      return;
+    }
+
     // we don't try to be too clever here, as the phone is probably in the
     // locked state. Let's just check if it's a number without normalizing
     if (!aIsEmergency) {
       aNumber = gPhoneNumberUtils.normalize(aNumber);
     }
 
+    // Validate the number.
     if (!gPhoneNumberUtils.isPlainPhoneNumber(aNumber)) {
       // Note: isPlainPhoneNumber also accepts USSD and SS numbers
       if (DEBUG) debug("Number '" + aNumber + "' is not viable. Drop.");
       let errorMsg = RIL.RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[RIL.CALL_FAIL_UNOBTAINABLE_NUMBER];
-      Services.tm.currentThread.dispatch(
-        this.notifyCallError.bind(this, aClientId, -1, errorMsg),
-        Ci.nsIThread.DISPATCH_NORMAL);
+      aTelephonyCallback.notifyDialError(errorMsg);
       return;
     }
 
+    this.isDialing = true;
     this._getClient(aClientId).sendWorkerMessage("dial", {
       number: aNumber,
       isDialEmergency: aIsEmergency
-    }, (function(clientId, response) {
-      if (!response.success) {
-        this.notifyCallError(clientId, -1, response.errorMsg);
+    }, (function(response) {
+      this.isDialing = false;
+      if (response.success) {
+        aTelephonyCallback.notifyDialSuccess();
+      } else {
+        aTelephonyCallback.notifyDialError(response.errorMsg);
       }
       return false;
-    }).bind(this, aClientId));
+    }).bind(this));
   },
 
   hangUp: function(aClientId, aCallIndex) {
     this._getClient(aClientId).sendWorkerMessage("hangUp", { callIndex: aCallIndex });
   },
 
   startTone: function(aClientId, aDtmfChar) {
     this._getClient(aClientId).sendWorkerMessage("startTone", { dtmfChar: aDtmfChar });
--- a/dom/telephony/ipc/PTelephony.ipdl
+++ b/dom/telephony/ipc/PTelephony.ipdl
@@ -7,16 +7,34 @@
 include protocol PContent;
 include protocol PTelephonyRequest;
 include TelephonyTypes;
 
 namespace mozilla {
 namespace dom {
 namespace telephony {
 
+struct EnumerateCallsRequest
+{
+  // empty.
+};
+
+struct DialRequest
+{
+  uint32_t clientId;
+  nsString number;
+  bool isEmergency;
+};
+
+union IPCTelephonyRequest
+{
+  EnumerateCallsRequest;
+  DialRequest;
+};
+
 sync protocol PTelephony {
   manager PContent;
   manages PTelephonyRequest;
 
 child:
   NotifyCallError(uint32_t aClientId, int32_t aCallIndex, nsString aError);
 
   NotifyCallStateChanged(uint32_t aClientId, IPCCallStateData aData);
@@ -32,27 +50,24 @@ child:
 
 parent:
   /**
    * Sent when the child no longer needs to use PTelephony.
    */
   __delete__();
 
   /**
-   * Sent when the child makes an asynchronous request to the parent.  It's
-   * currently only for request call enumeration.
+   * Sent when the child makes an asynchronous request to the parent.
    */
-  PTelephonyRequest();
+  PTelephonyRequest(IPCTelephonyRequest request);
 
   RegisterListener();
 
   UnregisterListener();
 
-  DialCall(uint32_t aClientId, nsString aNumber, bool aIsEmergency);
-
   HangUpCall(uint32_t aClientId, uint32_t aCallIndex);
 
   AnswerCall(uint32_t aClientId, uint32_t aCallIndex);
 
   RejectCall(uint32_t aClientId, uint32_t aCallIndex);
 
   HoldCall(uint32_t aClientId, uint32_t aCallIndex);
 
--- a/dom/telephony/ipc/PTelephonyRequest.ipdl
+++ b/dom/telephony/ipc/PTelephonyRequest.ipdl
@@ -6,25 +6,44 @@
 
 include protocol PTelephony;
 include TelephonyTypes;
 
 namespace mozilla {
 namespace dom {
 namespace telephony {
 
+struct EnumerateCallsResponse
+{
+  // empty.
+};
+
+struct DialResponse
+{
+  // empty.
+};
+
+union IPCTelephonyResponse
+{
+  EnumerateCallsResponse;
+  DialResponse;
+};
+
 protocol PTelephonyRequest
 {
   manager PTelephony;
 
 child:
   NotifyEnumerateCallState(uint32_t aClientId, IPCCallStateData aData);
 
+  NotifyDialError(nsString aError);
+
+  NotifyDialSuccess();
+
   /**
-   * Sent when the asynchronous request has completed. It's currently only for
-   * request call enumeration.
+   * Sent when the asynchronous request has completed.
    */
-  __delete__();
+  __delete__(IPCTelephonyResponse aResponse);
 };
 
 } /* namespace telephony */
 } /* namespace dom */
 } /* namespace mozilla */
--- a/dom/telephony/ipc/TelephonyChild.cpp
+++ b/dom/telephony/ipc/TelephonyChild.cpp
@@ -19,17 +19,17 @@ TelephonyChild::TelephonyChild(nsITeleph
 
 void
 TelephonyChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   mListener = nullptr;
 }
 
 PTelephonyRequestChild*
-TelephonyChild::AllocPTelephonyRequestChild()
+TelephonyChild::AllocPTelephonyRequestChild(const IPCTelephonyRequest& aRequest)
 {
   MOZ_CRASH("Caller is supposed to manually construct a request!");
 }
 
 bool
 TelephonyChild::DeallocPTelephonyRequestChild(PTelephonyRequestChild* aActor)
 {
   delete aActor;
@@ -104,34 +104,43 @@ TelephonyChild::RecvNotifySupplementaryS
                                               aNotification);
   return true;
 }
 
 /*******************************************************************************
  * TelephonyRequestChild
  ******************************************************************************/
 
-TelephonyRequestChild::TelephonyRequestChild(nsITelephonyListener* aListener)
-  : mListener(aListener)
+TelephonyRequestChild::TelephonyRequestChild(nsITelephonyListener* aListener,
+                                             nsITelephonyCallback* aCallback)
+  : mListener(aListener), mCallback(aCallback)
 {
-  MOZ_ASSERT(aListener);
 }
 
 void
 TelephonyRequestChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   mListener = nullptr;
+  mCallback = nullptr;
 }
 
 bool
-TelephonyRequestChild::Recv__delete__()
+TelephonyRequestChild::Recv__delete__(const IPCTelephonyResponse& aResponse)
 {
-  MOZ_ASSERT(mListener);
+  switch (aResponse.type()) {
+    case IPCTelephonyResponse::TEnumerateCallsResponse:
+      mListener->EnumerateCallStateComplete();
+      break;
+    case IPCTelephonyResponse::TDialResponse:
+      // Do nothing.
+      break;
+    default:
+      MOZ_CRASH("Unknown type!");
+  }
 
-  mListener->EnumerateCallStateComplete();
   return true;
 }
 
 bool
 TelephonyRequestChild::RecvNotifyEnumerateCallState(const uint32_t& aClientId,
                                                     const IPCCallStateData& aData)
 {
   MOZ_ASSERT(mListener);
@@ -141,8 +150,26 @@ TelephonyRequestChild::RecvNotifyEnumera
                                 aData.callState(),
                                 aData.number(),
                                 aData.isActive(),
                                 aData.isOutGoing(),
                                 aData.isEmergency(),
                                 aData.isConference());
   return true;
 }
+
+bool
+TelephonyRequestChild::RecvNotifyDialError(const nsString& aError)
+{
+  MOZ_ASSERT(mCallback);
+
+  mCallback->NotifyDialError(aError);
+  return true;
+}
+
+bool
+TelephonyRequestChild::RecvNotifyDialSuccess()
+{
+  MOZ_ASSERT(mCallback);
+
+  mCallback->NotifyDialSuccess();
+  return true;
+}
--- a/dom/telephony/ipc/TelephonyChild.h
+++ b/dom/telephony/ipc/TelephonyChild.h
@@ -20,17 +20,17 @@ public:
 
 protected:
   virtual ~TelephonyChild() {}
 
   virtual void
   ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
   virtual PTelephonyRequestChild*
-  AllocPTelephonyRequestChild() MOZ_OVERRIDE;
+  AllocPTelephonyRequestChild(const IPCTelephonyRequest& aRequest) MOZ_OVERRIDE;
 
   virtual bool
   DeallocPTelephonyRequestChild(PTelephonyRequestChild* aActor) MOZ_OVERRIDE;
 
   virtual bool
   RecvNotifyCallError(const uint32_t& aClientId, const int32_t& aCallIndex,
                       const nsString& aError) MOZ_OVERRIDE;
 
@@ -56,30 +56,38 @@ protected:
 
 private:
   nsCOMPtr<nsITelephonyListener> mListener;
 };
 
 class TelephonyRequestChild : public PTelephonyRequestChild
 {
 public:
-  TelephonyRequestChild(nsITelephonyListener* aListener);
+  TelephonyRequestChild(nsITelephonyListener* aListener,
+                        nsITelephonyCallback* aCallback);
 
 protected:
   virtual ~TelephonyRequestChild() {}
 
   virtual void
   ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
   virtual bool
-  Recv__delete__() MOZ_OVERRIDE;
+  Recv__delete__(const IPCTelephonyResponse& aResponse) MOZ_OVERRIDE;
 
   virtual bool
   RecvNotifyEnumerateCallState(const uint32_t& aClientId,
                                const IPCCallStateData& aData) MOZ_OVERRIDE;
 
+  virtual bool
+  RecvNotifyDialError(const nsString& aError) MOZ_OVERRIDE;
+
+  virtual bool
+  RecvNotifyDialSuccess() MOZ_OVERRIDE;
+
 private:
   nsCOMPtr<nsITelephonyListener> mListener;
+  nsCOMPtr<nsITelephonyCallback> mCallback;
 };
 
 END_TELEPHONY_NAMESPACE
 
 #endif // mozilla_dom_telephony_TelephonyChild_h
--- a/dom/telephony/ipc/TelephonyIPCProvider.cpp
+++ b/dom/telephony/ipc/TelephonyIPCProvider.cpp
@@ -112,32 +112,40 @@ TelephonyIPCProvider::UnregisterListener
   mListeners.RemoveElement(aListener);
 
   if (!mListeners.Length()) {
     mPTelephonyChild->SendUnregisterListener();
   }
   return NS_OK;
 }
 
-NS_IMETHODIMP
-TelephonyIPCProvider::EnumerateCalls(nsITelephonyListener *aListener)
+nsresult
+TelephonyIPCProvider::SendRequest(nsITelephonyListener *aListener,
+                                  nsITelephonyCallback *aCallback,
+                                  const IPCTelephonyRequest& aRequest)
 {
   // Life time of newly allocated TelephonyRequestChild instance is managed by
   // IPDL itself.
-  TelephonyRequestChild* actor = new TelephonyRequestChild(aListener);
-  mPTelephonyChild->SendPTelephonyRequestConstructor(actor);
+  TelephonyRequestChild* actor = new TelephonyRequestChild(aListener, aCallback);
+  mPTelephonyChild->SendPTelephonyRequestConstructor(actor, aRequest);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TelephonyIPCProvider::Dial(uint32_t aClientId, const nsAString& aNumber,
-                           bool aIsEmergency)
+TelephonyIPCProvider::EnumerateCalls(nsITelephonyListener *aListener)
 {
-  mPTelephonyChild->SendDialCall(aClientId, nsString(aNumber), aIsEmergency);
-  return NS_OK;
+  return SendRequest(aListener, nullptr, EnumerateCallsRequest());
+}
+
+NS_IMETHODIMP
+TelephonyIPCProvider::Dial(uint32_t aClientId, const nsAString& aNumber,
+                           bool aIsEmergency, nsITelephonyCallback *aCallback)
+{
+  return SendRequest(nullptr, aCallback,
+                     DialRequest(aClientId, nsString(aNumber), aIsEmergency));
 }
 
 NS_IMETHODIMP
 TelephonyIPCProvider::HangUp(uint32_t aClientId, uint32_t aCallIndex)
 {
   mPTelephonyChild->SendHangUpCall(aClientId, aCallIndex);
   return NS_OK;
 }
--- a/dom/telephony/ipc/TelephonyIPCProvider.h
+++ b/dom/telephony/ipc/TelephonyIPCProvider.h
@@ -8,16 +8,17 @@
 
 #include "mozilla/dom/telephony/TelephonyCommon.h"
 #include "mozilla/Attributes.h"
 #include "nsIObserver.h"
 #include "nsITelephonyProvider.h"
 
 BEGIN_TELEPHONY_NAMESPACE
 
+struct IPCTelephonyRequest;
 class PTelephonyChild;
 
 class TelephonyIPCProvider MOZ_FINAL : public nsITelephonyProvider
                                      , public nsITelephonyListener
                                      , public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
@@ -29,13 +30,17 @@ public:
 
 protected:
   virtual ~TelephonyIPCProvider();
 
 private:
   nsTArray<nsCOMPtr<nsITelephonyListener> > mListeners;
   PTelephonyChild* mPTelephonyChild;
   uint32_t mDefaultServiceId;
+
+  nsresult SendRequest(nsITelephonyListener *aListener,
+                       nsITelephonyCallback *aCallback,
+                       const IPCTelephonyRequest& aRequest);
 };
 
 END_TELEPHONY_NAMESPACE
 
 #endif // mozilla_dom_telephony_TelephonyIPCProvider_h
--- a/dom/telephony/ipc/TelephonyParent.cpp
+++ b/dom/telephony/ipc/TelephonyParent.cpp
@@ -28,25 +28,35 @@ TelephonyParent::ActorDestroy(ActorDestr
   // an error here to avoid sending a message to the dead process.
   mActorDestroyed = true;
 
   // Try to unregister listener if we're still registered.
   RecvUnregisterListener();
 }
 
 bool
-TelephonyParent::RecvPTelephonyRequestConstructor(PTelephonyRequestParent* aActor)
+TelephonyParent::RecvPTelephonyRequestConstructor(PTelephonyRequestParent* aActor,
+                                                  const IPCTelephonyRequest& aRequest)
 {
   TelephonyRequestParent* actor = static_cast<TelephonyRequestParent*>(aActor);
 
-  return actor->DoRequest();
+  switch (aRequest.type()) {
+    case IPCTelephonyRequest::TEnumerateCallsRequest:
+      return actor->DoRequest(aRequest.get_EnumerateCallsRequest());
+    case IPCTelephonyRequest::TDialRequest:
+      return actor->DoRequest(aRequest.get_DialRequest());
+    default:
+      MOZ_CRASH("Unknown type!");
+  }
+
+  return false;
 }
 
 PTelephonyRequestParent*
-TelephonyParent::AllocPTelephonyRequestParent()
+TelephonyParent::AllocPTelephonyRequestParent(const IPCTelephonyRequest& aRequest)
 {
   TelephonyRequestParent* actor = new TelephonyRequestParent();
   // Add an extra ref for IPDL. Will be released in
   // TelephonyParent::DeallocPTelephonyRequestParent().
   NS_ADDREF(actor);
 
   return actor;
 }
@@ -87,29 +97,16 @@ TelephonyParent::RecvUnregisterListener(
     do_GetService(TELEPHONY_PROVIDER_CONTRACTID);
   NS_ENSURE_TRUE(provider, true);
 
   mRegistered = !NS_SUCCEEDED(provider->UnregisterListener(this));
   return true;
 }
 
 bool
-TelephonyParent::RecvDialCall(const uint32_t& aClientId,
-                              const nsString& aNumber,
-                              const bool& aIsEmergency)
-{
-  nsCOMPtr<nsITelephonyProvider> provider =
-    do_GetService(TELEPHONY_PROVIDER_CONTRACTID);
-  NS_ENSURE_TRUE(provider, true);
-
-  provider->Dial(aClientId, aNumber, aIsEmergency);
-  return true;
-}
-
-bool
 TelephonyParent::RecvHangUpCall(const uint32_t& aClientId,
                                 const uint32_t& aCallIndex)
 {
   nsCOMPtr<nsITelephonyProvider> provider =
     do_GetService(TELEPHONY_PROVIDER_CONTRACTID);
   NS_ENSURE_TRUE(provider, true);
 
   provider->HangUp(aClientId, aCallIndex);
@@ -367,50 +364,67 @@ TelephonyParent::SupplementaryServiceNot
   return SendNotifySupplementaryService(aClientId, aCallIndex, aNotification)
       ? NS_OK : NS_ERROR_FAILURE;
 }
 
 /*******************************************************************************
  * TelephonyRequestParent
  ******************************************************************************/
 
-NS_IMPL_ISUPPORTS1(TelephonyRequestParent, nsITelephonyListener)
+NS_IMPL_ISUPPORTS2(TelephonyRequestParent,
+                   nsITelephonyListener,
+                   nsITelephonyCallback)
 
 TelephonyRequestParent::TelephonyRequestParent()
   : mActorDestroyed(false)
 {
 }
 
 void
 TelephonyRequestParent::ActorDestroy(ActorDestroyReason why)
 {
   // The child process could die before this asynchronous notification, in which
   // case ActorDestroy() was called and mActorDestroyed is set to true. Return
   // an error here to avoid sending a message to the dead process.
   mActorDestroyed = true;
 }
 
 bool
-TelephonyRequestParent::DoRequest()
+TelephonyRequestParent::DoRequest(const EnumerateCallsRequest& aRequest)
 {
   nsresult rv = NS_ERROR_FAILURE;
 
   nsCOMPtr<nsITelephonyProvider> provider =
     do_GetService(TELEPHONY_PROVIDER_CONTRACTID);
   if (provider) {
     rv = provider->EnumerateCalls(this);
   }
 
   if (NS_FAILED(rv)) {
     return NS_SUCCEEDED(EnumerateCallStateComplete());
   }
 
   return true;
 }
 
+bool
+TelephonyRequestParent::DoRequest(const DialRequest& aRequest)
+{
+  nsCOMPtr<nsITelephonyProvider> provider =
+    do_GetService(TELEPHONY_PROVIDER_CONTRACTID);
+  if (provider) {
+    provider->Dial(aRequest.clientId(), aRequest.number(),
+                   aRequest.isEmergency(), this);
+  } else {
+    return NS_SUCCEEDED(NotifyDialError(NS_LITERAL_STRING("InvalidStateError")));
+  }
+
+  return true;
+}
+
 // nsITelephonyListener
 
 NS_IMETHODIMP
 TelephonyRequestParent::CallStateChanged(uint32_t aClientId,
                                          uint32_t aCallIndex,
                                          uint16_t aCallState,
                                          const nsAString& aNumber,
                                          bool aIsActive,
@@ -427,17 +441,17 @@ TelephonyRequestParent::ConferenceCallSt
   MOZ_CRASH("Not a TelephonyParent!");
 }
 
 NS_IMETHODIMP
 TelephonyRequestParent::EnumerateCallStateComplete()
 {
   NS_ENSURE_TRUE(!mActorDestroyed, NS_ERROR_FAILURE);
 
-  return Send__delete__(this) ? NS_OK : NS_ERROR_FAILURE;
+  return Send__delete__(this, EnumerateCallsResponse()) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 TelephonyRequestParent::EnumerateCallState(uint32_t aClientId,
                                            uint32_t aCallIndex,
                                            uint16_t aCallState,
                                            const nsAString& aNumber,
                                            bool aIsActive,
@@ -477,8 +491,28 @@ TelephonyRequestParent::NotifyError(uint
 
 NS_IMETHODIMP
 TelephonyRequestParent::SupplementaryServiceNotification(uint32_t aClientId,
                                                          int32_t aCallIndex,
                                                          uint16_t aNotification)
 {
   MOZ_CRASH("Not a TelephonyParent!");
 }
+
+// nsITelephonyCallback
+
+NS_IMETHODIMP
+TelephonyRequestParent::NotifyDialError(const nsAString& aError)
+{
+  NS_ENSURE_TRUE(!mActorDestroyed, NS_ERROR_FAILURE);
+
+  return (SendNotifyDialError(nsString(aError)) &&
+          Send__delete__(this, DialResponse())) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+TelephonyRequestParent::NotifyDialSuccess()
+{
+  NS_ENSURE_TRUE(!mActorDestroyed, NS_ERROR_FAILURE);
+
+  return (SendNotifyDialSuccess() &&
+          Send__delete__(this, DialResponse())) ? NS_OK : NS_ERROR_FAILURE;
+}
--- a/dom/telephony/ipc/TelephonyParent.h
+++ b/dom/telephony/ipc/TelephonyParent.h
@@ -24,37 +24,34 @@ public:
 
 protected:
   virtual ~TelephonyParent() {}
 
   virtual void
   ActorDestroy(ActorDestroyReason why);
 
   virtual bool
-  RecvPTelephonyRequestConstructor(PTelephonyRequestParent* aActor) MOZ_OVERRIDE;
+  RecvPTelephonyRequestConstructor(PTelephonyRequestParent* aActor, const IPCTelephonyRequest& aRequest) MOZ_OVERRIDE;
 
   virtual PTelephonyRequestParent*
-  AllocPTelephonyRequestParent() MOZ_OVERRIDE;
+  AllocPTelephonyRequestParent(const IPCTelephonyRequest& aRequest) MOZ_OVERRIDE;
 
   virtual bool
   DeallocPTelephonyRequestParent(PTelephonyRequestParent* aActor) MOZ_OVERRIDE;
 
   virtual bool
   Recv__delete__() MOZ_OVERRIDE;
 
   virtual bool
   RecvRegisterListener() MOZ_OVERRIDE;
 
   virtual bool
   RecvUnregisterListener() MOZ_OVERRIDE;
 
   virtual bool
-  RecvDialCall(const uint32_t& aClientId, const nsString& aNumber, const bool& aIsEmergency) MOZ_OVERRIDE;
-
-  virtual bool
   RecvHangUpCall(const uint32_t& aClientId, const uint32_t& aCallIndex) MOZ_OVERRIDE;
 
   virtual bool
   RecvAnswerCall(const uint32_t& aClientId, const uint32_t& aCallIndex) MOZ_OVERRIDE;
 
   virtual bool
   RecvRejectCall(const uint32_t& aClientId, const uint32_t& aCallIndex) MOZ_OVERRIDE;
 
@@ -96,32 +93,37 @@ protected:
 
 private:
   bool mActorDestroyed;
   bool mRegistered;
 };
 
 class TelephonyRequestParent : public PTelephonyRequestParent
                              , public nsITelephonyListener
+                             , public nsITelephonyCallback
 {
   friend class TelephonyParent;
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSITELEPHONYLISTENER
+  NS_DECL_NSITELEPHONYCALLBACK
 
 protected:
   TelephonyRequestParent();
   virtual ~TelephonyRequestParent() {}
 
   virtual void
   ActorDestroy(ActorDestroyReason why);
 
 private:
   bool mActorDestroyed;
 
   bool
-  DoRequest();
+  DoRequest(const EnumerateCallsRequest& aRequest);
+
+  bool
+  DoRequest(const DialRequest& aRequest);
 };
 
 END_TELEPHONY_NAMESPACE
 
 #endif /* mozilla_dom_telephony_TelephonyParent_h */
--- a/dom/telephony/nsITelephonyProvider.idl
+++ b/dom/telephony/nsITelephonyProvider.idl
@@ -127,28 +127,44 @@ interface nsITelephonyListener : nsISupp
    *        Error name. Possible values are addError and removeError.
    * @param message
    *        Detailed error message from RIL.
    */
   void notifyConferenceError(in AString name,
                              in AString message);
 };
 
+[scriptable, uuid(c095aa82-aacb-4e53-a787-56a89c3f638e)]
+interface nsITelephonyCallback : nsISupports
+{
+  /**
+   * Called when a dial request fails.
+   * @param error
+   *        Error from RIL.
+   */
+  void notifyDialError(in AString error);
+
+  /**
+   * Called when a dial request succeeds.
+   */
+  void notifyDialSuccess();
+};
+
 %{C++
 #define TELEPHONY_PROVIDER_CID \
   { 0x9cf8aa52, 0x7c1c, 0x4cde, { 0x97, 0x4e, 0xed, 0x2a, 0xa0, 0xe7, 0x35, 0xfa } }
 #define TELEPHONY_PROVIDER_CONTRACTID \
   "@mozilla.org/telephony/telephonyprovider;1"
 %}
 
 /**
  * XPCOM component (in the content process) that provides the telephony
  * information.
  */
-[scriptable, uuid(4ff3ecb7-b024-4752-9dd6-c3623c6e6b8a)]
+[scriptable, uuid(b16ca98f-994f-4ae1-8c2d-e7b18e08d1f3)]
 interface nsITelephonyProvider : nsISupports
 {
   const unsigned short CALL_STATE_UNKNOWN = 0;
   const unsigned short CALL_STATE_DIALING = 1;
   const unsigned short CALL_STATE_ALERTING = 2;
   const unsigned short CALL_STATE_CONNECTING = 3;
   const unsigned short CALL_STATE_CONNECTED = 4;
   const unsigned short CALL_STATE_HOLDING = 5;
@@ -176,17 +192,17 @@ interface nsITelephonyProvider : nsISupp
    * returns false.
    */
   void enumerateCalls(in nsITelephonyListener listener);
 
   /**
    * Functionality for making and managing phone calls.
    */
   void dial(in unsigned long clientId, in DOMString number,
-            in boolean isEmergency);
+            in boolean isEmergency, in nsITelephonyCallback callback);
   void hangUp(in unsigned long clientId, in unsigned long callIndex);
 
   void startTone(in unsigned long clientId, in DOMString dtmfChar);
   void stopTone(in unsigned long clientId);
 
   void answerCall(in unsigned long clientId, in unsigned long callIndex);
   void rejectCall(in unsigned long clientId, in unsigned long callIndex);
   void holdCall(in unsigned long clientId, in unsigned long callIndex);
--- a/dom/telephony/test/marionette/test_audiomanager_phonestate.js
+++ b/dom/telephony/test/marionette/test_audiomanager_phonestate.js
@@ -44,28 +44,29 @@ function checkEventCallState(event, call
   is(call, event.call, "event.call");
   is(call.state, state, "call state");
 }
 
 function dial(number) {
   log("Make an outgoing call: " + number);
 
   let deferred = Promise.defer();
-  let call = telephony.dial(number);
 
-  ok(call);
-  is(call.number, number);
-  is(call.state, "dialing");
+  telephony.dial(number).then(call => {
+    ok(call);
+    is(call.number, number);
+    is(call.state, "dialing");
 
-  call.onalerting = function onalerting(event) {
-    call.onalerting = null;
-    log("Received 'onalerting' call event.");
-    checkEventCallState(event, call, "alerting");
-    deferred.resolve(call);
-  };
+    call.onalerting = function onalerting(event) {
+      call.onalerting = null;
+      log("Received 'onalerting' call event.");
+      checkEventCallState(event, call, "alerting");
+      deferred.resolve(call);
+    };
+  });
 
   return deferred.promise;
 }
 
 function answer(call) {
   log("Answering the incoming call.");
 
   let deferred = Promise.defer();
--- a/dom/telephony/test/marionette/test_conference.js
+++ b/dom/telephony/test/marionette/test_conference.js
@@ -101,28 +101,29 @@ function receivedPending(received, pendi
     nextAction();
   }
 }
 
 function dial(number) {
   log("Make an outgoing call: " + number);
 
   let deferred = Promise.defer();
-  let call = telephony.dial(number);
 
-  ok(call);
-  is(call.number, number);
-  is(call.state, "dialing");
+  telephony.dial(number).then(call => {
+    ok(call);
+    is(call.number, number);
+    is(call.state, "dialing");
 
-  call.onalerting = function onalerting(event) {
-    call.onalerting = null;
-    log("Received 'onalerting' call event.");
-    checkEventCallState(event, call, "alerting");
-    deferred.resolve(call);
-  };
+    call.onalerting = function onalerting(event) {
+      call.onalerting = null;
+      log("Received 'onalerting' call event.");
+      checkEventCallState(event, call, "alerting");
+      deferred.resolve(call);
+    };
+  });
 
   return deferred.promise;
 }
 
 // Answering an incoming call could trigger conference state change.
 function answer(call, conferenceStateChangeCallback) {
   log("Answering the incoming call.");
 
--- a/dom/telephony/test/marionette/test_crash_emulator.js
+++ b/dom/telephony/test/marionette/test_crash_emulator.js
@@ -4,22 +4,23 @@
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let outNumber = "5555551111";
 let outgoingCall;
 
 function dial() {
   log("Make an outgoing call.");
-  outgoingCall = telephony.dial(outNumber);
-
-  outgoingCall.onalerting = function onalerting(event) {
-    log("Received 'alerting' call event.");
-    answer();
-  };
+  telephony.dial(outNumber).then(call => {
+    outgoingCall = call;
+    outgoingCall.onalerting = function onalerting(event) {
+      log("Received 'alerting' call event.");
+      answer();
+    };
+  });
 }
 
 function answer() {
   log("Answering the outgoing call.");
 
   outgoingCall.onconnected = function onconnectedOut(event) {
     log("Received 'connected' call event for the original outgoing call.");
     // just some code to keep call active for awhile
--- a/dom/telephony/test/marionette/test_dsds_normal_call.js
+++ b/dom/telephony/test/marionette/test_dsds_normal_call.js
@@ -68,29 +68,30 @@ function checkAll(active, calls, callLis
   return checkEmulatorCallList(callList);
 }
 
 function dial(number, serviceId) {
   serviceId = typeof serviceId !== "undefined" ? serviceId : 0;
   log("Make an outgoing call: " + number + ", serviceId: " + serviceId);
 
   let deferred = Promise.defer();
-  let call = telephony.dial(number, serviceId);
 
-  ok(call);
-  is(call.number, number);
-  is(call.state, "dialing");
+  telephony.dial(number).then(call => {
+    ok(call);
+    is(call.number, number);
+    is(call.state, "dialing");
 
-  call.onalerting = function onalerting(event) {
-    call.onalerting = null;
-    log("Received 'onalerting' call event.");
-    is(call.serviceId, serviceId);
-    checkEventCallState(event, call, "alerting");
-    deferred.resolve(call);
-  };
+    call.onalerting = function onalerting(event) {
+      call.onalerting = null;
+      log("Received 'onalerting' call event.");
+      is(call.serviceId, serviceId);
+      checkEventCallState(event, call, "alerting");
+      deferred.resolve(call);
+    };
+  });
 
   return deferred.promise;
 }
 
 function remoteDial(number) {
   log("Simulating an incoming call.");
 
   let deferred = Promise.defer();
--- a/dom/telephony/test/marionette/test_emergency.js
+++ b/dom/telephony/test/marionette/test_emergency.js
@@ -6,38 +6,41 @@ MARIONETTE_HEAD_JS = 'head.js';
 
 let number = "911";
 let outgoing;
 let calls;
 
 function dial() {
   log("Make an emergency call.");
 
-  outgoing = telephony.dialEmergency(number);
-  ok(outgoing);
-  is(outgoing.number, number);
-  is(outgoing.state, "dialing");
+  telephony.dialEmergency(number).then(call => {
+    outgoing = call;
+    ok(outgoing);
+    is(outgoing.number, number);
+    is(outgoing.state, "dialing");
 
-  is(outgoing, telephony.active);
-  //ok(telephony.calls === calls); // bug 717414
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoing);
+    is(outgoing, telephony.active);
+    //ok(telephony.calls === calls); // bug 717414
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoing);
 
-  outgoing.onalerting = function onalerting(event) {
-    log("Received 'onalerting' call event.");
-    is(outgoing, event.call);
-    is(outgoing.state, "alerting");
+    outgoing.onalerting = function onalerting(event) {
+      log("Received 'onalerting' call event.");
+      is(outgoing, event.call);
+      is(outgoing.state, "alerting");
+      is(outgoing.emergency, true);
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + number + "        : ringing");
-      is(result[1], "OK");
-      answer();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + number + "        : ringing");
+        is(result[1], "OK");
+        answer();
+      });
+    };
+  });
 }
 
 function answer() {
   log("Answering the emergency call.");
 
   // We get no "connecting" event when the remote party answers the call.
 
   outgoing.onconnected = function onconnected(event) {
--- a/dom/telephony/test/marionette/test_emergency_badNumber.js
+++ b/dom/telephony/test/marionette/test_emergency_badNumber.js
@@ -1,46 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let number = "not a valid emergency number";
-let outgoing;
-let calls;
 
 function dial() {
   log("Make an outgoing call to an invalid number.");
 
-  outgoing = telephony.dialEmergency(number);
-  ok(outgoing);
-  is(outgoing.number, number);
-  is(outgoing.state, "dialing");
+  telephony.dialEmergency(number).then(null, cause => {
+    log("Received promise 'reject'");
 
-  is(outgoing, telephony.active);
-  //ok(telephony.calls === calls); // bug 717414
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoing);
-
-  outgoing.onerror = function onerror(event) {
-    log("Received 'error' event.");
-    is(event.call, outgoing);
-    ok(event.call.error);
-    is(event.call.error.name, "BadNumberError");
-
+    is(telephony.active, null);
     is(telephony.calls.length, 0);
-    is(telephony.active, null);
+    is(cause, "BadNumberError");
 
     emulator.run("gsm list", function(result) {
       log("Initial call list: " + result);
       is(result[0], "OK");
       cleanUp();
     });
-  };
+  });
 }
 
 function cleanUp() {
   finish();
 }
 
 startTest(function() {
   dial();
--- a/dom/telephony/test/marionette/test_emergency_label.js
+++ b/dom/telephony/test/marionette/test_emergency_label.js
@@ -21,38 +21,40 @@ function createGoldenCallListResult0(num
   let padPattern = "          ";
   let pad = padPattern.substring(0, padPattern.length - number.length);
   return "outbound to  " + number + pad + " : " + state;
 }
 
 function dial() {
   log("Make an outgoing call.");
 
-  outgoing = telephony.dial(number);
-  ok(outgoing);
-  is(outgoing.number, number);
-  is(outgoing.state, "dialing");
+  telephony.dial(number).then(call => {
+    outgoing = call;
+    ok(outgoing);
+    is(outgoing.number, number);
+    is(outgoing.state, "dialing");
 
-  is(outgoing, telephony.active);
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoing);
+    is(outgoing, telephony.active);
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoing);
 
-  outgoing.onalerting = function onalerting(event) {
-    log("Received 'onalerting' call event.");
-    is(outgoing, event.call);
-    is(outgoing.state, "alerting");
-    is(outgoing.emergency, emergency);
+    outgoing.onalerting = function onalerting(event) {
+      log("Received 'onalerting' call event.");
+      is(outgoing, event.call);
+      is(outgoing.state, "alerting");
+      is(outgoing.emergency, emergency);
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], createGoldenCallListResult0(number, "ringing"));
-      is(result[1], "OK");
-      answer();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], createGoldenCallListResult0(number, "ringing"));
+        is(result[1], "OK");
+        answer();
+      });
+    };
+  });
 }
 
 function answer() {
   log("Answering the call.");
 
   // We get no "connecting" event when the remote party answers the call.
 
   outgoing.onconnected = function onconnected(event) {
--- a/dom/telephony/test/marionette/test_incoming_already_connected.js
+++ b/dom/telephony/test/marionette/test_incoming_already_connected.js
@@ -7,37 +7,39 @@ MARIONETTE_HEAD_JS = 'head.js';
 let outNumber = "5555551111";
 let inNumber = "5555552222";
 let outgoingCall;
 let incomingCall;
 let gotOriginalConnected = false;
 
 function dial() {
   log("Make an outgoing call.");
-  outgoingCall = telephony.dial(outNumber);
-  ok(outgoingCall);
-  is(outgoingCall.number, outNumber);
-  is(outgoingCall.state, "dialing");
+  telephony.dial(outNumber).then(call => {
+    outgoingCall = call;
+    ok(outgoingCall);
+    is(outgoingCall.number, outNumber);
+    is(outgoingCall.state, "dialing");
 
-  is(outgoingCall, telephony.active);
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoingCall);
+    is(outgoingCall, telephony.active);
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoingCall);
 
-  outgoingCall.onalerting = function onalerting(event) {
-    log("Received 'onalerting' call event.");
-    is(outgoingCall, event.call);
-    is(outgoingCall.state, "alerting");
+    outgoingCall.onalerting = function onalerting(event) {
+      log("Received 'onalerting' call event.");
+      is(outgoingCall, event.call);
+      is(outgoingCall.state, "alerting");
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + outNumber + " : ringing");
-      is(result[1], "OK");
-      answer();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + outNumber + " : ringing");
+        is(result[1], "OK");
+        answer();
+      });
+    };
+  });
 }
 
 function answer() {
   log("Answering the outgoing call.");
 
   // We get no "connecting" event when the remote party answers the call.
   outgoingCall.onconnected = function onconnectedOut(event) {
     log("Received 'connected' call event for the original outgoing call.");
--- a/dom/telephony/test/marionette/test_incoming_already_held.js
+++ b/dom/telephony/test/marionette/test_incoming_already_held.js
@@ -7,37 +7,39 @@ MARIONETTE_HEAD_JS = 'head.js';
 let outNumber = "5555551111";
 let inNumber = "5555552222";
 let outgoingCall;
 let incomingCall;
 let gotOriginalConnected = false;
 
 function dial() {
   log("Make an outgoing call.");
-  outgoingCall = telephony.dial(outNumber);
-  ok(outgoingCall);
-  is(outgoingCall.number, outNumber);
-  is(outgoingCall.state, "dialing");
+  telephony.dial(outNumber).then(call => {
+    outgoingCall = call;
+    ok(outgoingCall);
+    is(outgoingCall.number, outNumber);
+    is(outgoingCall.state, "dialing");
 
-  is(outgoingCall, telephony.active);
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoingCall);
+    is(outgoingCall, telephony.active);
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoingCall);
 
-  outgoingCall.onalerting = function onalerting(event) {
-    log("Received 'onalerting' call event.");
-    is(outgoingCall, event.call);
-    is(outgoingCall.state, "alerting");
+    outgoingCall.onalerting = function onalerting(event) {
+      log("Received 'onalerting' call event.");
+      is(outgoingCall, event.call);
+      is(outgoingCall.state, "alerting");
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + outNumber + " : ringing");
-      is(result[1], "OK");
-      answer();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + outNumber + " : ringing");
+        is(result[1], "OK");
+        answer();
+      });
+    };
+  });
 }
 
 function answer() {
   log("Answering the outgoing call.");
 
   // We get no "connecting" event when the remote party answers the call.
   outgoingCall.onconnected = function onconnectedOut(event) {
     log("Received 'connected' call event for the original outgoing call.");
--- a/dom/telephony/test/marionette/test_multiple_hold.js
+++ b/dom/telephony/test/marionette/test_multiple_hold.js
@@ -91,38 +91,40 @@ function holdCall() {
   };
   incomingCall.hold();
 }
 
 // With one call on hold, make outgoing call
 function dial() {
   log("Making an outgoing call (while have one call already held).");
 
-  outgoingCall = telephony.dial(outNumber);
-  ok(outgoingCall);
-  is(outgoingCall.number, outNumber);
-  is(outgoingCall.state, "dialing");
-  is(outgoingCall, telephony.active);
-  is(telephony.calls.length, 2);
-  is(telephony.calls[0], incomingCall);
-  is(telephony.calls[1], outgoingCall);
+  telephony.dial(outNumber).then(call => {
+    outgoingCall = call;
+    ok(outgoingCall);
+    is(outgoingCall.number, outNumber);
+    is(outgoingCall.state, "dialing");
+    is(outgoingCall, telephony.active);
+    is(telephony.calls.length, 2);
+    is(telephony.calls[0], incomingCall);
+    is(telephony.calls[1], outgoingCall);
 
-  outgoingCall.onalerting = function onalerting(event) {
-    log("Received 'onalerting' call event.");
-    is(outgoingCall, event.call);
-    is(outgoingCall.state, "alerting");
+    outgoingCall.onalerting = function onalerting(event) {
+      log("Received 'onalerting' call event.");
+      is(outgoingCall, event.call);
+      is(outgoingCall.state, "alerting");
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "inbound from " + inNumber + " : held");
-      is(result[1], "outbound to  " + outNumber + " : ringing");
-      is(result[2], "OK");
-      answerOutgoing();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "inbound from " + inNumber + " : held");
+        is(result[1], "outbound to  " + outNumber + " : ringing");
+        is(result[2], "OK");
+        answerOutgoing();
+      });
+    };
+  });
 }
 
 // Have the outgoing call answered
 function answerOutgoing() {
   log("Answering the outgoing/2nd call");
 
   // We get no "connecting" event when the remote party answers the call.
   outgoingCall.onconnected = function onconnectedOut(event) {
--- a/dom/telephony/test/marionette/test_outgoing_already_held.js
+++ b/dom/telephony/test/marionette/test_outgoing_already_held.js
@@ -104,39 +104,41 @@ function holdCall(){
   };
   incomingCall.hold();
 }
 
 // With one call on hold, make outgoing call
 function dial() {
   log("Making an outgoing call (while have one call already held).");
 
-  outgoingCall = telephony.dial(outNumber);
-  ok(outgoingCall);
-  is(outgoingCall.number, outNumber);
-  is(outgoingCall.state, "dialing");
+  telephony.dial(outNumber).then(call => {
+    outgoingCall = call;
+    ok(outgoingCall);
+    is(outgoingCall.number, outNumber);
+    is(outgoingCall.state, "dialing");
 
-  is(outgoingCall, telephony.active);
-  is(telephony.calls.length, 2);
-  is(telephony.calls[0], incomingCall);
-  is(telephony.calls[1], outgoingCall);
+    is(outgoingCall, telephony.active);
+    is(telephony.calls.length, 2);
+    is(telephony.calls[0], incomingCall);
+    is(telephony.calls[1], outgoingCall);
 
-  outgoingCall.onalerting = function onalerting(event) {
-    log("Received 'onalerting' call event.");
-    is(outgoingCall, event.call);
-    is(outgoingCall.state, "alerting");
+    outgoingCall.onalerting = function onalerting(event) {
+      log("Received 'onalerting' call event.");
+      is(outgoingCall, event.call);
+      is(outgoingCall.state, "alerting");
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "inbound from " + inNumber + " : held");
-      is(result[1], "outbound to  " + outNumber + " : ringing");
-      is(result[2], "OK");
-      answerOutgoing();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "inbound from " + inNumber + " : held");
+        is(result[1], "outbound to  " + outNumber + " : ringing");
+        is(result[2], "OK");
+        answerOutgoing();
+      });
+    };
+  });
 }
 
 // Have the outgoing call answered
 function answerOutgoing() {
   log("Answering the outgoing/2nd call");
 
   // We get no "connecting" event when the remote party answers the call.
   outgoingCall.onconnected = function onconnectedOut(event) {
--- a/dom/telephony/test/marionette/test_outgoing_answer_hangup.js
+++ b/dom/telephony/test/marionette/test_outgoing_answer_hangup.js
@@ -6,38 +6,40 @@ MARIONETTE_HEAD_JS = 'head.js';
 
 let number = "5555552368";
 let outgoing;
 let calls;
 
 function dial() {
   log("Make an outgoing call.");
 
-  outgoing = telephony.dial(number);
-  ok(outgoing);
-  is(outgoing.number, number);
-  is(outgoing.state, "dialing");
+  telephony.dial(number).then(call => {
+    outgoing = call;
+    ok(outgoing);
+    is(outgoing.number, number);
+    is(outgoing.state, "dialing");
 
-  is(outgoing, telephony.active);
-  //ok(telephony.calls === calls); // bug 717414
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoing);
+    is(outgoing, telephony.active);
+    //ok(telephony.calls === calls); // bug 717414
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoing);
 
-  outgoing.onalerting = function onalerting(event) {
-    log("Received 'onalerting' call event.");
-    is(outgoing, event.call);
-    is(outgoing.state, "alerting");
+    outgoing.onalerting = function onalerting(event) {
+      log("Received 'onalerting' call event.");
+      is(outgoing, event.call);
+      is(outgoing.state, "alerting");
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + number + " : ringing");
-      is(result[1], "OK");
-      answer();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + number + " : ringing");
+        is(result[1], "OK");
+        answer();
+      });
+    };
+  });
 }
 
 function answer() {
   log("Answering the outgoing call.");
 
   // We get no "connecting" event when the remote party answers the call.
 
   outgoing.onconnected = function onconnected(event) {
--- a/dom/telephony/test/marionette/test_outgoing_answer_local_hangup.js
+++ b/dom/telephony/test/marionette/test_outgoing_answer_local_hangup.js
@@ -5,38 +5,40 @@ MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let outgoingCall;
 let outNumber = "5555551111";
 
 function dial() {
   log("Make an outgoing call.");
 
-  outgoingCall = telephony.dial(outNumber);
-  ok(outgoingCall);
-  is(outgoingCall.number, outNumber);
-  is(outgoingCall.state, "dialing");
+  telephony.dial(outNumber).then(call => {
+    outgoingCall = call;
+    ok(outgoingCall);
+    is(outgoingCall.number, outNumber);
+    is(outgoingCall.state, "dialing");
 
-  is(outgoingCall, telephony.active);
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoingCall);
+    is(outgoingCall, telephony.active);
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoingCall);
 
-  outgoingCall.onalerting = function onalerting(event) {
-    log("Received 'alerting' call event.");
+    outgoingCall.onalerting = function onalerting(event) {
+      log("Received 'alerting' call event.");
 
-    is(outgoingCall, event.call);
-    is(outgoingCall.state, "alerting");
+      is(outgoingCall, event.call);
+      is(outgoingCall.state, "alerting");
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + outNumber + " : ringing");
-      is(result[1], "OK");
-      answer();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + outNumber + " : ringing");
+        is(result[1], "OK");
+        answer();
+      });
+    };
+  });
 }
 
 function answer() {
   log("Answering the outgoing call.");
 
   // We get no "connecting" event when the remote party answers the call.
 
   outgoingCall.onconnected = function onconnected(event) {
--- a/dom/telephony/test/marionette/test_outgoing_answer_radio_off.js
+++ b/dom/telephony/test/marionette/test_outgoing_answer_radio_off.js
@@ -25,29 +25,29 @@ function setRadioEnabled(enabled) {
 
   return deferred.promise;
 }
 
 function dial(number) {
   log("Make an outgoing call.");
 
   let deferred = Promise.defer();
-  let call = telephony.dial(number);
-
-  ok(call);
-  is(call.number, number);
-  is(call.state, "dialing");
+  telephony.dial(number).then(call => {
+    ok(call);
+    is(call.number, number);
+    is(call.state, "dialing");
 
-  call.onalerting = function(event) {
-    log("Received 'onalerting' call event.");
-    call.onalerting = null;
-    is(call, event.call);
-    is(call.state, "alerting");
-    deferred.resolve(call);
-  };
+    call.onalerting = function(event) {
+      log("Received 'onalerting' call event.");
+      call.onalerting = null;
+      is(call, event.call);
+      is(call.state, "alerting");
+      deferred.resolve(call);
+    };
+  });
 
   return deferred.promise;
 }
 
 function remoteAnswer(call) {
   log("Remote answering the call.");
 
   let deferred = Promise.defer();
--- a/dom/telephony/test/marionette/test_outgoing_badNumber.js
+++ b/dom/telephony/test/marionette/test_outgoing_badNumber.js
@@ -2,40 +2,46 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let number = "****5555552368****";
 let outgoing;
 
+
 function dial() {
   log("Make an outgoing call to an invalid number.");
 
-  outgoing = telephony.dial(number);
-  ok(outgoing);
-  is(outgoing.number, number);
-  is(outgoing.state, "dialing");
+  // Note: The number is valid from the view of phone and the call could be
+  // dialed out successfully. However, it will later receive the BadNumberError
+  // from network side.
+  telephony.dial(number).then(call => {
+    outgoing = call;
+    ok(outgoing);
+    is(outgoing.number, number);
+    is(outgoing.state, "dialing");
 
-  is(outgoing, telephony.active);
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoing);
+    is(outgoing, telephony.active);
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoing);
 
-  outgoing.onerror = function onerror(event) {
-    log("Received 'error' event.");
-    is(event.call, outgoing);
-    ok(event.call.error);
-    is(event.call.error.name, "BadNumberError");
+    outgoing.onerror = function onerror(event) {
+      log("Received 'error' event.");
+      is(event.call, outgoing);
+      ok(event.call.error);
+      is(event.call.error.name, "BadNumberError");
 
-    emulator.run("gsm list", function(result) {
-      log("Initial call list: " + result);
-      is(result[0], "OK");
-      cleanUp();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Initial call list: " + result);
+        is(result[0], "OK");
+        cleanUp();
+      });
+    };
+  });
 }
 
 function cleanUp() {
   finish();
 }
 
 startTest(function() {
   dial();
--- a/dom/telephony/test/marionette/test_outgoing_busy.js
+++ b/dom/telephony/test/marionette/test_outgoing_busy.js
@@ -5,37 +5,39 @@ MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let number = "5555552368";
 let outgoing;
 
 function dial() {
   log("Make an outgoing call.");
 
-  outgoing = telephony.dial(number);
-  ok(outgoing);
-  is(outgoing.number, number);
-  is(outgoing.state, "dialing");
+  telephony.dial(number).then(call => {
+    outgoing = call;
+    ok(outgoing);
+    is(outgoing.number, number);
+    is(outgoing.state, "dialing");
 
-  is(outgoing, telephony.active);
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoing);
+    is(outgoing, telephony.active);
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoing);
 
-  outgoing.onalerting = function onalerting(event) {
-    log("Received 'onalerting' call event.");
-    is(outgoing, event.call);
-    is(outgoing.state, "alerting");
+    outgoing.onalerting = function onalerting(event) {
+      log("Received 'onalerting' call event.");
+      is(outgoing, event.call);
+      is(outgoing.state, "alerting");
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + number + " : ringing");
-      is(result[1], "OK");
-      busy();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + number + " : ringing");
+        is(result[1], "OK");
+        busy();
+      });
+    };
+  });
 }
 
 function busy() {
   log("The receiver is busy.");
 
   outgoing.onerror = function onerror(event) {
     log("Received 'error' call event.");
     is(outgoing, event.call);
--- a/dom/telephony/test/marionette/test_outgoing_emergency_in_airplane_mode.js
+++ b/dom/telephony/test/marionette/test_outgoing_emergency_in_airplane_mode.js
@@ -41,37 +41,39 @@ function setRadioEnabled(enabled, callba
   request.onerror = function onerror() {
     ok(false, "setRadioEnabled should be ok");
   };
 }
 
 function dial() {
   log("Make an outgoing call.");
 
-  outgoing = telephony.dial(number);
-  ok(outgoing);
-  is(outgoing.number, number);
-  is(outgoing.state, "dialing");
+  telephony.dial(number).then(call => {
+    outgoing = call;
+    ok(outgoing);
+    is(outgoing.number, number);
+    is(outgoing.state, "dialing");
 
-  is(outgoing, telephony.active);
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoing);
+    is(outgoing, telephony.active);
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoing);
 
-  outgoing.onalerting = function onalerting(event) {
-    log("Received 'onalerting' call event.");
-    is(outgoing, event.call);
-    is(outgoing.state, "alerting");
+    outgoing.onalerting = function onalerting(event) {
+      log("Received 'onalerting' call event.");
+      is(outgoing, event.call);
+      is(outgoing.state, "alerting");
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + number + "        : ringing");
-      is(result[1], "OK");
-      answer();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + number + "        : ringing");
+        is(result[1], "OK");
+        answer();
+      });
+    };
+  });
 }
 
 function answer() {
   log("Answering the outgoing call.");
 
   // We get no "connecting" event when the remote party answers the call.
 
   outgoing.onconnected = function onconnected(event) {
--- a/dom/telephony/test/marionette/test_outgoing_hangup_alerting.js
+++ b/dom/telephony/test/marionette/test_outgoing_hangup_alerting.js
@@ -6,35 +6,37 @@ MARIONETTE_HEAD_JS = 'head.js';
 
 let number = "5555552368";
 let outgoing;
 let calls;
 
 function dial() {
   log("Make an outgoing call.");
 
-  outgoing = telephony.dial(number);
-  ok(outgoing);
-  is(outgoing.number, number);
-  is(outgoing.state, "dialing");
+  telephony.dial(number).then(call => {
+    outgoing = call;
+    ok(outgoing);
+    is(outgoing.number, number);
+    is(outgoing.state, "dialing");
 
-  is(outgoing, telephony.active);
-  //ok(telephony.calls === calls); // bug 717414
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoing);
+    is(outgoing, telephony.active);
+    //ok(telephony.calls === calls); // bug 717414
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoing);
 
-  outgoing.onalerting = function onalerting(event) {
-    log("Received 'alerting' call event.");
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + number + " : ringing");
-      is(result[1], "OK");
-      hangUp();
-    });
-  };
+    outgoing.onalerting = function onalerting(event) {
+      log("Received 'alerting' call event.");
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + number + " : ringing");
+        is(result[1], "OK");
+        hangUp();
+      });
+    };
+  });
 }
 
 function hangUp() {
   log("Hang up the outgoing call.");
 
   let gotDisconnecting = false;
 
   outgoing.ondisconnecting = function ondisconnecting(event) {
--- a/dom/telephony/test/marionette/test_outgoing_hangup_held.js
+++ b/dom/telephony/test/marionette/test_outgoing_hangup_held.js
@@ -6,37 +6,39 @@ MARIONETTE_HEAD_JS = 'head.js';
 
 let number = "5555552368";
 let outgoing;
 let calls;
 
 function dial() {
   log("Make an outgoing call.");
 
-  outgoing = telephony.dial(number);
-  ok(outgoing);
-  is(outgoing.number, number);
-  is(outgoing.state, "dialing");
-  is(outgoing, telephony.active);
-  //ok(telephony.calls === calls); // bug 717414
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoing);
+  telephony.dial(number).then(call => {
+    outgoing = call;
+    ok(outgoing);
+    is(outgoing.number, number);
+    is(outgoing.state, "dialing");
+    is(outgoing, telephony.active);
+    //ok(telephony.calls === calls); // bug 717414
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoing);
 
-  outgoing.onalerting = function onalerting(event) {
-    log("Received 'onalerting' call event.");
-    is(outgoing, event.call);
-    is(outgoing.state, "alerting");
+    outgoing.onalerting = function onalerting(event) {
+      log("Received 'onalerting' call event.");
+      is(outgoing, event.call);
+      is(outgoing.state, "alerting");
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + number + " : ringing");
-      is(result[1], "OK");
-      answer();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + number + " : ringing");
+        is(result[1], "OK");
+        answer();
+      });
+    };
+  });
 }
 
 function answer() {
   log("Answering the outgoing call.");
 
   // We get no "connecting" event when the remote party answers the call.
 
   outgoing.onconnected = function onconnected(event) {
--- a/dom/telephony/test/marionette/test_outgoing_hold_resume.js
+++ b/dom/telephony/test/marionette/test_outgoing_hold_resume.js
@@ -6,37 +6,39 @@ MARIONETTE_HEAD_JS = 'head.js';
 
 let number = "5555557777";
 let connectedCalls;
 let outgoingCall;
 
 function dial() {
   log("Make an outgoing call.");
 
-  outgoingCall = telephony.dial(number);
-  ok(outgoingCall);
-  is(outgoingCall.number, number);
-  is(outgoingCall.state, "dialing");
+  telephony.dial(number).then(call => {
+    outgoingCall = call;
+    ok(outgoingCall);
+    is(outgoingCall.number, number);
+    is(outgoingCall.state, "dialing");
 
-  is(outgoingCall, telephony.active);
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoingCall);
+    is(outgoingCall, telephony.active);
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoingCall);
 
-  outgoingCall.onalerting = function onalerting(event) {
-    log("Received 'onalerting' call event.");
-    is(outgoingCall, event.call);
-    is(outgoingCall.state, "alerting");
+    outgoingCall.onalerting = function onalerting(event) {
+      log("Received 'onalerting' call event.");
+      is(outgoingCall, event.call);
+      is(outgoingCall.state, "alerting");
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + number + " : ringing");
-      is(result[1], "OK");
-      answer();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + number + " : ringing");
+        is(result[1], "OK");
+        answer();
+      });
+    };
+  });
 }
 
 function answer() {
   log("Answering the outgoing call.");
 
   // We get no "connecting" event when the remote party answers the call.
 
   outgoingCall.onconnected = function onconnected(event) {
--- a/dom/telephony/test/marionette/test_outgoing_onstatechange.js
+++ b/dom/telephony/test/marionette/test_outgoing_onstatechange.js
@@ -5,41 +5,43 @@ MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let outgoingCall;
 let outNumber = "5555551111";
 
 function dial() {
   log("Make an outgoing call.");
 
-  outgoingCall = telephony.dial(outNumber);
-  ok(outgoingCall);
-  is(outgoingCall.number, outNumber);
-  is(outgoingCall.state, "dialing");
+  telephony.dial(outNumber).then(call => {
+    outgoingCall = call;
+    ok(outgoingCall);
+    is(outgoingCall.number, outNumber);
+    is(outgoingCall.state, "dialing");
 
-  is(outgoingCall, telephony.active);
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoingCall);
+    is(outgoingCall, telephony.active);
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoingCall);
 
-  outgoingCall.onstatechange = function statechangering(event) {
-    log("Received 'onstatechange' call event.");
+    outgoingCall.onstatechange = function statechangering(event) {
+      log("Received 'onstatechange' call event.");
 
-    is(outgoingCall, event.call);
-    let expectedStates = ["dialing", "alerting"];
-    ok(expectedStates.indexOf(event.call.state) != -1);
+      is(outgoingCall, event.call);
+      let expectedStates = ["dialing", "alerting"];
+      ok(expectedStates.indexOf(event.call.state) != -1);
 
-    if (event.call.state == "alerting") {
-      emulator.run("gsm list", function(result) {
-        log("Call list is now: " + result);
-        is(result[0], "outbound to  " + outNumber + " : ringing");
-        is(result[1], "OK");
-        answer();
-      });
-    }
-  };
+      if (event.call.state == "alerting") {
+        emulator.run("gsm list", function(result) {
+          log("Call list is now: " + result);
+          is(result[0], "outbound to  " + outNumber + " : ringing");
+          is(result[1], "OK");
+          answer();
+        });
+      }
+    };
+  });
 }
 
 function answer() {
   log("Answering the outgoing call.");
 
   // We get no "connecting" event when the remote party answers the call.
 
   outgoingCall.onstatechange = function onstatechangeanswer(event) {
--- a/dom/telephony/test/marionette/test_outgoing_radio_off.js
+++ b/dom/telephony/test/marionette/test_outgoing_radio_off.js
@@ -1,16 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let connection;
-let outgoing;
 
 function receivedPending(received, pending, nextAction) {
   let index = pending.indexOf(received);
   if (index != -1) {
     pending.splice(index, 1);
   }
   if (pending.length === 0) {
     nextAction();
@@ -45,39 +44,31 @@ function setRadioEnabled(enabled, callba
 function dial(number) {
   // Verify initial state before dial.
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
 
   log("Make an outgoing call.");
-  outgoing = telephony.dial(number);
 
-  ok(outgoing);
-  is(outgoing.number, number);
-  is(outgoing.state, "dialing");
+  telephony.dial(number).then(null, cause => {
+    log("Received promise 'reject'");
 
-  is(telephony.active, outgoing);
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoing);
-
-  outgoing.onerror = function onerror(event) {
-    log("Received 'error' event.");
-    is(event.call, outgoing);
-    ok(event.call.error);
-    is(event.call.error.name, "RadioNotAvailable");
+    is(telephony.active, null);
+    is(telephony.calls.length, 0);
+    is(cause, "RadioNotAvailable");
 
     emulator.run("gsm list", function(result) {
       log("Initial call list: " + result);
       is(result[0], "OK");
 
       setRadioEnabled(true, cleanUp);
     });
-  };
+  });
 }
 
 function cleanUp() {
   finish();
 }
 
 startTestWithPermissions(['mobileconnection'], function() {
   connection = navigator.mozMobileConnections[0];
--- a/dom/telephony/test/marionette/test_outgoing_reject.js
+++ b/dom/telephony/test/marionette/test_outgoing_reject.js
@@ -5,37 +5,39 @@ MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let number = "5555552368";
 let outgoing;
 
 function dial() {
   log("Make an outgoing call.");
 
-  outgoing = telephony.dial(number);
-  ok(outgoing);
-  is(outgoing.number, number);
-  is(outgoing.state, "dialing");
+  telephony.dial(number).then(call => {
+    outgoing = call;
+    ok(outgoing);
+    is(outgoing.number, number);
+    is(outgoing.state, "dialing");
 
-  is(outgoing, telephony.active);
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoing);
+    is(outgoing, telephony.active);
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoing);
 
-  outgoing.onalerting = function onalerting(event) {
-    log("Received 'onalerting' call event.");
-    is(outgoing, event.call);
-    is(outgoing.state, "alerting");
+    outgoing.onalerting = function onalerting(event) {
+      log("Received 'onalerting' call event.");
+      is(outgoing, event.call);
+      is(outgoing.state, "alerting");
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + number + " : ringing");
-      is(result[1], "OK");
-      reject();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + number + " : ringing");
+        is(result[1], "OK");
+        reject();
+      });
+    };
+  });
 }
 
 function reject() {
   log("Reject the outgoing call on the other end.");
   // We get no "disconnecting" event when the remote party rejects the call.
 
   outgoing.ondisconnected = function ondisconnected(event) {
     log("Received 'disconnected' call event.");
--- a/dom/telephony/test/marionette/test_outgoing_remote_hangup_held.js
+++ b/dom/telephony/test/marionette/test_outgoing_remote_hangup_held.js
@@ -4,38 +4,40 @@
 MARIONETTE_TIMEOUT = 60000;
 
 let outNumber = "5555551111";
 let outgoingCall;
 
 function dial() {
   log("Make an outgoing call.");
 
-  outgoingCall = telephony.dial(outNumber);
-  ok(outgoingCall);
-  is(outgoingCall.number, outNumber);
-  is(outgoingCall.state, "dialing");
+  telephony.dial(outNumber).then(call => {
+    outgoingCall = call;
+    ok(outgoingCall);
+    is(outgoingCall.number, outNumber);
+    is(outgoingCall.state, "dialing");
 
-  is(outgoingCall, telephony.active);
-  is(telephony.calls.length, 1);
-  is(telephony.calls[0], outgoingCall);
+    is(outgoingCall, telephony.active);
+    is(telephony.calls.length, 1);
+    is(telephony.calls[0], outgoingCall);
 
-  outgoingCall.onalerting = function onalerting(event) {
-    log("Received 'alerting' call event.");
+    outgoingCall.onalerting = function onalerting(event) {
+      log("Received 'alerting' call event.");
 
-    is(outgoingCall, event.call);
-    is(outgoingCall.state, "alerting");
+      is(outgoingCall, event.call);
+      is(outgoingCall.state, "alerting");
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + outNumber + " : ringing");
-      is(result[1], "OK");
-      answer();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + outNumber + " : ringing");
+        is(result[1], "OK");
+        answer();
+      });
+    };
+  });
 }
 
 function answer() {
   log("Answering the outgoing call.");
 
   // We get no "connecting" event when the remote party answers the call.
 
   outgoingCall.onconnected = function onconnected(event) {
--- a/dom/telephony/test/marionette/test_swap_held_and_active.js
+++ b/dom/telephony/test/marionette/test_swap_held_and_active.js
@@ -9,37 +9,39 @@ let inNumber = "5555552222";
 let outgoingCall;
 let incomingCall;
 let gotOriginalConnected = false;
 let gotHeld = false;
 let gotConnected = false;
 
 function dial() {
   log("Make an outgoing call.");
-  outgoingCall = telephony.dial(outNumber);
-  ok(outgoingCall);
-  is(outgoingCall.number, outNumber);
-  is(outgoingCall.state, "dialing");
+  telephony.dial(outNumber).then(call => {
+    outgoingCall = call;
+    ok(outgoingCall);
+    is(outgoingCall.number, outNumber);
+    is(outgoingCall.state, "dialing");
 
-  outgoingCall.onalerting = function onalerting(event) {
-    log("Received 'alerting' call event.");
+    outgoingCall.onalerting = function onalerting(event) {
+      log("Received 'alerting' call event.");
 
-    is(outgoingCall, event.call);
-    is(outgoingCall.state, "alerting");
-    is(outgoingCall, telephony.active);
-    is(telephony.calls.length, 1);
-    is(telephony.calls[0], outgoingCall);
+      is(outgoingCall, event.call);
+      is(outgoingCall.state, "alerting");
+      is(outgoingCall, telephony.active);
+      is(telephony.calls.length, 1);
+      is(telephony.calls[0], outgoingCall);
 
-    emulator.run("gsm list", function(result) {
-      log("Call list is now: " + result);
-      is(result[0], "outbound to  " + outNumber + " : ringing");
-      is(result[1], "OK");
-      answer();
-    });
-  };
+      emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        is(result[0], "outbound to  " + outNumber + " : ringing");
+        is(result[1], "OK");
+        answer();
+      });
+    };
+  });
 }
 
 function answer() {
   log("Answering the outgoing call.");
 
   // We get no "connecting" event when the remote party answers the call.
   outgoingCall.onconnected = function onconnectedOut(event) {
     log("Received 'connected' call event for the original outgoing call.");
--- a/dom/webidl/Telephony.webidl
+++ b/dom/webidl/Telephony.webidl
@@ -11,21 +11,21 @@ interface Telephony : EventTarget {
    * |serviceId| to indicate the target telephony service. If not specified,
    * the implementation MUST use the default service.
    *
    * Possible values of |serviceId| are 0 ~ (number of services - 1), which is
    * simply the index of a service. Get number of services by acquiring
    * |navigator.mozMobileConnections.length|.
    */
 
-  [Throws]
-  TelephonyCall dial(DOMString number, optional unsigned long serviceId);
+  // Promise<TelephonyCall>
+  Promise dial(DOMString number, optional unsigned long serviceId);
 
-  [Throws]
-  TelephonyCall dialEmergency(DOMString number, optional unsigned long serviceId);
+  // Promise<TelephonyCall>
+  Promise dialEmergency(DOMString number, optional unsigned long serviceId);
 
   [Throws]
   void startTone(DOMString tone, optional unsigned long serviceId);
 
   [Throws]
   void stopTone(optional unsigned long serviceId);
 
   [Throws]
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -74,16 +74,52 @@ this.__defineGetter__("gIsInitializing",
 
 function initialize(event) {
   // XXXbz this listener gets _all_ load events for all nodes in the
   // document... but relies on not being called "too early".
   if (event.target instanceof XMLStylesheetProcessingInstruction) {
     return;
   }
   document.removeEventListener("load", initialize, true);
+
+  let globalCommandSet = document.getElementById("globalCommandSet");
+  globalCommandSet.addEventListener("command", function(event) {
+    gViewController.doCommand(event.target.id);
+  });
+
+  let viewCommandSet = document.getElementById("viewCommandSet");
+  viewCommandSet.addEventListener("commandupdate", function(event) {
+    gViewController.updateCommands();
+  });
+  viewCommandSet.addEventListener("command", function(event) {
+    gViewController.doCommand(event.target.id);
+  });
+
+  let detailScreenshot = document.getElementById("detail-screenshot");
+  detailScreenshot.addEventListener("load", function(event) {
+    this.removeAttribute("loading");
+  });
+  detailScreenshot.addEventListener("error", function(event) {
+    this.setAttribute("loading", "error");
+  });
+
+  let addonPage = document.getElementById("addons-page");
+  addonPage.addEventListener("dragenter", function(event) {
+    gDragDrop.onDragOver(event);
+  });
+  addonPage.addEventListener("dragover", function(event) {
+    gDragDrop.onDragOver(event);
+  });
+  addonPage.addEventListener("drop", function(event) {
+    gDragDrop.onDrop(event);
+  });
+  addonPage.addEventListener("keypress", function(event) {
+    gHeader.onKeyPress(event);
+  });
+
   gViewController.initialize();
   gCategories.initialize();
   gHeader.initialize();
   gEventManager.initialize();
   Services.obs.addObserver(sendEMPong, "EM-ping", false);
   Services.obs.notifyObservers(window, "EM-loaded", "");
 
   // If the initial view has already been selected (by a call to loadView from
@@ -674,16 +710,23 @@ var gViewController = {
       isEnabled: function cmd_forward_isEnabled() {
         return gHistory.canGoForward;
       },
       doCommand: function cmd_forward_doCommand() {
         gHistory.forward();
       }
     },
 
+    cmd_focusSearch: {
+      isEnabled: () => true,
+      doCommand: function cmd_focusSearch_doCommand() {
+        gHeader.focusSearchBox();
+      }
+    },
+
     cmd_restartApp: {
       isEnabled: function cmd_restartApp_isEnabled() true,
       doCommand: function cmd_restartApp_doCommand() {
         let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
                          createInstance(Ci.nsISupportsPRBool);
         Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
                                      "restart");
         if (cancelQuit.data)
@@ -1752,16 +1795,29 @@ var gHeader = {
 
   focusSearchBox: function gHeader_focusSearchBox() {
     this._search.focus();
   },
 
   onKeyPress: function gHeader_onKeyPress(aEvent) {
     if (String.fromCharCode(aEvent.charCode) == "/") {
       this.focusSearchBox();
+      return;
+    }
+
+    // XXXunf Temporary until bug 371900 is fixed.
+    let key = document.getElementById("focusSearch").getAttribute("key");
+#ifdef XP_MACOSX
+    let keyModifier = aEvent.metaKey;
+#else
+    let keyModifier = aEvent.ctrlKey;
+#endif
+    if (String.fromCharCode(aEvent.charCode) == key && keyModifier) {
+      this.focusSearchBox();
+      return;
     }
   },
 
   get shouldShowNavButtons() {
     var docshellItem = window.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIWebNavigation)
                              .QueryInterface(Ci.nsIDocShellTreeItem);
 
--- a/toolkit/mozapps/extensions/content/extensions.xul
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -13,21 +13,17 @@
 <!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
 %extensionsDTD;
 ]>
 
 <page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
       xmlns:xhtml="http://www.w3.org/1999/xhtml"
       id="addons-page" title="&addons.windowTitle;"
       role="application" windowtype="Addons:Manager"
-      disablefastfind="true"
-      ondragenter="gDragDrop.onDragOver(event)"
-      ondragover="gDragDrop.onDragOver(event)"
-      ondrop="gDragDrop.onDrop(event)"
-      onkeypress="gHeader.onKeyPress(event)">
+      disablefastfind="true">
 
   <xhtml:link rel="shortcut icon"
               href="chrome://mozapps/skin/extensions/extensionGeneric-16.png"/>
 
   <script type="application/javascript"
           src="chrome://mozapps/content/extensions/extensions.js"/>
   <script type="application/javascript"
           src="chrome://global/content/contentAreaUtils.js"/>
@@ -71,18 +67,18 @@
       <menuitem id="menuitem_about" command="cmd_showItemAbout"
                 label="&cmd.about.label;"
                 accesskey="&cmd.about.accesskey;"/>
     </menupopup>
   </popupset>
 
   <!-- global commands - these act on all addons, or affect the addons manager
        in some other way -->
-  <commandset id="globalCommandSet"
-              oncommand="gViewController.doCommand(event.target.id);">
+  <commandset id="globalCommandSet">
+    <command id="cmd_focusSearch"/>
     <command id="cmd_findAllUpdates"/>
     <command id="cmd_restartApp"/>
     <command id="cmd_goToDiscoverPane"/>
     <command id="cmd_goToRecentUpdates"/>
     <command id="cmd_goToAvailableUpdates"/>
     <command id="cmd_installFromFile"/>
     <command id="cmd_back"/>
     <command id="cmd_forward"/>
@@ -90,19 +86,17 @@
     <command id="cmd_pluginCheck"/>
     <command id="cmd_enableUpdateSecurity"/>
     <command id="cmd_toggleAutoUpdateDefault"/>
     <command id="cmd_resetAddonAutoUpdate"/>
   </commandset>
 
   <!-- view commands - these act on the selected addon -->
   <commandset id="viewCommandSet"
-              events="richlistbox-select" commandupdater="true"
-              oncommandupdate="gViewController.updateCommands();"
-              oncommand="gViewController.doCommand(event.target.id);">
+              events="richlistbox-select" commandupdater="true">
     <command id="cmd_showItemDetails"/>
     <command id="cmd_findItemUpdates"/>
     <command id="cmd_showItemPreferences"/>
     <command id="cmd_showItemAbout"/>
     <command id="cmd_enableItem"/>
     <command id="cmd_disableItem"/>
     <command id="cmd_installItem"/>
     <command id="cmd_purchaseItem"/>
@@ -111,18 +105,19 @@
     <command id="cmd_cancelOperation"/>
     <command id="cmd_contribute"/>
     <command id="cmd_askToActivateItem"/>
     <command id="cmd_alwaysActivateItem"/>
     <command id="cmd_neverActivateItem"/>
   </commandset>
 
   <keyset>
+    <!-- XXXunf Disabled until bug 371900 is fixed. -->
     <key id="focusSearch" key="&search.commandkey;" modifiers="accel"
-         oncommand="gHeader.focusSearchBox();"/>
+         disabled="true"/>
   </keyset>
 
   <!-- main header -->
   <hbox id="header" align="center">
     <toolbarbutton id="back-btn" class="nav-button header-button" command="cmd_back"
             tooltiptext="&cmd.back.tooltip;" hidden="true" disabled="true"/>
     <toolbarbutton id="forward-btn" class="nav-button header-button" command="cmd_forward"
             tooltiptext="&cmd.forward.tooltip;" hidden="true" disabled="true"/>
@@ -513,19 +508,17 @@
                       <label class="disabled-postfix" value="&addon.disabled.postfix;"/>
                       <label class="update-postfix" value="&addon.update.postfix;"/>
                       <spacer flex="5000"/> <!-- Necessary to allow the name to wrap -->
                     </hbox>
                     <label id="detail-creator" class="creator"/>
                   </vbox>
                   <hbox id="detail-desc-container" align="start">
                     <vbox pack="center"> <!-- Necessary to work around bug 394738 -->
-                      <image id="detail-screenshot" hidden="true"
-                             onload="this.removeAttribute('loading');"
-                             onerror="this.setAttribute('loading', 'error');"/>
+                      <image id="detail-screenshot" hidden="true"/>
                     </vbox>
                     <vbox flex="1">
                       <description id="detail-desc"/>
                       <description id="detail-fulldesc"/>
                     </vbox>
                   </hbox>
                   <vbox id="detail-contributions">
                     <description id="detail-contrib-description">
--- a/toolkit/mozapps/extensions/jar.mn
+++ b/toolkit/mozapps/extensions/jar.mn
@@ -1,17 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 toolkit.jar:
 % content mozapps %content/mozapps/
 * content/mozapps/extensions/extensions.xul                     (content/extensions.xul)
   content/mozapps/extensions/extensions.css                     (content/extensions.css)
-  content/mozapps/extensions/extensions.js                      (content/extensions.js)
+* content/mozapps/extensions/extensions.js                      (content/extensions.js)
 * content/mozapps/extensions/extensions.xml                     (content/extensions.xml)
   content/mozapps/extensions/updateinfo.xsl                     (content/updateinfo.xsl)
   content/mozapps/extensions/extensions-content.js              (content/extensions-content.js)
   content/mozapps/extensions/about.xul                          (content/about.xul)
   content/mozapps/extensions/about.js                           (content/about.js)
   content/mozapps/extensions/list.xul                           (content/list.xul)
   content/mozapps/extensions/list.js                            (content/list.js)
   content/mozapps/extensions/blocklist.xul                      (content/blocklist.xul)
deleted file mode 100644
--- a/toolkit/mozapps/extensions/service/VersionCheck.php
+++ /dev/null
@@ -1,221 +0,0 @@
-<?php
-/* -*- Mode: php; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/// config bits:
-$db_server = "";
-$db_user = "";
-$db_pass = "";
-$db_name = "";
-
-// error handling
-function bail ($errstr) {
-    die("Error: " . $errstr);
-}
-
-
-// major.minor.release.build[+]
-// make sure this is a valid version
-function expandversion ($vstr) {
-    $v = explode('.', $vstr);
-
-    if ($vstr == '' || count($v) == 0 || count($v) > 4) {
-        bail ('Bogus version.');
-    }
-
-    $vlen = count($v);
-    $ret = array();
-    $hasplus = 0;
-
-    for ($i = 0; $i < 4; $i++) {
-        if ($i > $vlen-1) {
-            // this version chunk was not specified; give 0
-            $ret[] = 0;
-        } else {
-            $s = $v[$i];
-            if ($i == 3) {
-                // need to check for +
-                $slen = strlen($s);
-                if ($s{$slen-1} == '+') {
-                    $s = substr($s, 0, $slen-1);
-                    $hasplus = 1;
-                }
-            }
-
-            $ret[] = intval($s);
-        }
-    }
-
-    $ret[] = $hasplus;
-
-    return $ret;
-}
-
-function vercmp ($a, $b) {
-    if ($a == $b)
-        return 0;
-
-    $va = expandversion($a);
-    $vb = expandversion($b);
-
-    for ($i = 0; $i < 5; $i++)
-        if ($va[$i] != $vb[$i])
-            return ($vb[$i] - $va[$i]);
-
-    return 0;
-}
-
-
-//
-// These are passed in the GET string
-//
-
-if (!array_key_exists('reqVersion', $_GET))
-    bail ("Invalid request.");
-
-$reqVersion = $_GET['reqVersion'];
-
-if ($reqVersion == 1) {
-
-    if (!array_key_exists('id', $_GET) ||
-        !array_key_exists('version', $_GET) ||
-        !array_key_exists('maxAppVersion', $_GET) ||
-        !array_key_exists('appID', $_GET) ||
-        !array_key_exists('appVersion', $_GET))
-        bail ("Invalid request.");
-
-    $reqItemGuid = $_GET['id'];
-    $reqItemVersion = $_GET['version'];
-    $reqItemMaxAppVersion = $_GET['maxAppVersion'];
-    $reqTargetAppGuid = $_GET['appID'];
-    $reqTargetAppVersion = $_GET['appVersion'];
-} else {
-    // bail
-    bail ("Bad request version received");
-}
-
-// check args
-if (empty($reqItemGuid) || empty($reqItemVersion) || empty($reqTargetAppGuid)) {
-    bail ("Invalid request.");
-}
-
-mysql_connect($db_server, $db_user, $db_pass)
-    || bail ("Failed to connect to database.");
-
-mysql_select_db ($db_name)
-    || bail ("Failed to select database.");
-
-// We need to fetch two things for the database:
-// 1) The current extension version's info, for a possibly updated max version
-// 2) The latest version available, if different from the above.
-//
-// We know:
-//  - $reqItemGuid
-//  - $reqItemVersion
-//  - $reqTargetAppGuid
-//  - $reqTargetAppVersion
-//
-// We need to get:
-//  - extension GUID
-//  - extension version
-//  - extension xpi link
-//  - app ID
-//  - app min version
-//  - app max version
-
-$query = "SELECT t_main.guid AS extguid,
-                 t_version.version AS extversion,
-                 t_version.uri AS exturi,
-                 t_version.minappver AS appminver,
-                 t_version.maxappver AS appmaxver,
-                 t_applications.guid AS appguid
-          FROM t_main, t_version, t_applications
-          WHERE t_main.guid = '" . mysql_real_escape_string($reqItemGuid) . "' AND
-                t_main.id = t_version.id AND
-                t_version.appid = t_applications.appid AND
-                t_applications.guid = '" . mysql_real_escape_string($reqTargetAppGuid) . "'";
-
-$result = mysql_query ($query);
-
-if (!$result) {
-    bail ('Query error: ' . mysql_error());
-}
-
-// info for this version
-$thisVersionData = '';
-// info for highest version
-$highestVersion = '';
-$highestVersionData = '';
-
-while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
-    // is this row for the current version?
-    if ($line['extversion'] == $reqItemVersion) {
-        // if so
-        $thisVersionData = $line;
-    } else if (vercmp ($reqItemVersion, $line['extversion']) > 0) {
-        // did we already see an update with a higher version than this?
-        if ($highestVersion != '' && vercmp ($highestVersion, $line['extversion']) < 0)
-            continue;
-
-        // does this update support my current app version?
-        if (vercmp($line['appmaxver'], $reqTargetAppVersion) > 0 ||
-            vercmp($reqTargetAppVersion, $line['appminver']) > 0)
-            continue;
-
-        $highestVersion = $line['extversion'];
-        $highestVersionData = $line;
-    }
-}
-
-mysql_free_result ($result);
-
-//
-// Now to spit out the RDF.  We hand-generate because the data is pretty simple.
-//
-
-print "<?xml version=\"1.0\"?>\n";
-print "<RDF:RDF xmlns:RDF=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:em=\"http://www.mozilla.org/2004/em-rdf#\">\n\n";
-
-print "<RDF:Description about=\"urn:mozilla:extension:{$reqItemGuid}\">\n";
-
-// output list of updates (just highest and this)
-print " <em:updates><RDF:Seq>\n";
-if (!empty($thisVersionData))
-    print "  <RDF:li resource=\"urn:mozilla:extension:{$reqItemGuid}:{$thisVersionData['extversion']}\"/>\n";
-if (!empty($highestVersionData))
-    print "  <RDF:li resource=\"urn:mozilla:extension:{$reqItemGuid}:{$highestVersionData['extversion']}\"/>\n";
-print " </RDF:Seq></em:updates>\n";
-
-// output compat bits for firefox 0.9
-if (!empty($highestVersionData)) {
-    print " <em:version>{$highestVersionData['extversion']}</em:version>\n";
-    print " <em:updateLink>{$highestVersionData['exturi']}</em:updateLink>\n";
-}
-
-print "</RDF:Description>\n\n";
-
-function print_update ($data) {
-    print "<RDF:Description about=\"urn:mozilla:extension:{$reqItemGuid}:{$data['extversion']}\">\n";
-    print " <em:version>{$data['extversion']}</em:version>\n";
-    print " <em:targetApplication>\n";
-    print "  <RDF:Description>\n";
-    print "   <em:id>{$data['appguid']}</em:id>\n";
-    print "   <em:minVersion>{$data['appminver']}</em:minVersion>\n";
-    print "   <em:maxVersion>{$data['appmaxver']}</em:maxVersion>\n";
-    print "   <em:updateLink>{$data['exturi']}</em:updateLink>\n";
-    print "  </RDF:Description>\n";
-    print " </em:targetApplication>\n";
-    print "</RDF:Description>\n";
-}
-
-if (!empty($thisVersionData))
-    print_update ($thisVersionData);
-if (!empty($highestVersionData))
-    print_update ($highestVersionData);
-
-print "</RDF:RDF>\n";
-
-?>
-