Merge m-c to inbound
authorWes Kocher <wkocher@mozilla.com>
Tue, 15 Jul 2014 17:39:19 -0700
changeset 216205 ae7ad782c7125c6bc37eb731f8d65ed15f28a477
parent 216204 45e32200a0c47021b6b9f72455e220f67ebc2d65 (current diff)
parent 216124 869971ad9fd64529113755e571fc88ae2901559f (diff)
child 216206 4f65e26da8b4b511bf2ed631c65a2e370951dc65
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound
addon-sdk/source/test/test-packaging.js
modules/libpref/src/init/all.js
--- a/addon-sdk/source/lib/sdk/deprecated/unit-test.js
+++ b/addon-sdk/source/lib/sdk/deprecated/unit-test.js
@@ -446,17 +446,17 @@ TestRunner.prototype = {
     if (ms === undefined)
       ms = this.DEFAULT_PAUSE_TIMEOUT;
 
     var self = this;
 
     function tiredOfWaiting() {
       self._logTestFailed("timed out");
       if ("testMessage" in self.console) {
-        self.console.testMessage(false, false, self.test.name, "Timed out");
+        self.console.testMessage(false, false, self.test.name, "Test timed out");
       }
       else {
         self.console.error("fail:", "Timed out")
       }
       if (self.waitUntilCallback) {
         self.waitUntilCallback(true);
         self.waitUntilCallback = null;
       }
--- a/addon-sdk/source/lib/sdk/panel.js
+++ b/addon-sdk/source/lib/sdk/panel.js
@@ -68,17 +68,18 @@ let displayContract = contract({
 let panelContract = contract(merge({
   // contentStyle* / contentScript* are sharing the same validation constraints,
   // so they can be mostly reused, except for the messages.
   contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
     msg: 'The `contentStyle` option must be a string or an array of strings.'
   }),
   contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
     msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
-  })
+  }),
+  contextMenu: boolean
 }, displayContract.rules, loaderContract.rules));
 
 
 function isDisposed(panel) !views.has(panel);
 
 let panels = new WeakMap();
 let models = new WeakMap();
 let views = new WeakMap();
@@ -129,16 +130,17 @@ const Panel = Class({
   ],
   extends: WorkerHost(workerFor),
   setup: function setup(options) {
     let model = merge({
       defaultWidth: 320,
       defaultHeight: 240,
       focus: true,
       position: Object.freeze({}),
+      contextMenu: false
     }, panelContract(options));
     models.set(this, model);
 
     if (model.contentStyle || model.contentStyleFile) {
       styles.set(this, Style({
         uri: model.contentStyleFile,
         source: model.contentStyle
       }));
@@ -146,16 +148,19 @@ const Panel = Class({
 
     // Setup view
     let view = domPanel.make();
     panels.set(view, this);
     views.set(this, view);
 
     // Load panel content.
     domPanel.setURL(view, model.contentURL);
+    
+    // Allow context menu
+    domPanel.allowContextMenu(view, model.contextMenu);
 
     setupAutoHide(this);
 
     // Setup listeners.
     setListeners(this, options);
     let worker = new Worker(stripListeners(options));
     workers.set(this, worker);
 
@@ -183,17 +188,25 @@ const Panel = Class({
   get height() modelFor(this).height,
   set height(value) this.resize(this.width, value),
 
   /* Public API: Panel.focus */
   get focus() modelFor(this).focus,
 
   /* Public API: Panel.position */
   get position() modelFor(this).position,
-
+  
+  /* Public API: Panel.contextMenu */
+  get contextMenu() modelFor(this).contextMenu,
+  set contextMenu(allow) {
+    let model = modelFor(this);
+    model.contextMenu = panelContract({ contextMenu: allow }).contextMenu;
+    domPanel.allowContextMenu(viewFor(this), model.contextMenu);
+  },
+    
   get contentURL() modelFor(this).contentURL,
   set contentURL(value) {
     let model = modelFor(this);
     model.contentURL = panelContract({ contentURL: value }).contentURL;
     domPanel.setURL(viewFor(this), model.contentURL);
     // Detach worker so that messages send will be queued until it's
     // reatached once panel content is ready.
     workerFor(this).detach();
@@ -221,17 +234,18 @@ const Panel = Class({
     let anchorView = getNodeView(anchor || options.position || model.position);
 
     options = merge({
       position: model.position,
       width: model.width,
       height: model.height,
       defaultWidth: model.defaultWidth,
       defaultHeight: model.defaultHeight,
-      focus: model.focus
+      focus: model.focus,
+      contextMenu: model.contextMenu
     }, displayContract(options));
 
     if (!isDisposed(this))
       domPanel.show(view, options, anchorView);
 
     return this;
   },
 
--- a/addon-sdk/source/lib/sdk/panel/utils.js
+++ b/addon-sdk/source/lib/sdk/panel/utils.js
@@ -206,17 +206,17 @@ function shimDefaultStyle(panel) {
       if (node) node.style.padding = 0;
   });
 }
 
 function show(panel, options, anchor) {
   // Prevent the panel from getting focus when showing up
   // if focus is set to false
   panel.setAttribute("noautofocus", !options.focus);
-
+  
   let window = anchor && getOwnerBrowserWindow(anchor);
   let { document } = window ? window : getMostRecentBrowserWindow();
   attach(panel, document);
 
   open(panel, options, anchor);
 }
 exports.show = show
 
@@ -399,8 +399,18 @@ let getContentFrame = panel =>
     panel.backgroundFrame
 exports.getContentFrame = getContentFrame;
 
 function getContentDocument(panel) getContentFrame(panel).contentDocument
 exports.getContentDocument = getContentDocument;
 
 function setURL(panel, url) getContentFrame(panel).setAttribute("src", url)
 exports.setURL = setURL;
+
+function allowContextMenu(panel, allow) {
+  if(allow) {
+    panel.setAttribute("context", "contentAreaContextMenu");
+  } 
+  else {
+    panel.removeAttribute("context");
+  }
+}
+exports.allowContextMenu = allowContextMenu;
--- a/addon-sdk/source/lib/sdk/test/harness.js
+++ b/addon-sdk/source/lib/sdk/test/harness.js
@@ -13,16 +13,17 @@ const { serializeStack, parseStack  } = 
 const { setTimeout } = require('../timers');
 const { PlainTextConsole } = require("../console/plain-text");
 const { when: unload } = require("../system/unload");
 const { format, fromException }  = require("../console/traceback");
 const system = require("../system");
 const memory = require('../deprecated/memory');
 const { gc: gcPromise } = require('./memory');
 const { defer } = require('../core/promise');
+const { extend } = require('../core/heritage');
 
 // Trick manifest builder to make it think we need these modules ?
 const unit = require("../deprecated/unit-test");
 const test = require("../../test");
 const url = require("../url");
 
 function emptyPromise() {
   let { promise, resolve } = defer();
@@ -448,35 +449,35 @@ var consoleListener = {
     var pointless = [err for each (err in POINTLESS_ERRORS)
                          if (message.indexOf(err) >= 0)];
     if (pointless.length == 0 && message)
       testConsole.log(message);
   }
 };
 
 function TestRunnerConsole(base, options) {
-  this.__proto__ = {
+  let proto = extend(base, {
     errorsLogged: 0,
     warn: function warn() {
       this.errorsLogged++;
       base.warn.apply(base, arguments);
     },
     error: function error() {
       this.errorsLogged++;
       base.error.apply(base, arguments);
     },
     info: function info(first) {
       if (options.verbose)
         base.info.apply(base, arguments);
       else
         if (first == "pass:")
           print(".");
     },
-    __proto__: base
-  };
+  });
+  return Object.create(proto);
 }
 
 function stringify(arg) {
   try {
     return String(arg);
   }
   catch(ex) {
     return "<toString() error>";
--- a/addon-sdk/source/lib/sdk/test/loader.js
+++ b/addon-sdk/source/lib/sdk/test/loader.js
@@ -14,28 +14,30 @@ let defaultGlobals = override(require('.
   console: console
 });
 
 function CustomLoader(module, globals, packaging, overrides={}) {
   let options = packaging || require("@loader/options");
   options = override(options, {
     id: overrides.id || options.id,
     globals: override(defaultGlobals, globals || {}),
-    modules: override(options.modules || {}, {
+    modules: override(override(options.modules || {}, overrides.modules || {}), {
       'sdk/addon/window': addonWindow
     })
   });
 
   let loaderModule = options.isNative ? '../../toolkit/loader' : '../loader/cuddlefish';
   let { Loader } = require(loaderModule);
   let loader = Loader(options);
   let wrapper = Object.create(loader, descriptor({
     require: Require(loader, module),
     sandbox: function(id) {
       let requirement = loader.resolve(id, module.id);
+      if (!requirement)
+        requirement = id;
       let uri = resolveURI(requirement, loader.mapping);
       return loader.sandboxes[uri];
     },
     unload: function(reason) {
       unload(loader, reason);
     }
   }));
   ensure(wrapper);
@@ -68,23 +70,23 @@ exports.LoaderWithHookedConsole = functi
     messages.push({ type: type, msg: msg, innerID: innerID });
     if (callback)
       callback(type, msg, innerID);
   }
 
   return {
     loader: CustomLoader(module, {
       console: new HookedPlainTextConsole(hook, null, null)
-    }, override(require("@loader/options"), {
+    }, null, {
       modules: {
         'sdk/console/plain-text': {
           PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
         }
       }
-    })),
+    }),
     messages: messages
   };
 }
 
 // Same than LoaderWithHookedConsole with lower level, instead we get what is
 // actually printed to the command line console
 exports.LoaderWithHookedConsole2 = function (module, callback) {
   let messages = [];
@@ -107,16 +109,16 @@ exports.LoaderWithFilteredConsole = func
   function hook(type, innerID, msg) {
     if (callback && callback(type, msg, innerID) == false)
       return;
     console[type](msg);
   }
 
   return CustomLoader(module, {
     console: new HookedPlainTextConsole(hook, null, null)
-  }, override(require("@loader/options"), {
+  }, null, {
     modules: {
       'sdk/console/plain-text': {
         PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
       }
     }
-  }));
+  });
 }
--- a/addon-sdk/source/lib/toolkit/loader.js
+++ b/addon-sdk/source/lib/toolkit/loader.js
@@ -709,17 +709,18 @@ const Loader = iced(function Loader(opti
     metadata, sharedGlobal, sharedGlobalBlacklist
   } = override({
     paths: {},
     modules: {},
     globals: {
       console: console
     },
     resolve: options.isNative ?
-      exports.nodeResolve :
+      // Make the returned resolve function have the same signature
+      (id, requirer) => exports.nodeResolve(id, requirer, { rootURI: rootURI }) :
       exports.resolve,
     sharedGlobalBlacklist: ["sdk/indexed-db"]
   }, options);
 
   // We create an identity object that will be dispatched on an unload
   // event as subject. This way unload listeners will be able to assert
   // which loader is unloaded. Please note that we intentionally don't
   // use `loader` as subject to prevent a loader access leakage through
--- a/addon-sdk/source/python-lib/cuddlefish/__init__.py
+++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py
@@ -229,16 +229,21 @@ parser_groups = (
         (("", "--output-file",), dict(dest="output_file",
                                       help="Where to put the finished .xpi",
                                       default=None,
                                       cmds=['xpi'])),
         (("", "--manifest-overload",), dict(dest="manifest_overload",
                                       help="JSON file to overload package.json properties",
                                       default=None,
                                       cmds=['xpi'])),
+        (("", "--abort-on-missing-module",), dict(dest="abort_on_missing",
+                                      help="Abort if required module is missing",
+                                      action="store_true",
+                                      default=False,
+                                      cmds=['test', 'run', 'xpi', 'testpkgs'])),
         ]
      ),
 
     ("Internal Command-Specific Options", [
         (("", "--addons",), dict(dest="addons",
                                  help=("paths of addons to install, "
                                        "comma-separated"),
                                  metavar=None,
@@ -646,17 +651,18 @@ def run(arguments=sys.argv[1:], target_c
     if options.manifest_overload:
         for k, v in packaging.load_json_file(options.manifest_overload).items():
             target_cfg[k] = v
 
     # At this point, we're either building an XPI or running Jetpack code in
     # a Mozilla application (which includes running tests).
 
     use_main = False
-    inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory']
+    inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory',
+                         'abort_on_missing']
     enforce_timeouts = False
 
     if command == "xpi":
         use_main = True
     elif command == "test":
         if 'tests' not in target_cfg:
             target_cfg['tests'] = []
         inherited_options.extend(['iterations', 'filter', 'profileMemory',
@@ -741,19 +747,19 @@ def run(arguments=sys.argv[1:], target_c
     loader_modules = [("addon-sdk", "lib", "sdk/loader/cuddlefish", cuddlefish_js_path)]
     scan_tests = command == "test"
     test_filter_re = None
     if scan_tests and options.filter:
         test_filter_re = options.filter
         if ":" in options.filter:
             test_filter_re = options.filter.split(":")[0]
     try:
-        manifest = build_manifest(target_cfg, pkg_cfg, deps,
-                                  scan_tests, test_filter_re,
-                                  loader_modules)
+        manifest = build_manifest(target_cfg, pkg_cfg, deps, scan_tests,
+                                  test_filter_re, loader_modules,
+                                  abort_on_missing=options.abort_on_missing)
     except ModuleNotFoundError, e:
         print str(e)
         sys.exit(1)
     except BadChromeMarkerError, e:
         # An error had already been displayed on stderr in manifest code
         sys.exit(1)
     used_deps = manifest.get_used_packages()
     if command == "test":
@@ -783,16 +789,20 @@ def run(arguments=sys.argv[1:], target_c
     # When cfx is run from sdk root directory, we will strip sdk modules and
     # override them with local modules.
     # So that integration tools will continue to work and use local modules
     if os.getcwd() == env_root:
         options.bundle_sdk = True
         options.force_use_bundled_sdk = False
         options.overload_modules = True
 
+    if options.pkgdir == env_root:
+        options.bundle_sdk = True
+        options.overload_modules = True
+
     extra_environment = {}
     if command == "test":
         # This should be contained in the test runner package.
         harness_options['main'] = 'sdk/test/runner'
         harness_options['mainPath'] = 'sdk/test/runner'
     else:
         harness_options['main'] = target_cfg.get('main')
         harness_options['mainPath'] = manifest.top_path
--- a/addon-sdk/source/python-lib/cuddlefish/manifest.py
+++ b/addon-sdk/source/python-lib/cuddlefish/manifest.py
@@ -176,28 +176,29 @@ class ModuleInfo:
     def __repr__(self):
         return "ModuleInfo [%s %s %s] (%s, %s)" % (self.package.name,
                                                    self.section,
                                                    self.name,
                                                    self.js, self.docs)
 
 class ManifestBuilder:
     def __init__(self, target_cfg, pkg_cfg, deps, extra_modules,
-                 stderr=sys.stderr):
+                 stderr=sys.stderr, abort_on_missing=False):
         self.manifest = {} # maps (package,section,module) to ManifestEntry
         self.target_cfg = target_cfg # the entry point
         self.pkg_cfg = pkg_cfg # all known packages
         self.deps = deps # list of package names to search
         self.used_packagenames = set()
         self.stderr = stderr
         self.extra_modules = extra_modules
         self.modules = {} # maps ModuleInfo to URI in self.manifest
         self.datamaps = {} # maps package name to DataMap instance
         self.files = [] # maps manifest index to (absfn,absfn) js/docs pair
         self.test_modules = [] # for runtime
+        self.abort_on_missing = abort_on_missing # cfx eol
 
     def build(self, scan_tests, test_filter_re):
         """
         process the top module, which recurses to process everything it reaches
         """
         if "main" in self.target_cfg:
             top_mi = self.find_top(self.target_cfg)
             top_me = self.process_module(top_mi)
@@ -411,16 +412,22 @@ class ManifestBuilder:
                 looked_in = [] # populated by subroutines
                 them_me = self.find_req_for(mi, reqname, looked_in, locations)
                 if them_me is None:
                     if mi.section == "tests":
                         # tolerate missing modules in tests, because
                         # test-securable-module.js, and the modules/red.js
                         # that it imports, both do that intentionally
                         continue
+                    if not self.abort_on_missing:
+                        # print a warning, but tolerate missing modules
+                        # unless cfx --abort-on-missing-module flag was set
+                        print >>self.stderr, "Warning: missing module: %s" % reqname
+                        me.add_requirement(reqname, reqname)
+                        continue
                     lineno = locations.get(reqname) # None means define()
                     if lineno is None:
                         reqtype = "define"
                     else:
                         reqtype = "require"
                     err = ModuleNotFoundError(reqtype, reqname,
                                               mi.js, lineno, looked_in)
                     raise err
@@ -628,17 +635,17 @@ class ManifestBuilder:
                     maybe_docs = os.path.join(pkg.root_dir, "docs",
                                               basename+".md")
                     if section == "lib" and os.path.exists(maybe_docs):
                         docs = maybe_docs
                     return ModuleInfo(pkg, section, name, js, docs)
         return None
 
 def build_manifest(target_cfg, pkg_cfg, deps, scan_tests,
-                   test_filter_re=None, extra_modules=[]):
+                   test_filter_re=None, extra_modules=[], abort_on_missing=False):
     """
     Perform recursive dependency analysis starting from entry_point,
     building up a manifest of modules that need to be included in the XPI.
     Each entry will map require() names to the URL of the module that will
     be used to satisfy that dependency. The manifest will be used by the
     runtime's require() code.
 
     This returns a ManifestBuilder object, with two public methods. The
@@ -654,17 +661,18 @@ def build_manifest(target_cfg, pkg_cfg, 
 
      * local disk name
      * name in the XPI
 
     note: we don't build the XPI here, but our manifest is passed to the
     code which does, so it knows what to copy into the XPI.
     """
 
-    mxt = ManifestBuilder(target_cfg, pkg_cfg, deps, extra_modules)
+    mxt = ManifestBuilder(target_cfg, pkg_cfg, deps, extra_modules,
+                          abort_on_missing=abort_on_missing)
     mxt.build(scan_tests, test_filter_re)
     return mxt
 
 
 
 COMMENT_PREFIXES = ["//", "/*", "*", "dump("]
 
 REQUIRE_RE = r"(?<![\'\"])require\s*\(\s*[\'\"]([^\'\"]+?)[\'\"]\s*\)"
--- a/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py
+++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py
@@ -43,30 +43,46 @@ class Basic(unittest.TestCase):
         # target_cfg.dependencies is not provided, so we'll search through
         # all known packages (everything in 'deps').
         m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False)
         m = m.get_harness_options_manifest(False)
 
         def assertReqIs(modname, reqname, path):
             reqs = m["one/%s" % modname]["requirements"]
             self.failUnlessEqual(reqs[reqname], path)
+
         assertReqIs("main", "sdk/panel", "sdk/panel")
         assertReqIs("main", "two.js", "one/two")
         assertReqIs("main", "./two", "one/two")
         assertReqIs("main", "sdk/tabs.js", "sdk/tabs")
         assertReqIs("main", "./subdir/three", "one/subdir/three")
         assertReqIs("two", "main", "one/main")
         assertReqIs("subdir/three", "../main", "one/main")
 
         target_cfg.dependencies = []
+
+        try:
+            # this should now work, as we ignore missing modules by default
+            m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False)
+            m = m.get_harness_options_manifest(False)
+
+            assertReqIs("main", "sdk/panel", "sdk/panel")
+            # note that with "addon-sdk" dependency present,
+            # "sdk/tabs.js" mapped to "sdk/tabs", but without,
+            # we just get the default (identity) mapping
+            assertReqIs("main", "sdk/tabs.js", "sdk/tabs.js")
+        except Exception, e:
+            self.fail("Must not throw from build_manifest() if modules are missing")
+
         # now, because .dependencies *is* provided, we won't search 'deps',
-        # so we'll get a link error
+        # and stop_on_missing is True, we'll get a link error
         self.assertRaises(manifest.ModuleNotFoundError,
                           manifest.build_manifest,
-                          target_cfg, pkg_cfg, deps, scan_tests=False)
+                          target_cfg, pkg_cfg, deps, scan_tests=False,
+                          abort_on_missing=True)
 
     def test_main_in_deps(self):
         target_cfg = self.get_pkg("three")
         package_path = [get_linker_files_dir("three-deps")]
         pkg_cfg = packaging.build_config(ROOT, target_cfg,
                                          packagepath=package_path)
         deps = packaging.get_deps_for_targets(pkg_cfg,
                                               [target_cfg.name, "addon-sdk"])
--- a/addon-sdk/source/test/addons/e10s-tabs/lib/main.js
+++ b/addon-sdk/source/test/addons/e10s-tabs/lib/main.js
@@ -1,13 +1,19 @@
 /* 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 { merge } = require('sdk/util/object');
+const { get } = require('sdk/preferences/service');
 
 merge(module.exports, require('./test-tab'));
 merge(module.exports, require('./test-tab-events'));
 merge(module.exports, require('./test-tab-observer'));
 merge(module.exports, require('./test-tab-utils'));
 
+// e10s tests should not ride the train to aurora
+if (get('app.update.channel') !== 'nightly') {
+  module.exports = {};
+}
+
 require('sdk/test/runner').runTestsFromModule(module);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/packaging/main.js
@@ -0,0 +1,46 @@
+/* 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";
+
+var options = require("@loader/options");
+
+exports.testPackaging = function(assert) {
+  assert.equal(options.metadata.description,
+                   "Add-on development made easy.",
+                   "packaging metadata should be available");
+  try {
+    options.metadata.description = 'new description';
+    assert.fail('should not have been able to set options.metadata property');
+  }
+  catch (e) {}
+
+  assert.equal(options.metadata.description,
+                   "Add-on development made easy.",
+                   "packaging metadata should be frozen");
+
+  assert.equal(options.metadata.permissions['private-browsing'], undefined,
+                   "private browsing metadata should be undefined");
+  assert.equal(options.metadata['private-browsing'], undefined,
+                   "private browsing metadata should be be frozen");
+  assert.equal(options['private-browsing'], undefined,
+                   "private browsing metadata should be be frozen");
+
+  try {
+    options.metadata['private-browsing'] = true;
+    assert.fail('should not have been able to set options.metadata property');
+  }
+  catch(e) {}
+  assert.equal(options.metadata['private-browsing'], undefined,
+                   "private browsing metadata should be be frozen");
+
+  try {
+    options.metadata.permissions['private-browsing'] = true;
+    assert.fail('should not have been able to set options.metadata.permissions property');
+  }
+  catch (e) {}
+  assert.equal(options.metadata.permissions['private-browsing'], undefined,
+                   "private browsing metadata should be be frozen");
+};
+
+require("sdk/test/runner").runTestsFromModule(module);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/packaging/package.json
@@ -0,0 +1,4 @@
+{
+  "id": "test-packaging",
+  "description": "Add-on development made easy."
+}
--- a/addon-sdk/source/test/addons/self/main.js
+++ b/addon-sdk/source/test/addons/self/main.js
@@ -12,9 +12,13 @@ exports["test self.data.load"] = assert 
                "# hello world",
                "paths work");
 
   assert.equal(self.data.load("./data.md").trim(),
                "# hello world",
                "relative paths work");
 };
 
+exports["test self.id"] = assert => {
+  assert.equal(self.id, "test-self@jetpack", "self.id should be correct.");
+};
+
 require("sdk/test/runner").runTestsFromModule(module);
--- a/addon-sdk/source/test/event/helpers.js
+++ b/addon-sdk/source/test/event/helpers.js
@@ -1,16 +1,65 @@
 /* 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 { on, once, off, emit, count } = require("sdk/event/core");
 
+const { setImmediate, setTimeout } = require("sdk/timers");
+const { defer } = require("sdk/core/promise");
+
+/**
+ * Utility function that returns a promise once the specified event's `type`
+ * is emitted on the given `target`, or the delay specified is passed.
+ *
+ * @param {Object|Number} [target]
+ *    The delay to wait, or the object that receives the event.
+ *    If not given, the function returns a promise that will be resolved
+ *    as soon as possible.
+ * @param {String} [type]
+ *    A string representing the event type to waiting for.
+ * @param {Boolean} [capture]
+ *    If `true`, `capture` indicates that the user wishes to initiate capture.
+ *
+ * @returns {Promise}
+ *    A promise resolved once the delay given is passed, or the object
+ *    receives the event specified
+ */
+const wait = (target, type, capture) => {
+  let { promise, resolve, reject } = defer();
+
+  if (!arguments.length) {
+    setImmediate(resolve);
+  }
+  else if (typeof(target) === "number") {
+    setTimeout(resolve, target);
+  }
+  else if (typeof(target.once) === "function") {
+    target.once(type, resolve);
+  }
+  else if (typeof(target.addEventListener) === "function") {
+    target.addEventListener(type, function listener(...args) {
+      this.removeEventListener(type, listener, capture);
+      resolve(...args);
+    }, capture);
+  }
+  else if (typeof(target) === "object" && target !== null) {
+    once(target, type, resolve);
+  }
+  else {
+    reject('Invalid target given.');
+  }
+
+  return promise;
+};
+exports.wait = wait;
+
 function scenario(setup) {
   return function(unit) {
     return function(assert) {
       let actual = [];
       let input = {};
       unit(input, function(output, events, expected, message) {
         let result = setup(output, expected, actual);
 
@@ -51,9 +100,9 @@ exports.ignoreNew = scenario(function(ou
 exports.FIFO = scenario(function(target, expected, actual) {
   on(target, "data", function($) actual.push($ + "#1"));
   on(target, "data", function($) actual.push($ + "#2"));
   on(target, "data", function($) actual.push($ + "#3"));
 
   return expected.reduce(function(result, value) {
     return result.concat(value + "#1", value + "#2", value + "#3");
   }, []);
-});
+});
\ No newline at end of file
--- a/addon-sdk/source/test/test-content-script.js
+++ b/addon-sdk/source/test/test-content-script.js
@@ -131,16 +131,17 @@ exports["test Create Proxy Test With Eve
   worker.port.on("foo", function () {
     assert.pass("You can use events");
     // And terminate your test with helper.done:
     helper.done();
   });
 
 });
 
+/* Disabled due to bug 1038432
 // Bug 714778: There was some issue around `toString` functions
 //             that ended up being shared between content scripts
 exports["test Shared To String Proxies"] = createProxyTest("", function(helper) {
 
   let worker = helper.createWorker(
     'new ' + function ContentScriptScope() {
       // We ensure that `toString` can't be modified so that nothing could
       // leak to/from the document and between content scripts
@@ -160,17 +161,17 @@ exports["test Shared To String Proxies"]
                "document.location.toString is different for each content script");
         assert(document.location.toString() == "data:text/html;charset=utf-8,",
                "Second document.location.toString()");
         done();
       }
     );
   });
 });
-
+*/
 
 // Ensure that postMessage is working correctly across documents with an iframe
 let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';
 exports["test postMessage"] = createProxyTest(html, function (helper, assert) {
   let ifWindow = helper.xrayWindow.document.getElementById("iframe").contentWindow;
   // Listen without proxies, to check that it will work in regular case
   // simulate listening from a web document.
   ifWindow.addEventListener("message", function listener(event) {
deleted file mode 100644
--- a/addon-sdk/source/test/test-packaging.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/* 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";
-
-var options = require("@loader/options");
-
-exports.testPackaging = function(assert) {
-  assert.equal(options.metadata.description,
-                   "Add-on development made easy.",
-                   "packaging metadata should be available");
-  try {
-    options.metadata.description = 'new description';
-    assert.fail('should not have been able to set options.metadata property');
-  }
-  catch (e) {}
-
-  assert.equal(options.metadata.description,
-                   "Add-on development made easy.",
-                   "packaging metadata should be frozen");
-
-  assert.equal(options.metadata.permissions['private-browsing'], undefined,
-                   "private browsing metadata should be undefined");
-  assert.equal(options.metadata['private-browsing'], undefined,
-                   "private browsing metadata should be be frozen");
-  assert.equal(options['private-browsing'], undefined,
-                   "private browsing metadata should be be frozen");
-
-  try {
-    options.metadata['private-browsing'] = true;
-    assert.fail('should not have been able to set options.metadata property');
-  }
-  catch(e) {}
-  assert.equal(options.metadata['private-browsing'], undefined,
-                   "private browsing metadata should be be frozen");
-
-  try {
-    options.metadata.permissions['private-browsing'] = true;
-    assert.fail('should not have been able to set options.metadata.permissions property');
-  }
-  catch (e) {}
-  assert.equal(options.metadata.permissions['private-browsing'], undefined,
-                   "private browsing metadata should be be frozen");
-};
-
-require('sdk/test').run(exports);
--- a/addon-sdk/source/test/test-panel.js
+++ b/addon-sdk/source/test/test-panel.js
@@ -7,26 +7,28 @@ module.metadata = {
   'engines': {
     'Firefox': '*'
   }
 };
 
 const { Cc, Ci } = require("chrome");
 const { Loader } = require('sdk/test/loader');
 const { LoaderWithHookedConsole } = require("sdk/test/loader");
-const timer = require("sdk/timers");
+const { setTimeout } = require("sdk/timers");
 const self = require('sdk/self');
 const { open, close, focus, ready } = require('sdk/window/helpers');
 const { isPrivate } = require('sdk/private-browsing');
 const { isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils');
 const { defer, all } = require('sdk/core/promise');
 const { getMostRecentBrowserWindow } = require('sdk/window/utils');
 const { getWindow } = require('sdk/panel/window');
 const { pb } = require('./private-browsing/helper');
 const { URL } = require('sdk/url');
+const { wait } = require('./event/helpers');
+
 const fixtures = require('./fixtures')
 
 const SVG_URL = fixtures.url('mofo_logo.SVG');
 const CSS_URL = fixtures.url('css-include-file.css');
 
 const Isolate = fn => '(' + fn + ')()';
 
 function ignorePassingDOMNodeWarning(type, message) {
@@ -539,17 +541,17 @@ function makeEventOrderTest(options) {
 
     let panel = Panel({ contentURL: "about:buildconfig" });
 
     function expect(event, cb) {
       expectedEvents.push(event);
       panel.on(event, function() {
         assert.equal(event, expectedEvents.shift());
         if (cb)
-          timer.setTimeout(cb, 1);
+          setTimeout(cb, 1);
       });
       return {then: expect};
     }
 
     options.test(assert, done, expect, panel);
   }
 }
 
@@ -1051,16 +1053,146 @@ exports['test panel CSS list'] = functio
         loader.unload();
       }).then(done, assert.fail);
     }
   });
 
   panel.show();
 };
 
+exports['test panel contextmenu validation'] = function(assert) {
+  const loader = Loader(module);
+  const { Panel } = loader.require('sdk/panel');
+
+  let panel = Panel({});
+
+  assert.equal(panel.contextMenu, false,
+    'contextMenu option is `false` by default');
+
+  panel.destroy();
+
+  panel = Panel({
+    contextMenu: false
+  });
+
+  assert.equal(panel.contextMenu, false,
+    'contextMenu option is `false`');
+
+  panel.contextMenu = true;
+
+  assert.equal(panel.contextMenu, true,
+    'contextMenu option accepts boolean values');
+
+  panel.destroy();
+
+  panel = Panel({
+    contextMenu: true
+  });
+
+  assert.equal(panel.contextMenu, true,
+    'contextMenu option is `true`');
+
+  panel.contextMenu = false;
+
+  assert.equal(panel.contextMenu, false,
+    'contextMenu option accepts boolean values');
+  
+  assert.throws(() =>
+    Panel({contextMenu: 1}),
+    /The option "contextMenu" must be one of the following types: boolean, undefined, null/,
+    'contextMenu only accepts boolean or nil values');
+
+  panel = Panel();
+
+  assert.throws(() =>
+    panel.contextMenu = 1,
+    /The option "contextMenu" must be one of the following types: boolean, undefined, null/,
+    'contextMenu only accepts boolean or nil values');
+
+  loader.unload();
+}
+
+exports['test panel contextmenu enabled'] = function*(assert) {
+  const loader = Loader(module);
+  const { Panel } = loader.require('sdk/panel');
+  const { getActiveView } = loader.require('sdk/view/core');
+  const { getContentDocument } = loader.require('sdk/panel/utils');
+
+  let contextmenu = getMostRecentBrowserWindow().
+                      document.getElementById("contentAreaContextMenu");
+
+  let panel = Panel({contextMenu: true});
+
+  panel.show();
+
+  yield wait(panel, 'show');
+
+  let view = getActiveView(panel);
+  let window = getContentDocument(view).defaultView;
+
+  let { sendMouseEvent } = window.QueryInterface(Ci.nsIInterfaceRequestor).
+                                    getInterface(Ci.nsIDOMWindowUtils);
+
+  yield ready(window);
+
+  assert.equal(contextmenu.state, 'closed',
+    'contextmenu must be closed');
+
+  sendMouseEvent('contextmenu', 20, 20, 2, 1, 0);
+
+  yield wait(contextmenu, 'popupshown');
+
+  assert.equal(contextmenu.state, 'open',
+    'contextmenu is opened');
+
+  contextmenu.hidePopup();
+
+  loader.unload();
+}
+
+exports['test panel contextmenu disabled'] = function*(assert) {
+  const loader = Loader(module);
+  const { Panel } = loader.require('sdk/panel');
+  const { getActiveView } = loader.require('sdk/view/core');
+  const { getContentDocument } = loader.require('sdk/panel/utils');
+
+  let contextmenu = getMostRecentBrowserWindow().
+                      document.getElementById("contentAreaContextMenu");
+  let listener = () => assert.fail('popupshown should never be called');
+
+  let panel = Panel();
+
+  panel.show();
+
+  yield wait(panel, 'show');
+
+  let view = getActiveView(panel);
+  let window = getContentDocument(view).defaultView;
+
+  let { sendMouseEvent } = window.QueryInterface(Ci.nsIInterfaceRequestor).
+                                    getInterface(Ci.nsIDOMWindowUtils);
+
+  yield ready(window);
+
+  assert.equal(contextmenu.state, 'closed',
+    'contextmenu must be closed');
+  
+  sendMouseEvent('contextmenu', 20, 20, 2, 1, 0);
+
+  contextmenu.addEventListener('popupshown', listener);
+
+  yield wait(1000);
+
+  contextmenu.removeEventListener('popupshown', listener);
+
+  assert.equal(contextmenu.state, 'closed',
+    'contextmenu was never open');
+
+  loader.unload();
+}
 
 if (isWindowPBSupported) {
   exports.testGetWindow = function(assert, done) {
     let activeWindow = getMostRecentBrowserWindow();
     open(null, { features: {
       toolbar: true,
       chrome: true,
       private: true
--- a/addon-sdk/source/test/test-plain-text-console.js
+++ b/addon-sdk/source/test/test-plain-text-console.js
@@ -229,16 +229,18 @@ exports.testPlainTextConsoleBoundMethods
 
   restorePrefs();
 };
 
 exports.testConsoleInnerID = function(assert) {
   let Console = require("sdk/console/plain-text").PlainTextConsole;
   let { log, info, warn, error, debug, exception, trace } = new Console(function() {}, "test ID");
 
+  prefs.set(SDK_LOG_LEVEL_PREF, "all");
+
   let messages = [];
   function onMessage({ subject }) {
     let message = subject.wrappedJSObject;
     messages.push({ msg: message.arguments[0], type: message.level, innerID: message.innerID });
   }
 
   const system = require("sdk/system/events");
   system.on("console-api-log-event", onMessage);
@@ -248,16 +250,18 @@ exports.testConsoleInnerID = function(as
   error("Test error");
 
   assert.equal(messages.length, 3, "Should see 3 log events");
   assert.deepEqual(messages[0], { msg: "Test log", type: "log", innerID: "test ID" }, "Should see the right event");
   assert.deepEqual(messages[1], { msg: "Test warning", type: "warn", innerID: "test ID" }, "Should see the right event");
   assert.deepEqual(messages[2], { msg: "Test error", type: "error", innerID: "test ID" }, "Should see the right event");
 
   system.off("console-api-log-event", onMessage);
+
+  restorePrefs();
 };
 
 function restorePrefs() {
   if (HAS_ORIGINAL_ADDON_LOG_LEVEL)
     prefs.set(ADDON_LOG_LEVEL_PREF, ORIGINAL_ADDON_LOG_LEVEL);
   else
     prefs.reset(ADDON_LOG_LEVEL_PREF);
 
--- a/addon-sdk/source/test/test-require.js
+++ b/addon-sdk/source/test/test-require.js
@@ -46,18 +46,22 @@ function checkError (assert, name, e) {
   else {
     assert.equal(msg.indexOf('Error: you must provide a module name when calling require() from '), 0);
     assert.ok(msg.indexOf("test-require") !== -1, msg);
   }
 
   // we'd also like to assert that the right filename
   // and linenumber is in the stacktrace
   let tb = traceback.fromException(e);
-  // Get the second to last frame, as the last frame is inside
-  // toolkit/loader
-  let lastFrame = tb[tb.length-2];
+
+  // The last frame may be inside a loader
+  let lastFrame = tb[tb.length - 1];
+  if (lastFrame.fileName.indexOf("toolkit/loader.js") !== -1 ||
+      lastFrame.fileName.indexOf("sdk/loader/cuddlefish.js") !== -1)
+    lastFrame = tb[tb.length - 2];
+
   assert.ok(lastFrame.fileName.indexOf("test-require.js") !== -1,
                           'Filename found in stacktrace');
   assert.equal(lastFrame.lineNumber, REQUIRE_LINE_NO,
                           'stacktrace has correct line number');
 }
 
 require('test').run(exports);
--- a/addon-sdk/source/test/test-self.js
+++ b/addon-sdk/source/test/test-self.js
@@ -35,37 +35,16 @@ exports.testSelf = function(assert) {
                                                             : "startup";
   assert.equal(self.loadReason, testLoadReason,
                "self.loadReason is either startup or install on test runs");
 
   assert.equal(self.isPrivateBrowsingSupported, false,
                'usePrivateBrowsing property is false by default');
 };
 
-exports.testSelfID = function(assert, done) {
-  var self = require("sdk/self");
-  // We can't assert anything about the ID inside the unit test right now,
-  // because the ID we get depends upon how the test was invoked. The idea
-  // is that it is supposed to come from the main top-level package's
-  // package.json file, from the "id" key.
-  assert.equal(typeof(self.id), "string", "self.id is a string");
-  assert.ok(self.id.length > 0);
-
-  AddonManager.getAddonByID(self.id, function(addon) {
-    if (!addon) {
-      assert.fail("did not find addon with self.id");
-    }
-    else {
-      assert.pass("found addon with self.id");
-    }
-
-    done();
-  });
-}
-
 exports.testSelfHandlesLackingLoaderOptions = function (assert) {
   let root = module.uri.substr(0, module.uri.lastIndexOf('/'));
   let uri = root + '/fixtures/loader/self/';
   let sdkPath = loaderOptions.paths[''] + 'sdk';
   let loader = Loader({ paths: { '': uri, 'sdk': sdkPath }});
   let program = main(loader, 'main');
   let self = program.self;
   assert.pass("No errors thrown when including sdk/self without loader options");
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,23 +14,23 @@
   <!--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="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bf9aaf39dd5a6491925a022db167c460f8207d34"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,20 +12,20 @@
   <!--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="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- 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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
@@ -123,15 +123,15 @@
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
   <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/>
   <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/>
   <!-- Emulator specific things -->
   <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="0e31f35a2a77301e91baa8a237aa9e9fa4076084"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c61e5f15fd62888f2c33d7d542b5b65c38102e8b"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="749880f1942620ee1caac7c248f7cfe33f56203d"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="832f4acaf481a19031e479a40b03d9ce5370ddee"/>
   <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="8400a1a850d19f28137880b31582efa3416223c3"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
   <project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
 </manifest>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--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="276ce45e78b09c4a4ee643646f691d22804754c1">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="ac6eb97a37035c09fb5ede0852f0881e9aadf9ad"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="737f591c5f95477148d26602c7be56cbea0cdeb9"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="51da9b1981be481b92a59a826d4d78dc73d0989a"/>
   <project name="device/common" path="device/common" revision="798a3664597e6041985feab9aef42e98d458bc3d"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,23 +14,23 @@
   <!--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="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bf9aaf39dd5a6491925a022db167c460f8207d34"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/flame/releng-flame.tt
+++ b/b2g/config/flame/releng-flame.tt
@@ -1,7 +1,7 @@
 [
-{"size": 159142772,
-"digest": "96b10af00afc6f13e556153790373ed938b7c04c9c393c1c43fa782038f37b66fa3b47578f0cf08818b8b358cc7de777ac4b15cb26d169b27d7b9aa79233d75d",
+{"size": 149922032,
+"digest": "8d1a71552ffee561e93b5b3f1bb47866592ab958f908007c75561156430eb1b85a265bfc4dc2038e58dda0264daa9854877a84ef3b591c9ac2f1ab97c098e61e",
 "filename": "backup-flame.tar.xz",
 "algorithm": "sha512"
 }
 ]
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,20 +12,20 @@
   <!--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="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "623b1441cb54a3105b8212589aaf2aa77d537aef", 
+    "revision": "d329fe1f9c94cc3e17bde84a0d7ea3b3ed5242fb", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,22 +12,22 @@
   <!--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="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
   <project name="platform/development" path="development" revision="2460485184bc8535440bb63876d4e63ec1b4770c"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- 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="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <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/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,20 +12,20 @@
   <!--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="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- 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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,22 +12,22 @@
   <!--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="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <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="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="e0a9ac010df3afaa47ba107192c05ac8b5516435"/>
   <project name="platform/development" path="development" revision="a384622f5fcb1d2bebb9102591ff7ae91fe8ed2d"/>
   <project name="device/common" path="device/common" revision="7c65ea240157763b8ded6154a17d3c033167afb7"/>
   <project name="device/sample" path="device/sample" revision="c328f3d4409db801628861baa8d279fb8855892f"/>
--- a/content/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/content/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -17,50 +17,44 @@
 #include "ImageContainer.h"
 #include "Layers.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIDOMDocument.h"
 #include "nsITabSource.h"
 #include "VideoUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIPrefService.h"
+#include "MediaTrackConstraints.h"
 
 namespace mozilla {
 
 using namespace mozilla::gfx;
+using dom::ConstrainLongRange;
 
 NS_IMPL_ISUPPORTS(MediaEngineTabVideoSource, nsIDOMEventListener, nsITimerCallback)
 
 MediaEngineTabVideoSource::MediaEngineTabVideoSource()
 : mMonitor("MediaEngineTabVideoSource")
 {
 }
 
 nsresult
 MediaEngineTabVideoSource::StartRunnable::Run()
 {
   mVideoSource->Draw();
-  nsCOMPtr<nsPIDOMWindow> privateDOMWindow = do_QueryInterface(mVideoSource->mWindow);
-  if (privateDOMWindow) {
-    privateDOMWindow->GetChromeEventHandler()->AddEventListener(NS_LITERAL_STRING("MozAfterPaint"), mVideoSource, false);
-  } else {
-    mVideoSource->mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
-    mVideoSource->mTimer->InitWithCallback(mVideoSource, mVideoSource->mTimePerFrame, nsITimer:: TYPE_REPEATING_SLACK);
-  }
+  mVideoSource->mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+  mVideoSource->mTimer->InitWithCallback(mVideoSource, mVideoSource->mTimePerFrame, nsITimer:: TYPE_REPEATING_SLACK);
   mVideoSource->mTabSource->NotifyStreamStart(mVideoSource->mWindow);
   return NS_OK;
 }
 
 nsresult
 MediaEngineTabVideoSource::StopRunnable::Run()
 {
   nsCOMPtr<nsPIDOMWindow> privateDOMWindow = do_QueryInterface(mVideoSource->mWindow);
-  if (privateDOMWindow && privateDOMWindow->GetChromeEventHandler()) {
-    privateDOMWindow->GetChromeEventHandler()->RemoveEventListener(NS_LITERAL_STRING("MozAfterPaint"), mVideoSource, false);
-  }
 
   if (mVideoSource->mTimer) {
     mVideoSource->mTimer->Cancel();
     mVideoSource->mTimer = nullptr;
   }
   mVideoSource->mTabSource->NotifyStreamStop(mVideoSource->mWindow);
   return NS_OK;
 }
@@ -71,31 +65,24 @@ MediaEngineTabVideoSource::HandleEvent(n
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MediaEngineTabVideoSource::Notify(nsITimer*) {
   Draw();
   return NS_OK;
 }
+#define LOGTAG "TabVideo"
 
 nsresult
 MediaEngineTabVideoSource::InitRunnable::Run()
 {
-  nsresult rv;
-  nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-  nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
-  if (!branch)
-    return NS_OK;
-  branch->GetIntPref("media.tabstreaming.width", &mVideoSource->mBufW);
-  branch->GetIntPref("media.tabstreaming.height", &mVideoSource->mBufH);
-  branch->GetIntPref("media.tabstreaming.time_per_frame", &mVideoSource->mTimePerFrame);
   mVideoSource->mData = (unsigned char*)malloc(mVideoSource->mBufW * mVideoSource->mBufH * 4);
 
+  nsresult rv;
   mVideoSource->mTabSource = do_GetService(NS_TABSOURCESERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIDOMWindow> win;
   rv = mVideoSource->mTabSource->GetTabToStream(getter_AddRefs(win));
   NS_ENSURE_SUCCESS(rv, rv);
   if (!win)
     return NS_OK;
@@ -114,19 +101,51 @@ MediaEngineTabVideoSource::GetName(nsASt
 
 void
 MediaEngineTabVideoSource::GetUUID(nsAString_internal& aUuid)
 {
   aUuid.AssignLiteral(MOZ_UTF16("uuid"));
 }
 
 nsresult
-MediaEngineTabVideoSource::Allocate(const VideoTrackConstraintsN&,
-                                    const MediaEnginePrefs&)
+MediaEngineTabVideoSource::Allocate(const VideoTrackConstraintsN& aConstraints,
+                                    const MediaEnginePrefs& aPrefs)
 {
+
+  ConstrainLongRange cWidth(aConstraints.mRequired.mWidth);
+  ConstrainLongRange cHeight(aConstraints.mRequired.mHeight);
+
+  if (aConstraints.mAdvanced.WasPassed()) {
+    const auto& advanced = aConstraints.mAdvanced.Value();
+    for (uint32_t i = 0; i < advanced.Length(); i++) {
+      if (cWidth.mMax >= advanced[i].mWidth.mMin && cWidth.mMin <= advanced[i].mWidth.mMax &&
+	  cHeight.mMax >= advanced[i].mHeight.mMin && cHeight.mMin <= advanced[i].mHeight.mMax) {
+	cWidth.mMin = std::max(cWidth.mMin, advanced[i].mWidth.mMin);
+	cHeight.mMin = std::max(cHeight.mMin, advanced[i].mHeight.mMin);
+      }
+    }
+  }
+
+  mBufW = aPrefs.GetWidth(false);
+  mBufH = aPrefs.GetHeight(false);
+
+  if (cWidth.mMin > mBufW) {
+    mBufW = cWidth.mMin;
+  } else if (cWidth.mMax < mBufW) {
+    mBufW = cWidth.mMax;
+  }
+
+  if (cHeight.mMin > mBufH) {
+    mBufH = cHeight.mMin;
+  } else if (cHeight.mMax < mBufH) {
+    mBufH = cHeight.mMax;
+  }
+
+  mTimePerFrame = aPrefs.mFPS ? 1000 / aPrefs.mFPS : aPrefs.mFPS;
+
   return NS_OK;
 }
 
 nsresult
 MediaEngineTabVideoSource::Deallocate()
 {
   return NS_OK;
 }
--- a/dom/base/DOMRequestHelper.jsm
+++ b/dom/base/DOMRequestHelper.jsm
@@ -181,18 +181,18 @@ DOMRequestIpcHelper.prototype = {
     }
 
     this._destroyed = true;
 
     Services.obs.removeObserver(this, "inner-window-destroyed");
 
     if (this._listeners) {
       Object.keys(this._listeners).forEach((aName) => {
-        this._listeners[aName] ? cpmm.removeWeakMessageListener(aName, this)
-                               : cpmm.removeMessageListener(aName, this);
+        this._listeners[aName].weakRef ? cpmm.removeWeakMessageListener(aName, this)
+                                       : cpmm.removeMessageListener(aName, this);
         delete this._listeners[aName];
       });
     }
 
     this._listeners = null;
     this._requests = null;
 
     // Objects inheriting from DOMRequestIPCHelper may have an uninit function.
--- a/dom/base/test/test_domrequesthelper.xul
+++ b/dom/base/test/test_domrequesthelper.xul
@@ -16,16 +16,18 @@
   <script type="application/javascript">
   <![CDATA[
     const Cc = Components.classes;
     const Cu = Components.utils;
     const Ci = Components.interfaces;
     Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
     let obs = Cc["@mozilla.org/observer-service;1"].
               getService(Ci.nsIObserverService);
+    let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"].
+              getService(Ci.nsIMessageBroadcaster);
 
     function DummyHelperSubclass() {
       this.onuninit = null;
     }
     DummyHelperSubclass.prototype = {
       __proto__: DOMRequestIpcHelper.prototype,
       uninit: function() {
         if (typeof this.onuninit === "function") {
@@ -138,17 +140,31 @@
 
       let frame = document.createElement("iframe");
       frame.onload = function() {
         obs.addObserver(observer, TOPIC, false);
         // Create dummy DOMRequestHelper specific to checkWindowDestruction()
         let cwdDummy = new DummyHelperSubclass();
         cwdDummy.onuninit = function() {
           uninitCalled = true;
+
+          if (!aOptions.messages || !aOptions.messages.length) {
+            return;
+          }
+
+          // If all message listeners are removed, cwdDummy.receiveMessage
+          // should never be called.
+          ppmm.broadcastAsyncMessage(aOptions.messages[0].name);
         };
+
+        // Test if we still receive messages from ppmm.
+        cwdDummy.receiveMessage = function(aMessage) {
+          ok(false, "cwdDummy.receiveMessage should NOT be called: " + aMessage.name);
+        };
+
         cwdDummy.initDOMRequestHelper(frame.contentWindow, aOptions.messages);
         // Make sure to clear our strong ref here so that we can test our
         // weak reference listeners and observer.
         cwdDummy = null;
         if (aOptions.gc) {
           Cu.schedulePreciseGC(function() {
             SpecialPowers.DOMWindowUtils.cycleCollect();
             SpecialPowers.DOMWindowUtils.garbageCollect();
--- a/dom/bluetooth/BluetoothProfileManagerBase.h
+++ b/dom/bluetooth/BluetoothProfileManagerBase.h
@@ -23,16 +23,28 @@
 #define ERR_OPERATION_TIMEOUT           "OperationTimeout"
 
 #include "BluetoothCommon.h"
 #include "nsIObserver.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 class BluetoothProfileController;
 
+class BluetoothProfileResultHandler
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothProfileResultHandler);
+
+  virtual ~BluetoothProfileResultHandler() { }
+
+  virtual void OnError(nsresult aResult) { }
+  virtual void Init() { }
+  virtual void Deinit() { }
+};
+
 class BluetoothProfileManagerBase : public nsIObserver
 {
 public:
   virtual void OnGetServiceChannel(const nsAString& aDeviceAddress,
                                    const nsAString& aServiceUuid,
                                    int aChannel) = 0;
   virtual void OnUpdateSdpRecords(const nsAString& aDeviceAddress) = 0;
 
--- a/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp
@@ -495,17 +495,17 @@ static btrc_callbacks_t sBtAvrcpCallback
 /*
  * This function will be only called when Bluetooth is turning on.
  * It is important to register a2dp callbacks before enable() gets called.
  * It is required to register a2dp callbacks before a2dp media task
  * starts up.
  */
 // static
 void
-BluetoothA2dpManager::InitA2dpInterface()
+BluetoothA2dpManager::InitA2dpInterface(BluetoothProfileResultHandler* aRes)
 {
   BluetoothInterface* btInf = BluetoothInterface::GetInstance();
   NS_ENSURE_TRUE_VOID(btInf);
 
   sBtA2dpInterface = btInf->GetBluetoothA2dpInterface();
   NS_ENSURE_TRUE_VOID(sBtA2dpInterface);
 
   int ret = sBtA2dpInterface->Init(&sBtA2dpCallbacks);
@@ -517,16 +517,20 @@ BluetoothA2dpManager::InitA2dpInterface(
   sBtAvrcpInterface = btInf->GetBluetoothAvrcpInterface();
   NS_ENSURE_TRUE_VOID(sBtAvrcpInterface);
 
   ret = sBtAvrcpInterface->Init(&sBtAvrcpCallbacks);
   if (ret != BT_STATUS_SUCCESS) {
     BT_LOGR("Warning: failed to init avrcp module");
   }
 #endif
+
+  if (aRes) {
+    aRes->Init();
+  }
 }
 
 BluetoothA2dpManager::~BluetoothA2dpManager()
 {
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   NS_ENSURE_TRUE_VOID(obs);
   if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
     BT_WARNING("Failed to remove shutdown observer!");
@@ -592,30 +596,33 @@ BluetoothA2dpManager::Get()
   // Create a new instance, register, and return
   BluetoothA2dpManager* manager = new BluetoothA2dpManager();
   sBluetoothA2dpManager = manager;
   return sBluetoothA2dpManager;
 }
 
 // static
 void
-BluetoothA2dpManager::DeinitA2dpInterface()
+BluetoothA2dpManager::DeinitA2dpInterface(BluetoothProfileResultHandler* aRes)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (sBtA2dpInterface) {
     sBtA2dpInterface->Cleanup();
     sBtA2dpInterface = nullptr;
   }
 #if ANDROID_VERSION > 17
   if (sBtAvrcpInterface) {
     sBtAvrcpInterface->Cleanup();
     sBtAvrcpInterface = nullptr;
   }
 #endif
+  if (aRes) {
+    aRes->Deinit();
+  }
 }
 
 void
 BluetoothA2dpManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   sInShutdown = true;
   Disconnect(nullptr);
--- a/dom/bluetooth/bluedroid/BluetoothA2dpManager.h
+++ b/dom/bluetooth/bluedroid/BluetoothA2dpManager.h
@@ -25,18 +25,18 @@ public:
     SINK_UNKNOWN,
     SINK_DISCONNECTED,
     SINK_CONNECTING,
     SINK_CONNECTED,
     SINK_PLAYING,
   };
 
   static BluetoothA2dpManager* Get();
-  static void InitA2dpInterface();
-  static void DeinitA2dpInterface();
+  static void InitA2dpInterface(BluetoothProfileResultHandler* aRes);
+  static void DeinitA2dpInterface(BluetoothProfileResultHandler* aRes);
   virtual ~BluetoothA2dpManager();
 
   // A2DP-specific functions
   void HandleSinkPropertyChanged(const BluetoothSignal& aSignal);
 
   // AVRCP-specific functions
   void SetAvrcpConnected(bool aConnected);
   bool IsAvrcpConnected();
--- a/dom/bluetooth/bluedroid/BluetoothInterface.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothInterface.cpp
@@ -571,152 +571,270 @@ struct interface_traits<BluetoothHandsfr
   typedef const bthf_interface_t const_interface_type;
 
   static const char* profile_id()
   {
     return BT_PROFILE_HANDSFREE_ID;
   }
 };
 
+typedef
+  BluetoothInterfaceRunnable0<BluetoothHandsfreeResultHandler, void>
+  BluetoothHandsfreeResultRunnable;
+
+typedef
+  BluetoothInterfaceRunnable1<BluetoothHandsfreeResultHandler, void, bt_status_t>
+  BluetoothHandsfreeErrorRunnable;
+
+static nsresult
+DispatchBluetoothHandsfreeResult(
+  BluetoothHandsfreeResultHandler* aRes,
+  void (BluetoothHandsfreeResultHandler::*aMethod)(),
+  bt_status_t aStatus)
+{
+  MOZ_ASSERT(aRes);
+
+  nsRunnable* runnable;
+
+  if (aStatus == BT_STATUS_SUCCESS) {
+    runnable = new BluetoothHandsfreeResultRunnable(aRes, aMethod);
+  } else {
+    runnable = new BluetoothHandsfreeErrorRunnable(aRes,
+      &BluetoothHandsfreeResultHandler::OnError, aStatus);
+  }
+  nsresult rv = NS_DispatchToMainThread(runnable);
+  if (NS_FAILED(rv)) {
+    BT_WARNING("NS_DispatchToMainThread failed: %X", rv);
+  }
+  return rv;
+}
+
 BluetoothHandsfreeInterface::BluetoothHandsfreeInterface(
   const bthf_interface_t* aInterface)
 : mInterface(aInterface)
 {
   MOZ_ASSERT(mInterface);
 }
 
 BluetoothHandsfreeInterface::~BluetoothHandsfreeInterface()
 { }
 
-bt_status_t
-BluetoothHandsfreeInterface::Init(bthf_callbacks_t* aCallbacks)
+void
+BluetoothHandsfreeInterface::Init(bthf_callbacks_t* aCallbacks,
+                                  BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->init(aCallbacks);
+  bt_status_t status = mInterface->init(aCallbacks);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(aRes,
+                                     &BluetoothHandsfreeResultHandler::Init,
+                                     status);
+  }
 }
 
 void
-BluetoothHandsfreeInterface::Cleanup()
+BluetoothHandsfreeInterface::Cleanup(BluetoothHandsfreeResultHandler* aRes)
 {
   mInterface->cleanup();
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(aRes,
+                                     &BluetoothHandsfreeResultHandler::Cleanup,
+                                     BT_STATUS_SUCCESS);
+  }
 }
 
 /* Connect / Disconnect */
 
-bt_status_t
-BluetoothHandsfreeInterface::Connect(bt_bdaddr_t* aBdAddr)
+void
+BluetoothHandsfreeInterface::Connect(bt_bdaddr_t* aBdAddr,
+                                     BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->connect(aBdAddr);
+  bt_status_t status = mInterface->connect(aBdAddr);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::Connect, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::Disconnect(bt_bdaddr_t* aBdAddr)
+void
+BluetoothHandsfreeInterface::Disconnect(bt_bdaddr_t* aBdAddr,
+                                        BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->disconnect(aBdAddr);
+  bt_status_t status = mInterface->disconnect(aBdAddr);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::Disconnect, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::ConnectAudio(bt_bdaddr_t* aBdAddr)
+void
+BluetoothHandsfreeInterface::ConnectAudio(
+  bt_bdaddr_t* aBdAddr, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->connect_audio(aBdAddr);
+  bt_status_t status = mInterface->connect_audio(aBdAddr);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::ConnectAudio, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::DisconnectAudio(bt_bdaddr_t* aBdAddr)
+void
+BluetoothHandsfreeInterface::DisconnectAudio(
+  bt_bdaddr_t* aBdAddr, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->disconnect_audio(aBdAddr);
+  bt_status_t status = mInterface->disconnect_audio(aBdAddr);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::DisconnectAudio, status);
+  }
 }
 
 /* Voice Recognition */
 
-bt_status_t
-BluetoothHandsfreeInterface::StartVoiceRecognition()
+void
+BluetoothHandsfreeInterface::StartVoiceRecognition(
+  BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->start_voice_recognition();
+  bt_status_t status = mInterface->start_voice_recognition();
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::StartVoiceRecognition, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::StopVoiceRecognition()
+void
+BluetoothHandsfreeInterface::StopVoiceRecognition(
+  BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->stop_voice_recognition();
+  bt_status_t status = mInterface->stop_voice_recognition();
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::StopVoiceRecognition, status);
+  }
 }
 
 /* Volume */
 
-bt_status_t
-BluetoothHandsfreeInterface::VolumeControl(bthf_volume_type_t aType,
-                                           int aVolume)
+void
+BluetoothHandsfreeInterface::VolumeControl(
+  bthf_volume_type_t aType, int aVolume, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->volume_control(aType, aVolume);
+  bt_status_t status = mInterface->volume_control(aType, aVolume);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::VolumeControl, status);
+  }
 }
 
 /* Device status */
 
-bt_status_t
+void
 BluetoothHandsfreeInterface::DeviceStatusNotification(
   bthf_network_state_t aNtkState, bthf_service_type_t aSvcType, int aSignal,
-  int aBattChg)
+  int aBattChg, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->device_status_notification(aNtkState, aSvcType, aSignal,
-                                                aBattChg);
+  bt_status_t status = mInterface->device_status_notification(
+    aNtkState, aSvcType, aSignal, aBattChg);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::DeviceStatusNotification,
+      status);
+  }
 }
 
 /* Responses */
 
-bt_status_t
-BluetoothHandsfreeInterface::CopsResponse(const char* aCops)
+void
+BluetoothHandsfreeInterface::CopsResponse(
+  const char* aCops, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->cops_response(aCops);
+  bt_status_t status = mInterface->cops_response(aCops);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::CopsResponse, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::CindResponse(int aSvc, int aNumActive,
-                                          int aNumHeld,
-                                          bthf_call_state_t aCallSetupState,
-                                          int aSignal, int aRoam, int aBattChg)
+void
+BluetoothHandsfreeInterface::CindResponse(
+  int aSvc, int aNumActive, int aNumHeld, bthf_call_state_t aCallSetupState,
+  int aSignal, int aRoam, int aBattChg, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->cind_response(aSvc, aNumActive, aNumHeld,
-                                   aCallSetupState, aSignal, aRoam,
-                                   aBattChg);
+  bt_status_t status = mInterface->cind_response(aSvc, aNumActive, aNumHeld,
+                                                 aCallSetupState, aSignal,
+                                                 aRoam, aBattChg);
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::CindResponse, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::FormattedAtResponse(const char* aRsp)
+void
+BluetoothHandsfreeInterface::FormattedAtResponse(
+  const char* aRsp, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->formatted_at_response(aRsp);
+  bt_status_t status = mInterface->formatted_at_response(aRsp);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::FormattedAtResponse, status);
+  }
 }
 
-bt_status_t
+void
 BluetoothHandsfreeInterface::AtResponse(bthf_at_response_t aResponseCode,
-                                        int aErrorCode)
+                                        int aErrorCode,
+                                        BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->at_response(aResponseCode, aErrorCode);
+  bt_status_t status = mInterface->at_response(aResponseCode, aErrorCode);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::AtResponse, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::ClccResponse(int aIndex,
-                                          bthf_call_direction_t aDir,
-                                          bthf_call_state_t aState,
-                                          bthf_call_mode_t aMode,
-                                          bthf_call_mpty_type_t aMpty,
-                                          const char* aNumber,
-                                          bthf_call_addrtype_t aType)
+void
+BluetoothHandsfreeInterface::ClccResponse(
+  int aIndex, bthf_call_direction_t aDir, bthf_call_state_t aState,
+  bthf_call_mode_t aMode, bthf_call_mpty_type_t aMpty, const char* aNumber,
+  bthf_call_addrtype_t aType, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->clcc_response(aIndex, aDir, aState, aMode, aMpty,
-                                   aNumber, aType);
+  bt_status_t status = mInterface->clcc_response(aIndex, aDir, aState, aMode,
+                                                 aMpty, aNumber, aType);
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::ClccResponse, status);
+  }
 }
 
 /* Phone State */
 
-bt_status_t
+void
 BluetoothHandsfreeInterface::PhoneStateChange(int aNumActive, int aNumHeld,
   bthf_call_state_t aCallSetupState, const char* aNumber,
-  bthf_call_addrtype_t aType)
+  bthf_call_addrtype_t aType, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->phone_state_change(aNumActive, aNumHeld, aCallSetupState,
-                                        aNumber, aType);
+  bt_status_t status = mInterface->phone_state_change(aNumActive, aNumHeld,
+                                                      aCallSetupState,
+                                                      aNumber, aType);
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::PhoneStateChange, status);
+  }
 }
 
 //
 // Bluetooth Advanced Audio Interface
 //
 
 template<>
 struct interface_traits<BluetoothA2dpInterface>
--- a/dom/bluetooth/bluedroid/BluetoothInterface.h
+++ b/dom/bluetooth/bluedroid/BluetoothInterface.h
@@ -67,65 +67,112 @@ protected:
 private:
   const btsock_interface_t* mInterface;
 };
 
 //
 // Handsfree Interface
 //
 
+class BluetoothHandsfreeResultHandler
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothHandsfreeResultHandler)
+
+  virtual ~BluetoothHandsfreeResultHandler() { }
+
+  virtual void OnError(bt_status_t aStatus)
+  {
+    BT_WARNING("Received error code %d", (int)aStatus);
+  }
+
+  virtual void Init() { }
+  virtual void Cleanup() { }
+
+  virtual void Connect() { }
+  virtual void Disconnect() { }
+  virtual void ConnectAudio() { }
+  virtual void DisconnectAudio() { }
+
+  virtual void StartVoiceRecognition() { }
+  virtual void StopVoiceRecognition() { }
+
+  virtual void VolumeControl() { }
+
+  virtual void DeviceStatusNotification() { }
+
+  virtual void CopsResponse() { }
+  virtual void CindResponse() { }
+  virtual void FormattedAtResponse() { }
+  virtual void AtResponse() { }
+  virtual void ClccResponse() { }
+  virtual void PhoneStateChange() { }
+};
+
 class BluetoothHandsfreeInterface
 {
 public:
   friend class BluetoothInterface;
 
-  bt_status_t Init(bthf_callbacks_t* aCallbacks);
-  void        Cleanup();
+  void Init(bthf_callbacks_t* aCallbacks,
+            BluetoothHandsfreeResultHandler* aRes);
+  void Cleanup(BluetoothHandsfreeResultHandler* aRes);
 
   /* Connect / Disconnect */
 
-  bt_status_t Connect(bt_bdaddr_t* aBdAddr);
-  bt_status_t Disconnect(bt_bdaddr_t* aBdAddr);
-  bt_status_t ConnectAudio(bt_bdaddr_t* aBdAddr);
-  bt_status_t DisconnectAudio(bt_bdaddr_t* aBdAddr);
+  void Connect(bt_bdaddr_t* aBdAddr,
+               BluetoothHandsfreeResultHandler* aRes);
+  void Disconnect(bt_bdaddr_t* aBdAddr,
+                  BluetoothHandsfreeResultHandler* aRes);
+  void ConnectAudio(bt_bdaddr_t* aBdAddr,
+                    BluetoothHandsfreeResultHandler* aRes);
+  void DisconnectAudio(bt_bdaddr_t* aBdAddr,
+                       BluetoothHandsfreeResultHandler* aRes);
 
   /* Voice Recognition */
 
-  bt_status_t StartVoiceRecognition();
-  bt_status_t StopVoiceRecognition();
+  void StartVoiceRecognition(BluetoothHandsfreeResultHandler* aRes);
+  void StopVoiceRecognition(BluetoothHandsfreeResultHandler* aRes);
 
   /* Volume */
 
-  bt_status_t VolumeControl(bthf_volume_type_t aType, int aVolume);
+  void VolumeControl(bthf_volume_type_t aType, int aVolume,
+                     BluetoothHandsfreeResultHandler* aRes);
 
   /* Device status */
 
-  bt_status_t DeviceStatusNotification(bthf_network_state_t aNtkState,
-                                       bthf_service_type_t aSvcType,
-                                       int aSignal, int aBattChg);
+  void DeviceStatusNotification(bthf_network_state_t aNtkState,
+                                bthf_service_type_t aSvcType,
+                                int aSignal, int aBattChg,
+                                BluetoothHandsfreeResultHandler* aRes);
 
   /* Responses */
 
-  bt_status_t CopsResponse(const char* aCops);
-  bt_status_t CindResponse(int aSvc, int aNumActive, int aNumHeld,
-                           bthf_call_state_t aCallSetupState, int aSignal,
-                           int aRoam, int aBattChg);
-  bt_status_t FormattedAtResponse(const char* aRsp);
-  bt_status_t AtResponse(bthf_at_response_t aResponseCode, int aErrorCode);
-  bt_status_t ClccResponse(int aIndex, bthf_call_direction_t aDir,
-                           bthf_call_state_t aState, bthf_call_mode_t aMode,
-                           bthf_call_mpty_type_t aMpty, const char* aNumber,
-                           bthf_call_addrtype_t aType);
+  void CopsResponse(const char* aCops,
+                    BluetoothHandsfreeResultHandler* aRes);
+  void CindResponse(int aSvc, int aNumActive, int aNumHeld,
+                    bthf_call_state_t aCallSetupState, int aSignal,
+                    int aRoam, int aBattChg,
+                    BluetoothHandsfreeResultHandler* aRes);
+  void FormattedAtResponse(const char* aRsp,
+                           BluetoothHandsfreeResultHandler* aRes);
+  void AtResponse(bthf_at_response_t aResponseCode, int aErrorCode,
+                  BluetoothHandsfreeResultHandler* aRes);
+  void ClccResponse(int aIndex, bthf_call_direction_t aDir,
+                    bthf_call_state_t aState, bthf_call_mode_t aMode,
+                    bthf_call_mpty_type_t aMpty, const char* aNumber,
+                    bthf_call_addrtype_t aType,
+                    BluetoothHandsfreeResultHandler* aRes);
 
   /* Phone State */
 
-  bt_status_t PhoneStateChange(int aNumActive, int aNumHeld,
-                               bthf_call_state_t aCallSetupState,
-                               const char* aNumber,
-                               bthf_call_addrtype_t aType);
+  void PhoneStateChange(int aNumActive, int aNumHeld,
+                        bthf_call_state_t aCallSetupState,
+                        const char* aNumber, bthf_call_addrtype_t aType,
+                        BluetoothHandsfreeResultHandler* aRes);
 
 protected:
   BluetoothHandsfreeInterface(const bthf_interface_t* aInterface);
   ~BluetoothHandsfreeInterface();
 
 private:
   const bthf_interface_t* mInterface;
 };
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
@@ -150,28 +150,73 @@ public:
     if (!opp || !opp->Listen()) {
       BT_LOGR("Fail to start BluetoothOppManager listening");
     }
 
     return NS_OK;
   }
 };
 
+/* |ProfileDeinitResultHandler| collect the results of all profile
+ * result handlers and calls |Proceed| after all results handlers
+ * have been run.
+ */
+class ProfileDeinitResultHandler MOZ_FINAL
+: public BluetoothProfileResultHandler
+{
+public:
+  ProfileDeinitResultHandler(unsigned char aNumProfiles)
+  : mNumProfiles(aNumProfiles)
+  {
+    MOZ_ASSERT(mNumProfiles);
+  }
+
+  void Deinit() MOZ_OVERRIDE
+  {
+    if (!(--mNumProfiles)) {
+      Proceed();
+    }
+  }
+
+  void OnError(nsresult aResult) MOZ_OVERRIDE
+  {
+    if (!(--mNumProfiles)) {
+      Proceed();
+    }
+  }
+
+private:
+  void Proceed() const
+  {
+    sBtInterface->Cleanup(nullptr);
+  }
+
+  unsigned char mNumProfiles;
+};
+
 class CleanupTask MOZ_FINAL : public nsRunnable
 {
 public:
   NS_IMETHOD
   Run()
   {
+    static void (* const sDeinitManager[])(BluetoothProfileResultHandler*) = {
+      BluetoothHfpManager::DeinitHfpInterface,
+      BluetoothA2dpManager::DeinitA2dpInterface
+    };
+
     MOZ_ASSERT(NS_IsMainThread());
 
     // Cleanup bluetooth interfaces after BT state becomes BT_STATE_OFF.
-    BluetoothHfpManager::DeinitHfpInterface();
-    BluetoothA2dpManager::DeinitA2dpInterface();
-    sBtInterface->Cleanup(nullptr);
+    nsRefPtr<ProfileDeinitResultHandler> res =
+      new ProfileDeinitResultHandler(MOZ_ARRAY_LENGTH(sDeinitManager));
+
+    for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sDeinitManager); ++i) {
+      sDeinitManager[i](res);
+    }
 
     return NS_OK;
   }
 };
 
 /**
  *  Static callback functions
  */
@@ -813,29 +858,74 @@ public:
 
     nsRefPtr<nsRunnable> runnable = new BluetoothService::ToggleBtAck(false);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
   }
 };
 
+/* |ProfileInitResultHandler| collect the results of all profile
+ * result handlers and calls |Proceed| after all results handlers
+ * have been run.
+ */
+class ProfileInitResultHandler MOZ_FINAL
+: public BluetoothProfileResultHandler
+{
+public:
+  ProfileInitResultHandler(unsigned char aNumProfiles)
+  : mNumProfiles(aNumProfiles)
+  {
+    MOZ_ASSERT(mNumProfiles);
+  }
+
+  void Init() MOZ_OVERRIDE
+  {
+    if (!(--mNumProfiles)) {
+      Proceed();
+    }
+  }
+
+  void OnError(nsresult aResult) MOZ_OVERRIDE
+  {
+    if (!(--mNumProfiles)) {
+      Proceed();
+    }
+  }
+
+private:
+  void Proceed() const
+  {
+    sBtInterface->Enable(new EnableResultHandler());
+  }
+
+  unsigned char mNumProfiles;
+};
+
 class InitResultHandler MOZ_FINAL : public BluetoothResultHandler
 {
 public:
   void Init() MOZ_OVERRIDE
   {
+    static void (* const sInitManager[])(BluetoothProfileResultHandler*) = {
+      BluetoothHfpManager::InitHfpInterface,
+      BluetoothA2dpManager::InitA2dpInterface
+    };
+
     MOZ_ASSERT(NS_IsMainThread());
 
     // Register all the bluedroid callbacks before enable() get called
     // It is required to register a2dp callbacks before a2dp media task starts up.
     // If any interface cannot be initialized, turn on bluetooth core anyway.
-    BluetoothHfpManager::InitHfpInterface();
-    BluetoothA2dpManager::InitA2dpInterface();
-    sBtInterface->Enable(new EnableResultHandler());
+    nsRefPtr<ProfileInitResultHandler> res =
+      new ProfileInitResultHandler(MOZ_ARRAY_LENGTH(sInitManager));
+
+    for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sInitManager); ++i) {
+      sInitManager[i](res);
+    }
   }
 
   void OnError(int aStatus) MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     BT_LOGR("BluetoothInterface::Init failed: %d", aStatus);
 
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
@@ -429,35 +429,107 @@ BluetoothHfpManager::Init()
 
   nsRefPtr<GetVolumeTask> callback = new GetVolumeTask();
   rv = settingsLock->Get(AUDIO_VOLUME_BT_SCO_ID, callback);
   NS_ENSURE_SUCCESS(rv, false);
 
   return true;
 }
 
+class CleanupInitResultHandler MOZ_FINAL : public BluetoothHandsfreeResultHandler
+{
+public:
+  CleanupInitResultHandler(BluetoothHandsfreeInterface* aInterface,
+                           BluetoothProfileResultHandler* aRes)
+  : mInterface(aInterface)
+  , mRes(aRes)
+  {
+    MOZ_ASSERT(mInterface);
+  }
+
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::Init failed: %d", (int)aStatus);
+    if (mRes) {
+      mRes->OnError(NS_ERROR_FAILURE);
+    }
+  }
+
+  void Init() MOZ_OVERRIDE
+  {
+    sBluetoothHfpInterface = mInterface;
+    if (mRes) {
+      mRes->Init();
+    }
+  }
+
+  void Cleanup() MOZ_OVERRIDE
+  {
+    sBluetoothHfpInterface = nullptr;
+    /* During re-initialization, a previouly initialized
+     * |BluetoothHandsfreeInterface| has now been cleaned
+     * up, so we start initialization.
+     */
+    RunInit();
+  }
+
+  void RunInit()
+  {
+    mInterface->Init(&sBluetoothHfpCallbacks, this);
+  }
+
+private:
+  BluetoothHandsfreeInterface* mInterface;
+  nsRefPtr<BluetoothProfileResultHandler> mRes;
+};
+
+class InitResultHandlerRunnable MOZ_FINAL : public nsRunnable
+{
+public:
+  InitResultHandlerRunnable(CleanupInitResultHandler* aRes)
+  : mRes(aRes)
+  {
+    MOZ_ASSERT(mRes);
+  }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    mRes->RunInit();
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<CleanupInitResultHandler> mRes;
+};
+
 // static
 void
-BluetoothHfpManager::InitHfpInterface()
+BluetoothHfpManager::InitHfpInterface(BluetoothProfileResultHandler* aRes)
 {
   BluetoothInterface* btInf = BluetoothInterface::GetInstance();
   NS_ENSURE_TRUE_VOID(btInf);
 
-  if (sBluetoothHfpInterface) {
-    sBluetoothHfpInterface->Cleanup();
-    sBluetoothHfpInterface = nullptr;
-  }
-
   BluetoothHandsfreeInterface *interface =
     btInf->GetBluetoothHandsfreeInterface();
   NS_ENSURE_TRUE_VOID(interface);
 
-  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-    interface->Init(&sBluetoothHfpCallbacks));
-  sBluetoothHfpInterface = interface;
+  nsRefPtr<CleanupInitResultHandler> res =
+    new CleanupInitResultHandler(interface, aRes);
+
+  if (sBluetoothHfpInterface) {
+    // Cleanup an initialized HFP before initializing again.
+    sBluetoothHfpInterface->Cleanup(res);
+  } else {
+    // If there's no HFP interface to cleanup first, we dispatch
+    // a runnable that calls the profile result handler.
+    nsRefPtr<nsRunnable> r = new InitResultHandlerRunnable(res);
+    if (NS_FAILED(NS_DispatchToMainThread(r))) {
+      BT_LOGR("Failed to dispatch HFP init runnable");
+    }
+  }
 }
 
 BluetoothHfpManager::~BluetoothHfpManager()
 {
   if (!mListener->Listen(false)) {
     BT_WARNING("Failed to stop listening RIL");
   }
   mListener = nullptr;
@@ -468,23 +540,75 @@ BluetoothHfpManager::~BluetoothHfpManage
   if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) ||
       NS_FAILED(obs->RemoveObserver(this, MOZSETTINGS_CHANGED_ID))) {
     BT_WARNING("Failed to remove observers!");
   }
 
   hal::UnregisterBatteryObserver(this);
 }
 
+class CleanupResultHandler MOZ_FINAL : public BluetoothHandsfreeResultHandler
+{
+public:
+  CleanupResultHandler(BluetoothProfileResultHandler* aRes)
+  : mRes(aRes)
+  { }
+
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::Cleanup failed: %d", (int)aStatus);
+    if (mRes) {
+      mRes->OnError(NS_ERROR_FAILURE);
+    }
+  }
+
+  void Cleanup() MOZ_OVERRIDE
+  {
+    sBluetoothHfpInterface = nullptr;
+    if (mRes) {
+      mRes->Deinit();
+    }
+  }
+
+private:
+  nsRefPtr<BluetoothProfileResultHandler> mRes;
+};
+
+class DeinitResultHandlerRunnable MOZ_FINAL : public nsRunnable
+{
+public:
+  DeinitResultHandlerRunnable(BluetoothProfileResultHandler* aRes)
+  : mRes(aRes)
+  {
+    MOZ_ASSERT(mRes);
+  }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    mRes->Deinit();
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<BluetoothProfileResultHandler> mRes;
+};
+
 // static
 void
-BluetoothHfpManager::DeinitHfpInterface()
+BluetoothHfpManager::DeinitHfpInterface(BluetoothProfileResultHandler* aRes)
 {
   if (sBluetoothHfpInterface) {
-    sBluetoothHfpInterface->Cleanup();
-    sBluetoothHfpInterface = nullptr;
+    sBluetoothHfpInterface->Cleanup(new CleanupResultHandler(aRes));
+  } else if (aRes) {
+    // We dispatch a runnable here to make the profile resource handler
+    // behave as if HFP was initialized.
+    nsRefPtr<nsRunnable> r = new DeinitResultHandlerRunnable(aRes);
+    if (NS_FAILED(NS_DispatchToMainThread(r))) {
+      BT_LOGR("Failed to dispatch cleanup-result-handler runnable");
+    }
   }
 }
 
 //static
 BluetoothHfpManager*
 BluetoothHfpManager::Get()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -668,42 +792,60 @@ BluetoothHfpManager::ProcessAtCnum()
     message.AppendLiteral(",,4");
 
     SendLine(message.get());
   }
 
   SendResponse(BTHF_AT_RESPONSE_OK);
 }
 
+class CindResponseResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::CindResponse failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::ProcessAtCind()
 {
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
 
   int numActive = GetNumberOfCalls(nsITelephonyService::CALL_STATE_CONNECTED);
   int numHeld = GetNumberOfCalls(nsITelephonyService::CALL_STATE_HELD);
+  bthf_call_state_t callState = ConvertToBthfCallState(GetCallSetupState());
 
-  bt_status_t status = sBluetoothHfpInterface->CindResponse(
-                          mService,
-                          numActive,
-                          numHeld,
-                          ConvertToBthfCallState(GetCallSetupState()),
-                          mSignal,
-                          mRoam,
-                          mBattChg);
-  NS_ENSURE_TRUE_VOID(status == BT_STATUS_SUCCESS);
+  sBluetoothHfpInterface->CindResponse(mService, numActive, numHeld,
+                                       callState, mSignal, mRoam, mBattChg,
+                                       new CindResponseResultHandler());
 }
 
+class CopsResponseResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::CopsResponse failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::ProcessAtCops()
 {
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
-  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->CopsResponse(
-      NS_ConvertUTF16toUTF8(mOperatorName).get()));
+
+  sBluetoothHfpInterface->CopsResponse(
+    NS_ConvertUTF16toUTF8(mOperatorName).get(),
+    new CopsResponseResultHandler());
 }
 
 void
 BluetoothHfpManager::ProcessAtClcc()
 {
   uint32_t callNumbers = mCurrentCallArray.Length();
   uint32_t i;
   for (i = 1; i < callNumbers; i++) {
@@ -715,24 +857,36 @@ BluetoothHfpManager::ProcessAtClcc()
     MOZ_ASSERT(i == 2);
 
     SendCLCC(mCdmaSecondCall, 2);
   }
 
   SendResponse(BTHF_AT_RESPONSE_OK);
 }
 
+class AtResponseResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::AtResponse failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::ProcessUnknownAt(char *aAtString)
 {
   BT_LOGR("[%s]", aAtString);
 
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
-  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->AtResponse(BTHF_AT_RESPONSE_ERROR, 0));
+
+  sBluetoothHfpInterface->AtResponse(BTHF_AT_RESPONSE_ERROR, 0,
+                                     new AtResponseResultHandler());
 }
 
 void
 BluetoothHfpManager::ProcessKeyPressed()
 {
   bool hasActiveCall =
     (FindFirstCall(nsITelephonyService::CALL_STATE_CONNECTED) > 0);
 
@@ -833,16 +987,27 @@ BluetoothHfpManager::NotifyDialer(const 
   NS_NAMED_LITERAL_STRING(type, "bluetooth-dialer-command");
   InfallibleTArray<BluetoothNamedValue> parameters;
 
   BT_APPEND_NAMED_VALUE(parameters, "command", nsString(aCommand));
 
   BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters);
 }
 
+class VolumeControlResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::VolumeControl failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::HandleVolumeChanged(const nsAString& aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The string that we're interested in will be a JSON string that looks like:
   //  {"key":"volumeup", "value":10}
   //  {"key":"volumedown", "value":2}
@@ -877,19 +1042,18 @@ BluetoothHfpManager::HandleVolumeChanged
   if (mReceiveVgsFlag) {
     mReceiveVgsFlag = false;
     return;
   }
 
   // Only send volume back when there's a connected headset
   if (IsConnected()) {
     NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
-    NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-      sBluetoothHfpInterface->VolumeControl(BTHF_VOLUME_TYPE_SPK,
-                                            mCurrentVgs));
+    sBluetoothHfpInterface->VolumeControl(BTHF_VOLUME_TYPE_SPK, mCurrentVgs,
+                                          new VolumeControlResultHandler());
   }
 }
 
 void
 BluetoothHfpManager::HandleVoiceConnectionChanged(uint32_t aClientId)
 {
   nsCOMPtr<nsIMobileConnectionProvider> connection =
     do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
@@ -970,16 +1134,27 @@ BluetoothHfpManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   sInShutdown = true;
   Disconnect(nullptr);
   DisconnectSco();
   sBluetoothHfpManager = nullptr;
 }
 
+class ClccResponseResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::ClccResponse failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::SendCLCC(Call& aCall, int aIndex)
 {
   NS_ENSURE_TRUE_VOID(aCall.mState !=
                         nsITelephonyService::CALL_STATE_DISCONNECTED);
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
 
   bthf_call_state_t callState = ConvertToBthfCallState(aCall.mState);
@@ -989,43 +1164,62 @@ BluetoothHfpManager::SendCLCC(Call& aCal
                                                BTHF_CALL_STATE_ACTIVE;
   }
 
   if (callState == BTHF_CALL_STATE_INCOMING &&
       FindFirstCall(nsITelephonyService::CALL_STATE_CONNECTED)) {
     callState = BTHF_CALL_STATE_WAITING;
   }
 
-  bt_status_t status = sBluetoothHfpInterface->ClccResponse(
-                          aIndex,
-                          aCall.mDirection,
-                          callState,
-                          BTHF_CALL_TYPE_VOICE,
-                          BTHF_CALL_MPTY_TYPE_SINGLE,
-                          NS_ConvertUTF16toUTF8(aCall.mNumber).get(),
-                          aCall.mType);
-  NS_ENSURE_TRUE_VOID(status == BT_STATUS_SUCCESS);
+  sBluetoothHfpInterface->ClccResponse(
+    aIndex, aCall.mDirection, callState, BTHF_CALL_TYPE_VOICE,
+    BTHF_CALL_MPTY_TYPE_SINGLE, NS_ConvertUTF16toUTF8(aCall.mNumber).get(),
+    aCall.mType, new ClccResponseResultHandler());
 }
 
+class FormattedAtResponseResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::FormattedAtResponse failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::SendLine(const char* aMessage)
 {
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
-  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->FormattedAtResponse(aMessage));
+
+  sBluetoothHfpInterface->FormattedAtResponse(
+    aMessage, new FormattedAtResponseResultHandler());
 }
 
 void
 BluetoothHfpManager::SendResponse(bthf_at_response_t aResponseCode)
 {
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
-  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->AtResponse(aResponseCode, 0));
+
+  sBluetoothHfpInterface->AtResponse(
+    aResponseCode, 0, new AtResponseResultHandler());
 }
 
+class PhoneStateChangeResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::PhoneStateChange failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::UpdatePhoneCIND(uint32_t aCallIndex)
 {
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
 
   int numActive = GetNumberOfCalls(nsITelephonyService::CALL_STATE_CONNECTED);
   int numHeld = GetNumberOfCalls(nsITelephonyService::CALL_STATE_HELD);
   bthf_call_state_t callSetupState =
@@ -1033,31 +1227,42 @@ BluetoothHfpManager::UpdatePhoneCIND(uin
   nsAutoCString number =
     NS_ConvertUTF16toUTF8(mCurrentCallArray[aCallIndex].mNumber);
   bthf_call_addrtype_t type = mCurrentCallArray[aCallIndex].mType;
 
   BT_LOGR("[%d] state %d => BTHF: active[%d] held[%d] setupstate[%d]",
           aCallIndex, mCurrentCallArray[aCallIndex].mState,
           numActive, numHeld, callSetupState);
 
-  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->PhoneStateChange(
-      numActive, numHeld, callSetupState, number.get(), type));
+  sBluetoothHfpInterface->PhoneStateChange(
+    numActive, numHeld, callSetupState, number.get(), type,
+    new PhoneStateChangeResultHandler());
 }
 
+class DeviceStatusNotificationResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING(
+      "BluetoothHandsfreeInterface::DeviceStatusNotification failed: %d",
+      (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::UpdateDeviceCIND()
 {
   if (sBluetoothHfpInterface) {
-    NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-      sBluetoothHfpInterface->DeviceStatusNotification(
-        (bthf_network_state_t) mService,
-        (bthf_service_type_t) mRoam,
-        mSignal,
-        mBattChg));
+    sBluetoothHfpInterface->DeviceStatusNotification(
+      (bthf_network_state_t) mService,
+      (bthf_service_type_t) mRoam,
+      mSignal,
+      mBattChg, new DeviceStatusNotificationResultHandler());
   }
 }
 
 uint32_t
 BluetoothHfpManager::FindFirstCall(uint16_t aState)
 {
   uint32_t callLength = mCurrentCallArray.Length();
 
@@ -1283,43 +1488,65 @@ BluetoothHfpManager::ToggleCalls()
   MOZ_ASSERT(mPhoneType == PhoneType::CDMA);
 
   // Toggle acitve and held calls
   mCdmaSecondCall.mState = (mCdmaSecondCall.IsActive()) ?
                              nsITelephonyService::CALL_STATE_HELD :
                              nsITelephonyService::CALL_STATE_CONNECTED;
 }
 
+class ConnectAudioResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::ConnectAudio failed: %d",
+               (int)aStatus);
+  }
+};
+
 bool
 BluetoothHfpManager::ConnectSco()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   NS_ENSURE_TRUE(!sInShutdown, false);
   NS_ENSURE_TRUE(IsConnected() && !IsScoConnected(), false);
   NS_ENSURE_TRUE(sBluetoothHfpInterface, false);
 
   bt_bdaddr_t deviceBdAddress;
   StringToBdAddressType(mDeviceAddress, &deviceBdAddress);
-  NS_ENSURE_TRUE(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->ConnectAudio(&deviceBdAddress), false);
+  sBluetoothHfpInterface->ConnectAudio(&deviceBdAddress,
+                                       new ConnectAudioResultHandler());
 
   return true;
 }
 
+class DisconnectAudioResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::DisconnectAudio failed: %d",
+               (int)aStatus);
+  }
+};
+
 bool
 BluetoothHfpManager::DisconnectSco()
 {
   NS_ENSURE_TRUE(IsScoConnected(), false);
   NS_ENSURE_TRUE(sBluetoothHfpInterface, false);
 
   bt_bdaddr_t deviceBdAddress;
   StringToBdAddressType(mDeviceAddress, &deviceBdAddress);
-  NS_ENSURE_TRUE(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->DisconnectAudio(&deviceBdAddress), false);
+  sBluetoothHfpInterface->DisconnectAudio(&deviceBdAddress,
+                                          new DisconnectAudioResultHandler());
 
   return true;
 }
 
 bool
 BluetoothHfpManager::IsScoConnected()
 {
   return (mAudioState == BTHF_AUDIO_STATE_CONNECTED);
@@ -1327,16 +1554,47 @@ BluetoothHfpManager::IsScoConnected()
 
 bool
 BluetoothHfpManager::IsConnected()
 {
   return (mConnectionState == BTHF_CONNECTION_STATE_SLC_CONNECTED);
 }
 
 void
+BluetoothHfpManager::OnConnectError()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mController->NotifyCompletion(NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
+
+  mController = nullptr;
+  mDeviceAddress.Truncate();
+}
+
+class ConnectResultHandler MOZ_FINAL : public BluetoothHandsfreeResultHandler
+{
+public:
+  ConnectResultHandler(BluetoothHfpManager* aHfpManager)
+  : mHfpManager(aHfpManager)
+  {
+    MOZ_ASSERT(mHfpManager);
+  }
+
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::Connect failed: %d",
+               (int)aStatus);
+    mHfpManager->OnConnectError();
+  }
+
+private:
+  BluetoothHfpManager* mHfpManager;
+};
+
+void
 BluetoothHfpManager::Connect(const nsAString& aDeviceAddress,
                              BluetoothProfileController* aController)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aController && !mController);
 
   if (sInShutdown) {
     aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
@@ -1347,50 +1605,71 @@ BluetoothHfpManager::Connect(const nsASt
     BT_LOGR("sBluetoothHfpInterface is null");
     aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
     return;
   }
 
   bt_bdaddr_t deviceBdAddress;
   StringToBdAddressType(aDeviceAddress, &deviceBdAddress);
 
-  bt_status_t result = sBluetoothHfpInterface->Connect(&deviceBdAddress);
-  if (BT_STATUS_SUCCESS != result) {
-    BT_LOGR("Failed to connect: %x", result);
-    aController->NotifyCompletion(NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
-    return;
+  mDeviceAddress = aDeviceAddress;
+  mController = aController;
+
+  sBluetoothHfpInterface->Connect(&deviceBdAddress,
+                                  new ConnectResultHandler(this));
+}
+
+void
+BluetoothHfpManager::OnDisconnectError()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mController->NotifyCompletion(NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
+}
+
+class DisconnectResultHandler MOZ_FINAL : public BluetoothHandsfreeResultHandler
+{
+public:
+  DisconnectResultHandler(BluetoothHfpManager* aHfpManager)
+  : mHfpManager(aHfpManager)
+  {
+    MOZ_ASSERT(mHfpManager);
   }
 
-  mDeviceAddress = aDeviceAddress;
-  mController = aController;
-}
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::Disconnect failed: %d",
+               (int)aStatus);
+    mHfpManager->OnDisconnectError();
+  }
+
+private:
+  BluetoothHfpManager* mHfpManager;
+};
+
 
 void
 BluetoothHfpManager::Disconnect(BluetoothProfileController* aController)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mController);
 
   if (!sBluetoothHfpInterface) {
     BT_LOGR("sBluetoothHfpInterface is null");
     aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
     return;
   }
 
   bt_bdaddr_t deviceBdAddress;
   StringToBdAddressType(mDeviceAddress, &deviceBdAddress);
 
-  bt_status_t result = sBluetoothHfpInterface->Disconnect(&deviceBdAddress);
-  if (BT_STATUS_SUCCESS != result) {
-    BT_LOGR("Failed to disconnect: %x", result);
-    aController->NotifyCompletion(NS_LITERAL_STRING(ERR_DISCONNECTION_FAILED));
-    return;
-  }
+  mController = aController;
 
-  mController = aController;
+  sBluetoothHfpInterface->Disconnect(&deviceBdAddress,
+                                     new DisconnectResultHandler(this));
 }
 
 void
 BluetoothHfpManager::OnConnect(const nsAString& aErrorStr)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   /**
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.h
+++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.h
@@ -70,25 +70,29 @@ public:
   bthf_call_addrtype_t mType;
 };
 
 class BluetoothHfpManager : public BluetoothHfpManagerBase
                           , public BatteryObserver
 {
 public:
   BT_DECL_HFP_MGR_BASE
+
+  void OnConnectError();
+  void OnDisconnectError();
+
   virtual void GetName(nsACString& aName)
   {
     aName.AssignLiteral("HFP/HSP");
   }
 
   static BluetoothHfpManager* Get();
   virtual ~BluetoothHfpManager();
-  static void InitHfpInterface();
-  static void DeinitHfpInterface();
+  static void InitHfpInterface(BluetoothProfileResultHandler* aRes);
+  static void DeinitHfpInterface(BluetoothProfileResultHandler* aRes);
 
   bool ConnectSco();
   bool DisconnectSco();
 
   /**
    * @param aSend A boolean indicates whether we need to notify headset or not
    */
   void HandleCallStateChanged(uint32_t aCallIndex, uint16_t aCallState,
--- a/dom/browser-element/BrowserElementParent.jsm
+++ b/dom/browser-element/BrowserElementParent.jsm
@@ -621,16 +621,36 @@ BrowserElementParent.prototype = {
       extListener: null,
       onStartRequest: function(aRequest, aContext) {
         debug('DownloadListener - onStartRequest');
         let extHelperAppSvc =
           Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
           getService(Ci.nsIExternalHelperAppService);
         let channel = aRequest.QueryInterface(Ci.nsIChannel);
 
+        // First, we'll ensure the filename doesn't have any leading
+        // periods. We have to do it here to avoid ending up with a filename
+        // that's only an extension with no extension (e.g. Sending in
+        // '.jpeg' without stripping the '.' would result in a filename of
+        // 'jpeg' where we want 'jpeg.jpeg'.
+        _options.filename = _options.filename.replace(/^\.+/, "");
+
+        let ext = null;
+        let mimeSvc = extHelperAppSvc.QueryInterface(Ci.nsIMIMEService);
+        try {
+          ext = '.' + mimeSvc.getPrimaryExtension(channel.contentType, '');
+        } catch (e) { ext = null; }
+
+        // Check if we need to add an extension to the filename.
+        if (ext && !_options.filename.endsWith(ext)) {
+          _options.filename += ext;
+        }
+        // Set the filename to use when saving to disk.
+        channel.contentDispositionFilename = _options.filename;
+
         this.extListener =
           extHelperAppSvc.doContent(
               channel.contentType,
               aRequest,
               interfaceRequestor,
               true);
         this.extListener.onStartRequest(aRequest, aContext);
       },
--- a/dom/camera/GonkCameraSource.cpp
+++ b/dom/camera/GonkCameraSource.cpp
@@ -704,17 +704,22 @@ void GonkCameraSource::dataCallbackTimes
         Mutex::Autolock autoLock(mLock);
         if (!mStarted || (mNumFramesReceived == 0 && timestampUs < mStartTimeUs)) {
             CS_LOGV("Drop frame at %lld/%lld us", timestampUs, mStartTimeUs);
             releaseOneRecordingFrame(data);
             return;
         }
 
         if (mNumFramesReceived > 0) {
-            CHECK(timestampUs > mLastFrameTimestampUs);
+            if (timestampUs <= mLastFrameTimestampUs) {
+                CS_LOGE("Drop frame at %lld us, before last at %lld us",
+                    timestampUs, mLastFrameTimestampUs);
+                releaseOneRecordingFrame(data);
+                return;
+            }
             if (timestampUs - mLastFrameTimestampUs > mGlitchDurationThresholdUs) {
                 ++mNumGlitches;
             }
         }
 
         // May need to skip frame or modify timestamp. Currently implemented
         // by the subclass CameraSourceTimeLapse.
         if (skipCurrentFrame(timestampUs)) {
--- a/dom/nfc/tests/marionette/head.js
+++ b/dom/nfc/tests/marionette/head.js
@@ -90,17 +90,17 @@ let NCI = (function() {
     MORE_NOTIFICATIONS: 2
   };
 }());
 
 let TAG = (function() {
   function setData(re, flag, tnf, type, payload) {
     let deferred = Promise.defer();
     let cmd = "nfc tag set " + re +
-              " [" + flag + "," + tnf + "," + type + "," + payload + ",]";
+              " [" + flag + "," + tnf + "," + type + ",," + payload + "]";
 
     emulator.run(cmd, function(result) {
       is(result.pop(), "OK", "set NDEF data of tag" + re);
       deferred.resolve();
     });
 
     return deferred.promise;
   };
@@ -117,23 +117,23 @@ let TAG = (function() {
 
   return {
     setData: setData,
     clearData: clearData
   };
 }());
 
 let SNEP = (function() {
-  function put(dsap, ssap, flags, tnf, type, payload, id) {
+  function put(dsap, ssap, flags, tnf, type, id, payload) {
     let deferred = Promise.defer();
     let cmd = "nfc snep put " + dsap + " " + ssap + " [" + flags + "," +
                                                            tnf + "," +
                                                            type + "," +
-                                                           payload + "," +
-                                                           id + "]";
+                                                           id + "," +
+                                                           payload + "]";
     emulator.run(cmd, function(result) {
       is(result.pop(), "OK", "send SNEP PUT");
       deferred.resolve();
     });
 
     return deferred.promise;
   };
 
--- a/dom/nfc/tests/marionette/test_nfc_manager_tech_discovered_ndef.js
+++ b/dom/nfc/tests/marionette/test_nfc_manager_tech_discovered_ndef.js
@@ -1,18 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 30000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let tnf = NDEF.TNF_WELL_KNOWN;
 let type = "U";
+let id = "";
 let payload = "https://www.example.com";
-let id = "";
 
 let ndef = null;
 
 function handleTechnologyDiscoveredRE0(msg) {
   log("Received 'nfc-manager-tech-discovered'");
   is(msg.type, "techDiscovered", "check for correct message type");
   is(msg.techList[0], "P2P", "check for correct tech type");
 
@@ -30,17 +30,17 @@ function handleTechnologyDiscoveredRE0(m
 
 function testReceiveNDEF() {
   log("Running 'testReceiveNDEF'");
   window.navigator.mozSetMessageHandler(
     "nfc-manager-tech-discovered", handleTechnologyDiscoveredRE0);
   toggleNFC(true)
     .then(() => NCI.activateRE(emulator.P2P_RE_INDEX_0))
     .then(() => SNEP.put(SNEP.SAP_NDEF, SNEP.SAP_NDEF, 0, tnf, btoa(type),
-                         btoa(payload), btoa(id)));
+                         btoa(id), btoa(payload)));
 }
 
 let tests = [
   testReceiveNDEF
 ];
 
 SpecialPowers.pushPermissions(
   [{'type': 'nfc', 'allow': true,
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -385,16 +385,20 @@
         <provider android:name="org.mozilla.gecko.db.HomeProvider"
                   android:authorities="@ANDROID_PACKAGE_NAME@.db.home"
                   android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
 
         <provider android:name="org.mozilla.gecko.db.ReadingListProvider"
                   android:authorities="@ANDROID_PACKAGE_NAME@.db.readinglist"
                   android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
 
+        <provider android:name="org.mozilla.gecko.db.SearchHistoryProvider"
+                  android:authorities="@ANDROID_PACKAGE_NAME@.db.searchhistory"
+                  android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
+
         <service
             android:exported="false"
             android:name="org.mozilla.gecko.updater.UpdateService"
             android:process="@MANGLED_ANDROID_PACKAGE_NAME@.UpdateService">
         </service>
 
         <service
             android:exported="false"
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -20,16 +20,17 @@ import org.json.JSONObject;
 
 import org.mozilla.gecko.DynamicToolbar.PinReason;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
+import org.mozilla.gecko.db.BrowserContract.SearchHistory;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.SuggestedSites;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.LoadFaviconTask;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
@@ -524,16 +525,17 @@ public class BrowserApp extends GeckoApp
 
         mFindInPageBar = (FindInPageBar) findViewById(R.id.find_in_page);
         mMediaCastingBar = (MediaCastingBar) findViewById(R.id.media_casting);
 
         EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this,
             "Menu:Update",
             "Reader:Added",
             "Reader:FaviconRequest",
+            "Search:Keyword",
             "Prompt:ShowTop",
             "Accounts:Exist");
 
         EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener)this,
             "Accounts:Create",
             "CharEncoding:Data",
             "CharEncoding:State",
             "Feedback:LastUrl",
@@ -931,16 +933,17 @@ public class BrowserApp extends GeckoApp
             mBrowserHealthReporter.uninit();
             mBrowserHealthReporter = null;
         }
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener)this,
             "Menu:Update",
             "Reader:Added",
             "Reader:FaviconRequest",
+            "Search:Keyword",
             "Prompt:ShowTop",
             "Accounts:Exist");
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener)this,
             "Accounts:Create",
             "CharEncoding:Data",
             "CharEncoding:State",
             "Feedback:LastUrl",
@@ -1378,16 +1381,18 @@ public class BrowserApp extends GeckoApp
                 }
 
             } else if (event.equals("Reader:Added")) {
                 final int result = message.getInt("result");
                 handleReaderAdded(result, messageToReadingListContentValues(message));
             } else if (event.equals("Reader:FaviconRequest")) {
                 final String url = message.getString("url");
                 handleReaderFaviconRequest(url);
+            } else if (event.equals("Search:Keyword")) {
+                storeSearchQuery(message.getString("query"));
             } else if (event.equals("Prompt:ShowTop")) {
                 // Bring this activity to front so the prompt is visible..
                 Intent bringToFrontIntent = new Intent();
                 bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS_NAME);
                 bringToFrontIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
                 startActivity(bringToFrontIntent);
             } else if (event.equals("Accounts:Exist")) {
                 final String kind = message.getString("kind");
@@ -1801,16 +1806,37 @@ public class BrowserApp extends GeckoApp
             message.put("location", where);
             message.put("identifier", identifier);
             EventDispatcher.getInstance().dispatchEvent(message, null);
         } catch (Exception e) {
             Log.w(LOGTAG, "Error recording search.", e);
         }
     }
 
+    /**
+     * Store search query in SearchHistoryProvider.
+     *
+     * @param query
+     *        a search query to store. We won't store empty queries.
+     */
+    private void storeSearchQuery(String query) {
+        if (TextUtils.isEmpty(query)) {
+            return;
+        }
+
+        final ContentValues values = new ContentValues();
+        values.put(SearchHistory.QUERY, query);
+        ThreadUtils.postToBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                getContentResolver().insert(SearchHistory.CONTENT_URI, values);
+            }
+        });
+    }
+
     void filterEditingMode(String searchTerm, AutocompleteHandler handler) {
         if (TextUtils.isEmpty(searchTerm)) {
             hideBrowserSearch();
         } else {
             showBrowserSearch();
             mBrowserSearch.filter(searchTerm, handler);
         }
     }
@@ -2844,16 +2870,17 @@ public class BrowserApp extends GeckoApp
             openUrlAndStopEditing(url);
         }
     }
 
     // BrowserSearch.OnSearchListener
     @Override
     public void onSearch(SearchEngine engine, String text) {
         recordSearch(engine, "barsuggest");
+        storeSearchQuery(text);
         openUrlAndStopEditing(text, engine.name);
     }
 
     // BrowserSearch.OnEditSuggestionListener
     @Override
     public void onEditSuggestion(String suggestion) {
         mBrowserToolbar.onEditSuggestion(suggestion);
     }
--- a/mobile/android/base/resources/layout/remote_tabs_setup_panel.xml
+++ b/mobile/android/base/resources/layout/remote_tabs_setup_panel.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <LinearLayout android:id="@+id/remote_tabs_setup_containing_layout"
-                  style="@style/TabsPanelFrame"
+                  style="@style/TabsPanelFrame.RemoteTabs"
                   android:layout_width="match_parent"
                   android:layout_height="match_parent">
 
         <LinearLayout style="@style/TabsPanelSection"
                       android:layout_width="match_parent"
                       android:layout_height="wrap_content">
 
            <TextView style="@style/TabsPanelItem.TextAppearance.Header"
--- a/mobile/android/base/resources/layout/remote_tabs_verification_panel.xml
+++ b/mobile/android/base/resources/layout/remote_tabs_verification_panel.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <LinearLayout android:id="@+id/remote_tabs_verification_containing_layout"
-                  style="@style/TabsPanelFrame"
+                  style="@style/TabsPanelFrame.RemoteTabs"
                   android:layout_width="match_parent"
                   android:layout_height="match_parent">
 
         <LinearLayout style="@style/TabsPanelSection"
                       android:layout_width="match_parent"
                       android:layout_height="wrap_content">
 
             <TextView style="@style/TabsPanelItem.TextAppearance.Header.FXAccounts"
--- a/mobile/android/base/resources/values-large-land-v11/styles.xml
+++ b/mobile/android/base/resources/values-large-land-v11/styles.xml
@@ -59,18 +59,24 @@
         <item name="android:paddingLeft">0dp</item>
         <item name="android:paddingRight">0dp</item>
     </style>
 
     <style name="TabsPanelFrame.PrivateTabs">
         <!-- We set values in tablet portrait. -->
     </style>
 
+    <style name="TabsPanelFrame.RemoteTabs">
+        <item name="android:paddingLeft">0dp</item>
+        <item name="android:paddingRight">0dp</item>
+    </style>
+
     <style name="TabsPanelSection" parent="TabsPanelSectionBase">
-        <!-- To override the values-land style. -->
+        <item name="android:layout_marginLeft">20dp</item>
+        <item name="android:layout_marginRight">20dp</item>
     </style>
 
     <style name="TabsPanelSection.PrivateTabs">
         <!-- We set values in tablet portrait. -->
     </style>
 
     <style name="TabsPanelSection.PrivateTabs.Header">
         <!-- We set values in tablet portrait. -->
@@ -81,18 +87,16 @@
         <!-- To override the values-land style. -->
     </style>
 
     <style name="TabsPanelItem.Button" parent="TabsPanelItem.ButtonBase">
         <item name="android:paddingTop">12dp</item>
         <item name="android:paddingBottom">12dp</item>
         <item name="android:paddingLeft">6dp</item>
         <item name="android:paddingRight">6dp</item>
-        <item name="android:layout_marginLeft">12dp</item>
-        <item name="android:layout_marginRight">12dp</item>
         <item name="android:textSize">16sp</item>
     </style>
 
     <style name="TabsPanelItem.TextAppearance.Header.FXAccounts">
         <item name="android:visibility">gone</item>
     </style>
 
     <style name="TabsPanelItem.TextAppearance.Header.PrivateTabs">
--- a/mobile/android/base/resources/values-large-v11/styles.xml
+++ b/mobile/android/base/resources/values-large-v11/styles.xml
@@ -90,16 +90,21 @@
     </style>
 
     <style name="Widget.HomeBanner">
         <item name="android:paddingLeft">32dp</item>
         <item name="android:paddingRight">32dp</item>
     </style>
 
     <!-- Tabs panel -->
+    <style name="TabsPanelFrame.RemoteTabs" parent="TabsPanelFrameBase">
+        <item name="android:paddingLeft">112dp</item>
+        <item name="android:paddingRight">112dp</item>
+    </style>
+
     <style name="TabsPanelFrame.PrivateTabs">
         <item name="android:orientation">horizontal</item>
         <item name="android:paddingTop">0dp</item>
     </style>
 
     <style name="TabsPanelSection.PrivateTabs">
         <item name="android:layout_weight">1</item>
     </style>
@@ -113,24 +118,9 @@
     </style>
 
     <style name="TabsPanelItem.TextAppearance.Linkified.LearnMore">
         <item name="android:layout_height">match_parent</item>
         <item name="android:gravity">center</item>
         <item name="android:layout_gravity">center</item>
     </style>
 
-    <!-- Tabs panel -->
-    <style name="TabsPanelFrame" parent="TabsPanelFrameBase">
-        <item name="android:paddingLeft">84dp</item>
-        <item name="android:paddingRight">84dp</item>
-    </style>
-
-    <style name="TabsPanelItem.Button" parent="TabsPanelItem.ButtonBase">
-        <item name="android:paddingTop">18dp</item>
-        <item name="android:paddingBottom">18dp</item>
-        <item name="android:paddingLeft">9dp</item>
-        <item name="android:paddingRight">9dp</item>
-        <item name="android:layout_marginLeft">90dp</item>
-        <item name="android:layout_marginRight">90dp</item>
-    </style>
-
 </resources>
--- a/mobile/android/base/resources/values-xlarge-land-v11/styles.xml
+++ b/mobile/android/base/resources/values-xlarge-land-v11/styles.xml
@@ -24,19 +24,22 @@
     <style name="Widget.Home.HistoryTabWidget">
         <item name="android:showDividers">beginning|middle|end</item>
         <item name="android:dividerPadding">0dp</item>
         <item name="android:paddingLeft">100dp</item>
         <item name="android:paddingTop">30dp</item>
     </style>
 
     <!-- Tabs panel -->
+    <style name="TabsPanelFrame.RemoteTabs" parent="TabsPanelFrameBase">
+        <item name="android:paddingLeft">0dp</item>
+        <item name="android:paddingRight">0dp</item>
+    </style>
+
     <style name="TabsPanelItem.Button" parent="TabsPanelItem.ButtonBase">
         <item name="android:paddingTop">12dp</item>
         <item name="android:paddingBottom">12dp</item>
         <item name="android:paddingLeft">6dp</item>
         <item name="android:paddingRight">6dp</item>
-        <item name="android:layout_marginLeft">12dp</item>
-        <item name="android:layout_marginRight">12dp</item>
         <item name="android:textSize">16dp</item>
     </style>
 
 </resources>
--- a/mobile/android/base/resources/values-xlarge-v11/styles.xml
+++ b/mobile/android/base/resources/values-xlarge-v11/styles.xml
@@ -26,18 +26,14 @@
 
     <style name="Widget.Home.HistoryTabWidget">
         <item name="android:showDividers">beginning|middle|end</item>
         <item name="android:dividerPadding">0dp</item>
         <item name="android:paddingTop">30dp</item>
     </style>
 
     <!-- Tabs panel -->
-    <style name="TabsPanelItem.Button" parent="TabsPanelItem.ButtonBase">
-        <item name="android:paddingTop">18dp</item>
-        <item name="android:paddingBottom">18dp</item>
-        <item name="android:paddingLeft">9dp</item>
-        <item name="android:paddingRight">9dp</item>
-        <item name="android:layout_marginLeft">180dp</item>
-        <item name="android:layout_marginRight">180dp</item>
+    <style name="TabsPanelFrame.RemoteTabs" parent="TabsPanelFrameBase">
+        <item name="android:paddingLeft">212dp</item>
+        <item name="android:paddingRight">212dp</item>
     </style>
 
 </resources>
--- a/mobile/android/base/toolbar/PageActionLayout.java
+++ b/mobile/android/base/toolbar/PageActionLayout.java
@@ -10,40 +10,45 @@ import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.util.AttributeSet;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
+import java.util.Iterator;
+import java.util.List;
 import java.util.UUID;
 import java.util.ArrayList;
 
 public class PageActionLayout extends LinearLayout implements NativeEventListener,
                                                               View.OnClickListener,
                                                               View.OnLongClickListener {
     private static final String MENU_BUTTON_KEY = "MENU_BUTTON_KEY";
     private static final int DEFAULT_PAGE_ACTIONS_SHOWN = 2;
 
-    private ArrayList<PageAction> mPageActionList;
+    private final Context mContext;
+    private final LinearLayout mLayout;
+    private final List<PageAction> mPageActionList;
+
     private GeckoPopupMenu mPageActionsMenu;
-    private Context mContext;
-    private LinearLayout mLayout;
 
     // By default it's two, can be changed by calling setNumberShown(int)
     private int mMaxVisiblePageActions;
 
     public PageActionLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
         mLayout = this;
@@ -53,91 +58,117 @@ public class PageActionLayout extends Li
         refreshPageActionIcons();
 
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
             "PageActions:Add",
             "PageActions:Remove");
     }
 
     private void setNumberShown(int count) {
+        ThreadUtils.assertOnUiThread();
+
         mMaxVisiblePageActions = count;
 
-        for(int index = 0; index < count; index++) {
-            if ((this.getChildCount() - 1) < index) {
+        for (int index = 0; index < count; index++) {
+            if ((getChildCount() - 1) < index) {
                 mLayout.addView(createImageButton());
             }
         }
     }
 
     public void onDestroy() {
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
             "PageActions:Add",
             "PageActions:Remove");
     }
 
     @Override
-    public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
+    public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) {
+        // NativeJSObject cannot be used off of the Gecko thread, so convert it to a Bundle.
+        final Bundle bundle = message.toBundle();
+
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                handleUiMessage(event, bundle);
+            }
+        });
+    }
+
+    private void handleUiMessage(final String event, final Bundle message) {
+        ThreadUtils.assertOnUiThread();
+
         if (event.equals("PageActions:Add")) {
             final String id = message.getString("id");
             final String title = message.getString("title");
-            final String imageURL = message.optString("icon", null);
-            final boolean mImportant = message.getBoolean("important");
+            final String imageURL = message.getString("icon");
+            final boolean important = message.getBoolean("important");
 
             addPageAction(id, title, imageURL, new OnPageActionClickListeners() {
                 @Override
                 public void onClick(String id) {
                     GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:Clicked", id));
                 }
 
                 @Override
                 public boolean onLongClick(String id) {
                     GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:LongClicked", id));
                     return true;
                 }
-            }, mImportant);
+            }, important);
         } else if (event.equals("PageActions:Remove")) {
             final String id = message.getString("id");
 
             removePageAction(id);
         }
     }
 
-    private void addPageAction(final String id, final String title, final String imageData, final OnPageActionClickListeners mOnPageActionClickListeners, boolean mImportant) {
-        final PageAction pageAction = new PageAction(id, title, null, mOnPageActionClickListeners, mImportant);
+    private void addPageAction(final String id, final String title, final String imageData,
+            final OnPageActionClickListeners onPageActionClickListeners, boolean important) {
+        ThreadUtils.assertOnUiThread();
+
+        final PageAction pageAction = new PageAction(id, title, null, onPageActionClickListeners, important);
 
         int insertAt = mPageActionList.size();
-        while(insertAt > 0 && mPageActionList.get(insertAt-1).isImportant()) {
-          insertAt--;
+        while (insertAt > 0 && mPageActionList.get(insertAt - 1).isImportant()) {
+            insertAt--;
         }
         mPageActionList.add(insertAt, pageAction);
 
         BitmapUtils.getDrawable(mContext, imageData, new BitmapUtils.BitmapLoader() {
             @Override
             public void onBitmapFound(final Drawable d) {
                 if (mPageActionList.contains(pageAction)) {
                     pageAction.setDrawable(d);
                     refreshPageActionIcons();
                 }
             }
         });
     }
 
     private void removePageAction(String id) {
-        for(int i = 0; i < mPageActionList.size(); i++) {
-            if (mPageActionList.get(i).getID().equals(id)) {
-                mPageActionList.remove(i);
+        ThreadUtils.assertOnUiThread();
+
+        final Iterator<PageAction> iter = mPageActionList.iterator();
+        while (iter.hasNext()) {
+            final PageAction pageAction = iter.next();
+            if (pageAction.getID().equals(id)) {
+                iter.remove();
                 refreshPageActionIcons();
                 return;
             }
         }
     }
 
     private ImageButton createImageButton() {
+        ThreadUtils.assertOnUiThread();
+
+        final int width = mContext.getResources().getDimensionPixelSize(R.dimen.page_action_button_width);
         ImageButton imageButton = new ImageButton(mContext, null, R.style.UrlBar_ImageButton_Icon);
-        imageButton.setLayoutParams(new LayoutParams(mContext.getResources().getDimensionPixelSize(R.dimen.page_action_button_width), LayoutParams.MATCH_PARENT));
+        imageButton.setLayoutParams(new LayoutParams(width, LayoutParams.MATCH_PARENT));
         imageButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
         imageButton.setOnClickListener(this);
         imageButton.setOnLongClickListener(this);
         return imageButton;
     }
 
     @Override
     public void onClick(View v) {
@@ -158,122 +189,115 @@ public class PageActionLayout extends Li
             showMenu(v, mPageActionList.size() - mMaxVisiblePageActions + 1);
             return true;
         } else {
             return getPageActionWithId(buttonClickedId).onLongClick();
         }
     }
 
     private void setActionForView(final ImageButton view, final PageAction pageAction) {
+        ThreadUtils.assertOnUiThread();
+
         if (pageAction == null) {
             view.setTag(null);
-            ThreadUtils.postToUiThread(new Runnable() {
-                @Override
-                public void run () {
-                    view.setImageDrawable(null);
-                    view.setVisibility(View.GONE);
-                    view.setContentDescription(null);
-                }
-            });
+            view.setImageDrawable(null);
+            view.setVisibility(View.GONE);
+            view.setContentDescription(null);
             return;
         }
 
         view.setTag(pageAction.getID());
-        ThreadUtils.postToUiThread(new Runnable() {
-            @Override
-            public void run () {
-                view.setImageDrawable(pageAction.getDrawable());
-                view.setVisibility(View.VISIBLE);
-                view.setContentDescription(pageAction.getTitle());
-            }
-        });
+        view.setImageDrawable(pageAction.getDrawable());
+        view.setVisibility(View.VISIBLE);
+        view.setContentDescription(pageAction.getTitle());
     }
 
     private void refreshPageActionIcons() {
+        ThreadUtils.assertOnUiThread();
+
         final Resources resources = mContext.getResources();
-        for(int index = 0; index < this.getChildCount(); index++) {
-            final ImageButton v = (ImageButton)this.getChildAt(index);
-            final PageAction pageAction = getPageActionForViewAt(index);
+        for (int i = 0; i < this.getChildCount(); i++) {
+            final ImageButton v = (ImageButton) this.getChildAt(i);
+            final PageAction pageAction = getPageActionForViewAt(i);
 
-            // If there are more pageactions then buttons, set the menu icon. Otherwise set the page action's icon if there is a page action.
-            if (index == (this.getChildCount() - 1) && mPageActionList.size() > mMaxVisiblePageActions) {
+            // If there are more page actions than buttons, set the menu icon.
+            // Otherwise, set the page action's icon if there is a page action.
+            if ((i == this.getChildCount() - 1) && (mPageActionList.size() > mMaxVisiblePageActions)) {
                 v.setTag(MENU_BUTTON_KEY);
-                ThreadUtils.postToUiThread(new Runnable() {
-                    @Override
-                    public void run () {
-                        v.setImageDrawable(resources.getDrawable(R.drawable.icon_pageaction));
-                        v.setVisibility((pageAction != null) ? View.VISIBLE : View.GONE);
-                        v.setContentDescription(resources.getString(R.string.page_action_dropmarker_description));
-                    }
-                });
+                v.setImageDrawable(resources.getDrawable(R.drawable.icon_pageaction));
+                v.setVisibility((pageAction != null) ? View.VISIBLE : View.GONE);
+                v.setContentDescription(resources.getString(R.string.page_action_dropmarker_description));
             } else {
                 setActionForView(v, pageAction);
             }
         }
     }
 
     private PageAction getPageActionForViewAt(int index) {
+        ThreadUtils.assertOnUiThread();
+
         /**
          * We show the user the most recent pageaction added since this keeps the user aware of any new page actions being added
          * Also, the order of the pageAction is important i.e. if a page action is added, instead of shifting the pagactions to the
          * left to make space for the new one, it would be more visually appealing to have the pageaction appear in the blank space.
          *
          * buttonIndex is needed for this reason because every new View added to PageActionLayout gets added to the right of its neighbouring View.
          * Hence the button on the very leftmost has the index 0. We want our pageactions to start from the rightmost
          * and hence we maintain the insertion order of the child Views which is essentially the reverse of their index
          */
 
-        int buttonIndex = (this.getChildCount() - 1) - index;
-        int totalVisibleButtons = ((mPageActionList.size() < this.getChildCount()) ? mPageActionList.size() : this.getChildCount());
+        final int buttonIndex = (this.getChildCount() - 1) - index;
 
         if (mPageActionList.size() > buttonIndex) {
             // Return the pageactions starting from the end of the list for the number of visible pageactions.
-            return mPageActionList.get((mPageActionList.size() - totalVisibleButtons) + buttonIndex);
+            final int buttonCount = Math.min(mPageActionList.size(), getChildCount());
+            return mPageActionList.get((mPageActionList.size() - buttonCount) + buttonIndex);
         }
         return null;
     }
 
     private PageAction getPageActionWithId(String id) {
-        for(int i = 0; i < mPageActionList.size(); i++) {
-            PageAction pageAction = mPageActionList.get(i);
+        ThreadUtils.assertOnUiThread();
+
+        for (PageAction pageAction : mPageActionList) {
             if (pageAction.getID().equals(id)) {
                 return pageAction;
             }
         }
         return null;
     }
 
-    private void showMenu(View mPageActionButton, int toShow) {
+    private void showMenu(View pageActionButton, int toShow) {
+        ThreadUtils.assertOnUiThread();
+
         if (mPageActionsMenu == null) {
-            mPageActionsMenu = new GeckoPopupMenu(mPageActionButton.getContext(), mPageActionButton);
+            mPageActionsMenu = new GeckoPopupMenu(pageActionButton.getContext(), pageActionButton);
             mPageActionsMenu.inflate(0);
             mPageActionsMenu.setOnMenuItemClickListener(new GeckoPopupMenu.OnMenuItemClickListener() {
                 @Override
                 public boolean onMenuItemClick(MenuItem item) {
                     int id = item.getItemId();
-                    for(int i = 0; i < mPageActionList.size(); i++) {
+                    for (int i = 0; i < mPageActionList.size(); i++) {
                         PageAction pageAction = mPageActionList.get(i);
                         if (pageAction.key() == id) {
                             pageAction.onClick();
                             return true;
                         }
                     }
                     return false;
                 }
             });
         }
         Menu menu = mPageActionsMenu.getMenu();
         menu.clear();
 
-        for(int i = 0; i < mPageActionList.size(); i++) {
-            if (i < toShow) {
-                PageAction pageAction = mPageActionList.get(i);
-                MenuItem item = menu.add(Menu.NONE, pageAction.key(), Menu.NONE, pageAction.getTitle());
-                item.setIcon(pageAction.getDrawable());
-            }
+        for (int i = 0; i < mPageActionList.size() && i < toShow; i++) {
+            PageAction pageAction = mPageActionList.get(i);
+            MenuItem item = menu.add(Menu.NONE, pageAction.key(), Menu.NONE, pageAction.getTitle());
+            item.setIcon(pageAction.getDrawable());
         }
         mPageActionsMenu.show();
     }
 
     private static interface OnPageActionClickListeners {
         public void onClick(String id);
         public boolean onLongClick(String id);
     }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1590,16 +1590,17 @@ var BrowserApp = {
         // perform a keyword search on the selected tab.
         this.selectedTab.userSearch = aData;
 
         let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
         sendMessageToJava({
           type: "Search:Keyword",
           identifier: engine.identifier,
           name: engine.name,
+          query: aData
         });
         break;
 
       case "Browser:Quit":
         this.quit();
         break;
 
       case "SaveAs:PDF":
--- a/mobile/android/config/proguard.cfg
+++ b/mobile/android/config/proguard.cfg
@@ -14,16 +14,18 @@
 -keep public class * extends android.app.Service
 -keep public class * extends android.app.backup.BackupAgentHelper
 -keep public class * extends android.content.BroadcastReceiver
 -keep public class * extends android.content.ContentProvider
 -keep public class * extends android.preference.Preference
 -keep public class * extends org.mozilla.gecko.sync.syncadapter.SyncAdapter
 -keep class org.mozilla.gecko.sync.syncadapter.SyncAdapter
 
+-keep public class * extends android.support.v4.app.Fragment
+
 # Preserve all native method names and the names of their classes.
 -keepclasseswithmembernames class * {
     native <methods>;
 }
 
 -keepclasseswithmembers class * {
     public <init>(android.content.Context, android.util.AttributeSet, int);
 }
--- a/mobile/android/search/java/org/mozilla/search/Constants.java
+++ b/mobile/android/search/java/org/mozilla/search/Constants.java
@@ -16,9 +16,12 @@ package org.mozilla.search;
  */
 public class Constants {
 
     public static final String POSTSEARCH_FRAGMENT = "org.mozilla.search.POSTSEARCH_FRAGMENT";
     public static final String PRESEARCH_FRAGMENT = "org.mozilla.search.PRESEARCH_FRAGMENT";
     public static final String SEARCH_FRAGMENT = "org.mozilla.search.SEARCH_FRAGMENT";
 
     public static final String AUTOCOMPLETE_ROW_LIMIT = "5";
+
+    public static final String YAHOO_WEB_SEARCH_BASE_URL = "https://search.yahoo.com/search?p=";
+    public static final String YAHOO_WEB_SEARCH_RESULTS_FILTER = "//search.yahoo.com";
 }
--- a/mobile/android/search/java/org/mozilla/search/MainActivity.java
+++ b/mobile/android/search/java/org/mozilla/search/MainActivity.java
@@ -34,33 +34,33 @@ public class MainActivity extends Fragme
         super.onCreate(stateBundle);
         setContentView(R.layout.search_activity_main);
         startPresearch();
     }
 
     @Override
     public void onSearch(String s) {
         startPostsearch();
-        ((PostSearchFragment) getSupportFragmentManager().findFragmentById(R.id.gecko))
-                .setUrl("https://search.yahoo.com/search?p=" + Uri.encode(s));
+        ((PostSearchFragment) getSupportFragmentManager().findFragmentById(R.id.postsearch))
+                .startSearch(s);
     }
 
     private void startPresearch() {
         if (state != State.PRESEARCH) {
             state = State.PRESEARCH;
-            findViewById(R.id.gecko).setVisibility(View.INVISIBLE);
+            findViewById(R.id.postsearch).setVisibility(View.INVISIBLE);
             findViewById(R.id.presearch).setVisibility(View.VISIBLE);
         }
     }
 
     private void startPostsearch() {
         if (state != State.POSTSEARCH) {
             state = State.POSTSEARCH;
             findViewById(R.id.presearch).setVisibility(View.INVISIBLE);
-            findViewById(R.id.gecko).setVisibility(View.VISIBLE);
+            findViewById(R.id.postsearch).setVisibility(View.VISIBLE);
         }
     }
 
     @Override
     public void onBackPressed() {
         if (state == State.POSTSEARCH) {
             startPresearch();
         } else {
--- a/mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
+++ b/mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
@@ -1,100 +1,69 @@
 /* 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/. */
 
 package org.mozilla.search;
 
 import android.content.Intent;
+import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-
-import org.mozilla.gecko.GeckoView;
-import org.mozilla.gecko.GeckoViewChrome;
-import org.mozilla.gecko.GeckoViewContent;
-import org.mozilla.gecko.PrefsHelper;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
 
 public class PostSearchFragment extends Fragment {
 
     private static final String LOGTAG = "PostSearchFragment";
-    private GeckoView geckoView;
+    private WebView webview;
+
+    public PostSearchFragment() {}
 
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
                              Bundle savedInstanceState) {
         View mainView = inflater.inflate(R.layout.search_activity_detail, container, false);
 
 
-        geckoView = (GeckoView) mainView.findViewById(R.id.gecko_view);
-
-        geckoView.setChromeDelegate(new MyGeckoViewChrome());
-        geckoView.setContentDelegate(new SearchGeckoView());
-
-        PrefsHelper.setPref("privacy.clearOnShutdown.cache", true);
-        PrefsHelper.setPref("privacy.clearOnShutdown.cookies", true);
-
-
-        if (null == geckoView.getCurrentBrowser()) {
-            // This pageload allows Fennec to be loaded in a background fragment.
-            // Without supplying a URL, it doesn't look like Fennec will get loaded?
-            geckoView.addBrowser("https://search.yahoo.com/search?p=firefox%20android");
-
-        }
-
+        webview = (WebView) mainView.findViewById(R.id.webview);
+        webview.setWebViewClient(new WebViewClient(){
+            @Override
+            public void onPageStarted(WebView view, String url, Bitmap favicon) {
+                if (isSearchResultsPage(url)) {
+                    super.onPageStarted(view, url, favicon);
+                } else {
+                    webview.stopLoading();
+                    Intent i = new Intent(Intent.ACTION_VIEW);
+                    i.setData(Uri.parse(url));
+                    startActivity(i);
+                }
+            }
+        });
         return mainView;
     }
 
-
-    public void setUrl(String url) {
-        if (null == geckoView.getCurrentBrowser()) {
-            geckoView.addBrowser(url);
-        } else {
-            geckoView.getCurrentBrowser().loadUrl(url);
-        }
-    }
-
-
-    private static class MyGeckoViewChrome extends GeckoViewChrome {
-        @Override
-        public void onReady(GeckoView view) {
-            Log.i(LOGTAG, "Gecko is ready");
-
-            PrefsHelper.setPref("devtools.debugger.remote-enabled", true);
-
-            // The Gecko libraries have finished loading and we can use the rendering engine.
-            // Let's add a browser (required) and load a page into it.
-        }
-
+    /**
+     * Test if a given URL is a page of search results.
+     * <p>
+     * Search results pages will be shown in the embedded view.  Other pages are
+     * opened in external browsers.
+     *
+     * @param url to test.
+     * @return true if <code>url</code> is a page of search results.
+     */
+    protected boolean isSearchResultsPage(String url) {
+        return url.contains(Constants.YAHOO_WEB_SEARCH_RESULTS_FILTER);
     }
 
-
-    private class SearchGeckoView extends GeckoViewContent {
-
-        @Override
-        public void onPageStart(GeckoView geckoView, GeckoView.Browser browser, String s) {
-            Log.i("OnPageStart", s);
-            // Only load this page if it's the Yahoo search page that we're using.
-            // TODO: Make this check more robust, and allow for other search providers.
-            if (s.contains("//search.yahoo.com")) {
-                super.onPageStart(geckoView, browser, s);
-
+    public void startSearch(String query) {
+        setUrl(Constants.YAHOO_WEB_SEARCH_BASE_URL + Uri.encode(query));
+    }
 
-            } else {
-                browser.stop();
-                Intent i = new Intent(Intent.ACTION_VIEW);
-                i.setData(Uri.parse(s));
-                startActivity(i);
-            }
-        }
-
-        @Override
-        public void onPageShow(GeckoView geckoView, GeckoView.Browser browser) {
-
-        }
+    public void setUrl(String url) {
+        webview.loadUrl(url);
     }
 }
--- a/mobile/android/search/res/layout/search_activity_detail.xml
+++ b/mobile/android/search/res/layout/search_activity_detail.xml
@@ -1,20 +1,16 @@
 <!-- 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/. -->
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                 xmlns:tools="http://schemas.android.com/tools"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
-                android:paddingBottom="@dimen/activity_vertical_margin"
-                android:paddingLeft="@dimen/activity_horizontal_margin"
-                android:paddingRight="@dimen/activity_horizontal_margin"
-                android:paddingTop="@dimen/activity_vertical_margin"
                 tools:context="org.mozilla.search.PostSearchFragment">
 
-    <org.mozilla.gecko.GeckoView
-        android:id="@+id/gecko_view"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"/>
 
+    <WebView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/webview"/>
 </RelativeLayout>
--- a/mobile/android/search/res/layout/search_activity_main.xml
+++ b/mobile/android/search/res/layout/search_activity_main.xml
@@ -9,17 +9,17 @@
     android:layout_height="match_parent"
     android:orientation="vertical"
     tools:context=".MainActivity">
 
     <fragment
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:name="org.mozilla.search.PostSearchFragment"
-        android:id="@+id/gecko"
+        android:id="@+id/postsearch"
         />
 
     <fragment
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:name="org.mozilla.search.PreSearchFragment"
         android:id="@+id/presearch"
         />
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -352,20 +352,16 @@ pref("media.peerconnection.capture_delay
 pref("media.getusermedia.playout_delay", 50);
 #endif
 #else
 #ifdef ANDROID
 pref("media.navigator.enabled", true);
 #endif
 #endif
 
-pref("media.tabstreaming.width", 320);
-pref("media.tabstreaming.height", 240);
-pref("media.tabstreaming.time_per_frame", 40);
-
 // TextTrack support
 pref("media.webvtt.enabled", true);
 pref("media.webvtt.regions.enabled", false);
 
 // AudioTrack and VideoTrack support
 pref("media.track.enabled", false);
 
 // Whether to enable MediaSource support