merge m-c to oak
authorRobert Strong <robert.bugzilla@gmail.com>
Thu, 25 Sep 2014 17:19:23 -0700
changeset 491282 4f72efdd35d70c48b3452c6017debb8d5b5cc1ff
parent 491281 2050a94fd07e5bc02b8ce1246a51e33535d5136a (current diff)
parent 207311 9e3d649b80a224b48bc4a28d3d46c1e2e929ebc0 (diff)
child 491283 d4a44b660af118d80661f169ed0df3439aac64da
push id47343
push userbmo:dothayer@mozilla.com
push dateWed, 01 Mar 2017 22:58:58 +0000
milestone35.0a1
merge m-c to oak
b2g/installer/package-manifest.in
content/base/test/csp/file_csp_regexp_parsing.html
content/base/test/csp/file_csp_regexp_parsing.js
content/base/test/csp/test_csp_regexp_parsing.html
dom/media/nsIDOMMediaStream.idl
gfx/angle/src/compiler/translator/BuiltInFunctionEmulator.cpp.orig
gfx/angle/src/libGLESv2/renderer/d3d/d3d9/formatutils9.cpp.orig
js/src/jit-test/tests/basic/testReferenceErrorNameSuggestion-01.js
js/src/jit-test/tests/basic/testReferenceErrorNameSuggestion-02.js
js/src/jit-test/tests/basic/testReferenceErrorNameSuggestion-03.js
js/xpconnect/src/XPCShellImpl.cpp
layout/generic/nsObjectFrame.cpp
layout/generic/nsObjectFrame.h
media/libvpx/mingw.patch
media/libvpx/unified.patch
mobile/android/base/resources/drawable-large-hdpi-v11/favicon_none.png
mobile/android/base/resources/drawable-large-mdpi-v11/favicon_none.png
mobile/android/base/resources/drawable-large-xhdpi-v11/favicon_none.png
mobile/android/base/resources/drawable-large-xxhdpi-v11/favicon_none.png
testing/mochitest/mochitest_options.py
testing/mochitest/runtests.py
toolkit/xre/nsAppRunner.cpp
--- a/accessible/base/TextAttrs.cpp
+++ b/accessible/base/TextAttrs.cpp
@@ -455,17 +455,17 @@ TextAttrsMgr::FontFamilyTextAttr::
 bool
 TextAttrsMgr::FontFamilyTextAttr::
   GetFontFamily(nsIFrame* aFrame, nsString& aFamily)
 {
   nsRefPtr<nsFontMetrics> fm;
   nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm));
 
   gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
-  gfxFont* font = fontGroup->GetFontAt(0);
+  gfxFont* font = fontGroup->GetFirstValidFont();
   gfxFontEntry* fontEntry = font->GetFontEntry();
   aFamily = fontEntry->FamilyName();
   return true;
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // FontSizeTextAttr
@@ -613,17 +613,17 @@ TextAttrsMgr::FontWeightTextAttr::
   GetFontWeight(nsIFrame* aFrame)
 {
   // nsFont::width isn't suitable here because it's necessary to expose real
   // value of font weight (used font might not have some font weight values).
   nsRefPtr<nsFontMetrics> fm;
   nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm));
 
   gfxFontGroup *fontGroup = fm->GetThebesFontGroup();
-  gfxFont *font = fontGroup->GetFontAt(0);
+  gfxFont *font = fontGroup->GetFirstValidFont();
 
   // When there doesn't exist a bold font in the family and so the rendering of
   // a non-bold font face is changed so that the user sees what looks like a
   // bold font, i.e. synthetic bolding is used. IsSyntheticBold method is only
   // needed on Mac, but it is "safe" to use on all platforms.  (For non-Mac
   // platforms it always return false.)
   if (font->IsSyntheticBold())
     return 700;
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -52,17 +52,17 @@
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 
 #include "nsImageFrame.h"
 #include "nsIObserverService.h"
 #include "nsLayoutUtils.h"
-#include "nsObjectFrame.h"
+#include "nsPluginFrame.h"
 #include "nsSVGPathGeometryFrame.h"
 #include "nsTreeBodyFrame.h"
 #include "nsTreeColumns.h"
 #include "nsTreeUtils.h"
 #include "nsXBLPrototypeBinding.h"
 #include "nsXBLBinding.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/dom/DOMStringList.h"
@@ -258,21 +258,21 @@ public:
 private:
   nsCOMPtr<nsIContent> mContent;
 };
 
 NS_IMPL_ISUPPORTS(PluginTimerCallBack, nsITimerCallback)
 #endif
 
 already_AddRefed<Accessible>
-nsAccessibilityService::CreatePluginAccessible(nsObjectFrame* aFrame,
+nsAccessibilityService::CreatePluginAccessible(nsPluginFrame* aFrame,
                                                nsIContent* aContent,
                                                Accessible* aContext)
 {
-  // nsObjectFrame means a plugin, so we need to use the accessibility support
+  // nsPluginFrame means a plugin, so we need to use the accessibility support
   // of the plugin.
   if (aFrame->GetRect().IsEmpty())
     return nullptr;
 
 #if defined(XP_WIN) || defined(MOZ_ACCESSIBILITY_ATK)
   nsRefPtr<nsNPAPIPluginInstance> pluginInstance;
   if (NS_SUCCEEDED(aFrame->GetPluginInstance(getter_AddRefs(pluginInstance))) &&
       pluginInstance) {
@@ -1614,18 +1614,18 @@ nsAccessibilityService::CreateAccessible
 
     case eImageType:
       newAcc = new ImageAccessibleWrap(aContent, document);
       break;
     case eOuterDocType:
       newAcc = new OuterDocAccessible(aContent, document);
       break;
     case ePluginType: {
-      nsObjectFrame* objectFrame = do_QueryFrame(aFrame);
-      newAcc = CreatePluginAccessible(objectFrame, aContent, aContext);
+      nsPluginFrame* pluginFrame = do_QueryFrame(aFrame);
+      newAcc = CreatePluginAccessible(pluginFrame, aContent, aContext);
       break;
     }
     case eTextLeafType:
       newAcc = new TextLeafAccessibleWrap(aContent, document);
       break;
     default:
       MOZ_ASSERT(false);
       break;
--- a/accessible/base/nsAccessibilityService.h
+++ b/accessible/base/nsAccessibilityService.h
@@ -10,17 +10,17 @@
 
 #include "mozilla/a11y/DocManager.h"
 #include "mozilla/a11y/FocusManager.h"
 #include "mozilla/a11y/SelectionManager.h"
 
 #include "nsIObserver.h"
 
 class nsImageFrame;
-class nsObjectFrame;
+class nsPluginFrame;
 class nsITreeView;
 
 namespace mozilla {
 namespace a11y {
 
 class ApplicationAccessible;
 
 /**
@@ -58,17 +58,17 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIACCESSIBLERETRIEVAL
   NS_DECL_NSIOBSERVER
 
   // nsIAccessibilityService
   virtual Accessible* GetRootDocumentAccessible(nsIPresShell* aPresShell,
                                                 bool aCanCreate);
   already_AddRefed<Accessible>
-    CreatePluginAccessible(nsObjectFrame* aFrame, nsIContent* aContent,
+    CreatePluginAccessible(nsPluginFrame* aFrame, nsIContent* aContent,
                            Accessible* aContext);
 
   /**
    * Adds/remove ATK root accessible for gtk+ native window to/from children
    * of the application accessible.
    */
   virtual Accessible* AddNativeRootAccessible(void* aAtkAccessible);
   virtual void RemoveNativeRootAccessible(Accessible* aRootAccessible);
--- a/accessible/interfaces/nsIAccessibilityService.h
+++ b/accessible/interfaces/nsIAccessibilityService.h
@@ -19,17 +19,17 @@ class Accessible;
 
 } // namespace a11y
 } // namespace mozilla
 
 class nsINode;
 class nsIContent;
 class nsIFrame;
 class nsIPresShell;
-class nsObjectFrame;
+class nsPluginFrame;
 
 // 10ff6dca-b219-4b64-9a4c-67a62b86edce
 #define NS_IACCESSIBILITYSERVICE_IID \
 { 0x84dd9182, 0x6639, 0x4377, \
  { 0xa4, 0x13, 0xad, 0xe1, 0xae, 0x4e, 0x52, 0xdd } }
 
 class nsIAccessibilityService : public nsIAccessibleRetrieval
 {
--- a/accessible/jsat/TraversalRules.jsm
+++ b/accessible/jsat/TraversalRules.jsm
@@ -99,31 +99,31 @@ var gSimpleTraversalRoles =
    // Used for traversing in to child OOP frames.
    Roles.INTERNAL_FRAME];
 
 var gSimpleMatchFunc = function gSimpleMatchFunc(aAccessible) {
   // An object is simple, if it either has a single child lineage,
   // or has a flat subtree.
   function isSingleLineage(acc) {
     for (let child = acc; child; child = child.firstChild) {
-      if (child.childCount > 1) {
+      if (Utils.visibleChildCount(child) > 1) {
         return false;
       }
     }
     return true;
   }
 
   function isFlatSubtree(acc) {
     for (let child = acc.firstChild; child; child = child.nextSibling) {
       // text leafs inherit the actionCount of any ancestor that has a click
       // listener.
       if ([Roles.TEXT_LEAF, Roles.STATICTEXT].indexOf(child.role) >= 0) {
         continue;
       }
-      if (child.childCount > 0 || child.actionCount > 0) {
+      if (Utils.visibleChildCount(child) > 0 || child.actionCount > 0) {
         return false;
       }
     }
     return true;
   }
 
   switch (aAccessible.role) {
   case Roles.COMBOBOX:
--- a/accessible/jsat/Utils.jsm
+++ b/accessible/jsat/Utils.jsm
@@ -355,16 +355,26 @@ this.Utils = { // jshint ignore:line
 
   isHidden: function isHidden(aAccessible) {
     // Need to account for aria-hidden, so can't just check for INVISIBLE
     // state.
     let hidden = Utils.getAttributes(aAccessible).hidden;
     return hidden && hidden === 'true';
   },
 
+  visibleChildCount: function visibleChildCount(aAccessible) {
+    let count = 0;
+    for (let child = aAccessible.firstChild; child; child = child.nextSibling) {
+      if (!this.isHidden(child)) {
+        ++count;
+      }
+    }
+    return count;
+  },
+
   inHiddenSubtree: function inHiddenSubtree(aAccessible) {
     for (let acc=aAccessible; acc; acc=acc.parent) {
       if (this.isHidden(acc)) {
         return true;
       }
     }
     return false;
   },
--- a/accessible/tests/mochitest/jsat/doc_traversal.html
+++ b/accessible/tests/mochitest/jsat/doc_traversal.html
@@ -1,13 +1,18 @@
 <!DOCTYPE html>
 <html>
 <head>
   <title>Traversal Rule test document</title>
   <meta charset="utf-8" />
+  <style>
+    .content:before {
+      content: "Content";
+    }
+  </style>
 </head>
 <body>
   <h3 id="heading-1">A small first heading</h3>
   <form>
     <label for="input-1-1">Name:</label>
     <input id="input-1-1">
     <label id="label-1-2">Favourite Ice Cream Flavour:<input id="input-1-2"></label>
     <button id="button-1-1">Magic Button</button>
@@ -91,16 +96,35 @@
     <td>3</td>
     <td>1</td>
   </tr>
   <tr>
     <td>4</td>
     <td>1</td>
   </tr>
   </table>
+  <section id="grid" role="grid">
+    <ol role="row">
+      <li role="presentation"></li>
+      <li role="columnheader" aria-label="Sunday">S</li>
+      <li role="columnheader">M</li>
+    </ol>
+    <ol role="row">
+      <li role="rowheader" aria-label="Week 1">1</li>
+      <li role="gridcell"><span>3</span><div></div></li>
+      <li role="gridcell"><span>4</span><div>7</div></li>
+    </ol>
+    <ol role="row">
+      <li role="rowheader">2</li>
+      <li role="gridcell"><span>5</span><div role="presentation">8</div></li>
+      <li id="gridcell4" role="gridcell">
+        <span>6</span><div aria-hidden="true"><div class="content"></div></div>
+      </li>
+    </ol>
+  </section>
   <div id="separator-2" role="separator">Just an innocuous separator</div>
   <table id="table-2">
     <thead>
       <tr>
         <th>Dirty Words</th>
         <th>Meaning</th>
       </tr>
     </thead>
--- a/accessible/tests/mochitest/jsat/test_traversal.html
+++ b/accessible/tests/mochitest/jsat/test_traversal.html
@@ -89,17 +89,17 @@
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.Anchor, null,
                              ['anchor-1', 'anchor-2']);
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.Separator, null,
                              ['separator-1', 'separator-2']);
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.Table, null,
-                             ['table-1', 'table-2']);
+                             ['table-1', 'grid', 'table-2']);
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.Simple, null,
                              ['heading-1', 'Name:', 'input-1-1', 'label-1-2',
                               'button-1-1', 'Radios are old: ', 'radio-1-1',
                               'Radios are new: ', 'radio-1-2', 'Password:',
                               'input-1-3', 'Unlucky number:', 'input-1-4',
                               'button-1-2', 'Check me: ', 'checkbox-1-1',
                               'select-1-1', 'Value 1', 'Value 2', 'Value 3',
@@ -109,23 +109,25 @@
                               'button-1-3', 'heading-2', 'heading-3',
                               'button-2-1', 'button-2-2', 'button-2-3',
                               'button-2-4', 'Programming Language',
                               'A esoteric weapon wielded by only the most ' +
                               'formidable warriors, for its unrelenting strict' +
                               ' power is unfathomable.',
                               '• Lists of Programming Languages', 'Lisp ',
                               '1. Scheme', '2. Racket', '3. Clojure',
-                              '4. Standard Lisp', 'link-0', ' Lisp', 'checkbox-1-5',
-                              ' LeLisp', '• JavaScript', 'heading-5',
-                              'image-2', 'image-3', 'Not actually an image',
-                              'link-1', 'anchor-1', 'link-2', 'anchor-2', 'link-3',
-                              '3', '1', '4', '1', 'Just an innocuous separator',
-                              'Dirty Words', 'Meaning', 'Mud', 'Wet Dirt',
-                              'Dirt', 'Messy Stuff']);
+                              '4. Standard Lisp', 'link-0', ' Lisp',
+                              'checkbox-1-5', ' LeLisp', '• JavaScript',
+                              'heading-5', 'image-2', 'image-3',
+                              'Not actually an image', 'link-1', 'anchor-1',
+                              'link-2', 'anchor-2', 'link-3', '3', '1', '4',
+                              '1', 'S', 'M', '1', '3', '4', '7', '2', '5', '8',
+                              '6', 'Just an innocuous separator', 'Dirty Words',
+                              'Meaning', 'Mud', 'Wet Dirt', 'Dirt',
+                              'Messy Stuff']);
 
       gQueue.invoke();
     }
 
     SimpleTest.waitForExplicitFinish();
     addLoadEvent(function () {
       /* We open a new browser because we need to test with a top-level content
          document. */
--- a/accessible/windows/sdn/sdnTextAccessible.cpp
+++ b/accessible/windows/sdn/sdnTextAccessible.cpp
@@ -177,17 +177,18 @@ sdnTextAccessible::get_fontFamily(BSTR _
 
   nsIFrame* frame = mAccessible->GetFrame();
   if (!frame)
     return E_FAIL;
 
   nsRefPtr<nsFontMetrics> fm;
   nsLayoutUtils::GetFontMetricsForFrame(frame, getter_AddRefs(fm));
 
-  const nsString& name = fm->GetThebesFontGroup()->GetFontAt(0)->GetName();
+  const nsString& name =
+    fm->GetThebesFontGroup()->GetFirstValidFont()->GetName();
   if (name.IsEmpty())
     return S_FALSE;
 
   *aFontFamily = ::SysAllocStringLen(name.get(), name.Length());
   return *aFontFamily ? S_OK : E_OUTOFMEMORY;
 
   A11Y_TRYBLOCK_END
 }
--- a/addon-sdk/mach_commands.py
+++ b/addon-sdk/mach_commands.py
@@ -43,24 +43,24 @@ class MachCommands(MachCommandBase):
     @Command('generate-addon-sdk-moz-build', category='misc',
         description='Generates the moz.build file for the addon-sdk/ directory.')
     def run_addon_sdk_moz_build(self, **params):
         addon_sdk_dir = mozpath.join(self.topsrcdir, 'addon-sdk')
         js_src_dir = mozpath.join(addon_sdk_dir, 'source/lib')
         dirs_to_files = {}
 
         for path, dirs, files in os.walk(js_src_dir):
-            js_files = [f for f in files if f.endswith(('.js', '.jsm'))]
+            js_files = [f for f in files if f.endswith(('.js', '.jsm', '.html'))]
             if not js_files:
                 continue
 
             relative = mozpath.relpath(path, js_src_dir)
             dirs_to_files[relative] = js_files
 
-        moz_build = """# AUTOMATICALLY GENERATED FROM moz.build.in AND mach.  DO NOT EDIT.
+        moz_build = """# AUTOMATICALLY GENERATED FROM mozbuild.template AND mach.  DO NOT EDIT.
 # 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/.
 
 %(moz-build-template)s
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
 %(non-b2g-modules)s
 %(always-on-modules)s"""
--- a/addon-sdk/moz.build
+++ b/addon-sdk/moz.build
@@ -394,16 +394,17 @@ EXTRA_JS_MODULES.commonjs.sdk.tab += [
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.ui.button.view += [
     'source/lib/sdk/ui/button/view/events.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.ui.frame += [
     'source/lib/sdk/ui/frame/model.js',
+    'source/lib/sdk/ui/frame/view.html',
     'source/lib/sdk/ui/frame/view.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.ui.state += [
     'source/lib/sdk/ui/state/events.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.ui.toolbar += [
--- a/addon-sdk/mozbuild.template
+++ b/addon-sdk/mozbuild.template
@@ -3,13 +3,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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini']
 JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
 
-DIRS += ["source/modules/system"]
-
 EXTRA_JS_MODULES.sdk += [
     'source/app-extension/bootstrap.js',
 ]
+
+EXTRA_JS_MODULES.sdk.system += [
+    'source/modules/system/Startup.js',
+    'source/modules/system/XulApp.js',
+]
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -739,24 +739,24 @@ pref("hal.processPriorityManager.gonk.BA
 
 // Processes get this niceness when they have low CPU priority.
 pref("hal.processPriorityManager.gonk.LowCPUNice", 18);
 
 // By default the compositor thread on gonk runs without real-time priority.  RT
 // priority can be enabled by setting this pref to a value between 1 and 99.
 // Note that audio processing currently runs at RT priority 2 or 3 at most.
 //
-// If RT priority is disabled, then the compositor nice value is used.  The
-// code will default to ANDROID_PRIORITY_URGENT_DISPLAY which is -8.  Per gfx
-// request we are keeping the compositor at nice level 0 until we can complete
-// the investigation in bug 982972.
+// If RT priority is disabled, then the compositor nice value is used. We prefer
+// to use a nice value of -4, which matches Android's preferences. Setting a preference
+// of RT priority 1 would mean it is higher than audio, which is -16. The compositor
+// priority must be below the audio thread.
 //
 // Do not change these values without gfx team review.
 pref("hal.gonk.COMPOSITOR.rt_priority", 0);
-pref("hal.gonk.COMPOSITOR.nice", 0);
+pref("hal.gonk.COMPOSITOR.nice", -4);
 
 // Fire a memory pressure event when the system has less than Xmb of memory
 // remaining.  You should probably set this just above Y.KillUnderKB for
 // the highest priority class Y that you want to make an effort to keep alive.
 // (For example, we want BACKGROUND_PERCEIVABLE to stay alive.)  If you set
 // this too high, then we'll send out a memory pressure event every Z seconds
 // (see below), even while we have processes that we would happily kill in
 // order to free up memory.
--- a/b2g/chrome/content/payment.js
+++ b/b2g/chrome/content/payment.js
@@ -173,23 +173,25 @@ PaymentSettings.prototype = {
   },
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic != kMozSettingsChangedObserverTopic) {
       return;
     }
 
     try {
-      let setting = JSON.parse(aData);
-      if (!setting.key ||
-          (setting.key !== kRilDefaultDataServiceId &&
-           setting.key !== kRilDefaultPaymentServiceId)) {
+      if ('wrappedJSObject' in aSubject) {
+        aSubject = aSubject.wrappedJSObject;
+      }
+      if (!aSubject.key ||
+          (aSubject.key !== kRilDefaultDataServiceId &&
+           aSubject.key !== kRilDefaultPaymentServiceId)) {
         return;
       }
-      this.setServiceId(setting.key, setting.value);
+      this.setServiceId(aSubject.key, aSubject.value);
     } catch (e) {
       LOGE(e);
     }
   },
 
   cleanup: function() {
     Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
   }
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -327,16 +327,17 @@ var shell = {
 
     window.addEventListener('MozApplicationManifest', this);
     window.addEventListener('mozfullscreenchange', this);
     window.addEventListener('MozAfterPaint', this);
     window.addEventListener('sizemodechange', this);
     window.addEventListener('unload', this);
     this.contentBrowser.addEventListener('mozbrowserloadstart', this, true);
     this.contentBrowser.addEventListener('mozbrowserselectionchange', this, true);
+    this.contentBrowser.addEventListener('mozbrowserscrollviewchange', this, true);
 
     CustomEventManager.init();
     WebappsHelper.init();
     UserAgentOverrides.init();
     IndexedDBPromptHelper.init();
     CaptivePortalLoginHelper.init();
 
     this.contentBrowser.src = homeURL;
@@ -354,16 +355,17 @@ var shell = {
     window.removeEventListener('keydown', this, true);
     window.removeEventListener('keypress', this, true);
     window.removeEventListener('keyup', this, true);
     window.removeEventListener('MozApplicationManifest', this);
     window.removeEventListener('mozfullscreenchange', this);
     window.removeEventListener('sizemodechange', this);
     this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
     this.contentBrowser.removeEventListener('mozbrowserselectionchange', this, true);
+    this.contentBrowser.removeEventListener('mozbrowserscrollviewchange', this, true);
     ppmm.removeMessageListener("content-handler", this);
 
     UserAgentOverrides.uninit();
     IndexedDBPromptHelper.uninit();
   },
 
   // If this key event actually represents a hardware button, filter it here
   // and send a mozChromeEvent with detail.type set to xxx-button-press or
@@ -495,17 +497,22 @@ var shell = {
         break;
       case 'mozbrowserlocationchange':
         if (content.document.location == 'about:blank') {
           return;
         }
 
         this.notifyContentStart();
        break;
-
+      case 'mozbrowserscrollviewchange':
+        this.sendChromeEvent({
+          type: 'scrollviewchange',
+          detail: evt.detail,
+        });
+        break;
       case 'mozbrowserselectionchange':
         // The mozbrowserselectionchange event, may have crossed the chrome-content boundary.
         // This event always dispatch to shell.js. But the offset we got from this event is
         // based on tab's coordinate. So get the actual offsets between shell and evt.target.
         let elt = evt.target;
         let win = elt.ownerDocument.defaultView;
         let offsetX = win.mozInnerScreenX - window.mozInnerScreenX;
         let offsetY = win.mozInnerScreenY - window.mozInnerScreenY;
--- a/b2g/components/MobileIdentityUIGlue.js
+++ b/b2g/components/MobileIdentityUIGlue.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict"
 
 const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
 
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/ContentRequestHelper.jsm");
 Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
 Cu.import("resource://gre/modules/MobileIdentityUIGlueCommon.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/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="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a06714c555ca7068545f10b4437a16c14cd8e7f5"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <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="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <!-- 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="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"/>
   <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="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <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="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="83760d213fb3bec7b4117d266fcfbf6fe2ba14ab"/>
   <project name="device/common" path="device/common" revision="6a2995683de147791e516aae2ccb31fdfbe2ad30"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -10,27 +10,27 @@
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--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="4d1e85908d792d9468c4da7040acd191fbb51b40">
+  <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a06714c555ca7068545f10b4437a16c14cd8e7f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <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="c058843242068d0df7c107e09da31b53d2e08fa6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <!-- 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="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a06714c555ca7068545f10b4437a16c14cd8e7f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <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/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="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a06714c555ca7068545f10b4437a16c14cd8e7f5"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <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="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <!-- 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
@@ -10,27 +10,27 @@
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--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="4d1e85908d792d9468c4da7040acd191fbb51b40">
+  <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a06714c555ca7068545f10b4437a16c14cd8e7f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <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="c058843242068d0df7c107e09da31b53d2e08fa6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <!-- 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-kk/sources.xml
+++ b/b2g/config/flame-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="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a06714c555ca7068545f10b4437a16c14cd8e7f5"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <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="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <!-- 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="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"/>
   <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="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <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="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="a32003194f707f66a2d8cdb913ed1869f1926c5d"/>
   <project name="device/common" path="device/common" revision="96d4d2006c4fcb2f19a3fa47ab10cb409faa017b"/>
--- 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="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a06714c555ca7068545f10b4437a16c14cd8e7f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <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": "89fc79354058663937beda2c0eb657e87354a872", 
+    "revision": "253fcdd727387f6ad023de9aed30a20e7b8bd72d", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -8,26 +8,26 @@
   <!--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"/>
   <!--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="4d1e85908d792d9468c4da7040acd191fbb51b40">
+  <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a06714c555ca7068545f10b4437a16c14cd8e7f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <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="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <!-- 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
@@ -6,21 +6,21 @@
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--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"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
-  <project name="platform_build" path="build" remote="b2g" revision="4d1e85908d792d9468c4da7040acd191fbb51b40">
+  <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a06714c555ca7068545f10b4437a16c14cd8e7f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <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="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <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="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a06714c555ca7068545f10b4437a16c14cd8e7f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <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
@@ -8,26 +8,26 @@
   <!--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"/>
   <!--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="4d1e85908d792d9468c4da7040acd191fbb51b40">
+  <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a06714c555ca7068545f10b4437a16c14cd8e7f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <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="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <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/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -201,16 +201,17 @@
 @BINPATH@/components/dom_views.xpt
 #ifdef MOZ_WEBSPEECH
 @BINPATH@/components/dom_webspeechrecognition.xpt
 #endif
 @BINPATH@/components/dom_xbl.xpt
 @BINPATH@/components/dom_xpath.xpt
 @BINPATH@/components/dom_xul.xpt
 @BINPATH@/components/dom_time.xpt
+@BINPATH@/components/dom_engineeringmode.xpt
 @BINPATH@/components/downloads.xpt
 @BINPATH@/components/editor.xpt
 @BINPATH@/components/embed_base.xpt
 @BINPATH@/components/extensions.xpt
 @BINPATH@/components/exthandler.xpt
 @BINPATH@/components/exthelper.xpt
 @BINPATH@/components/fastfind.xpt
 @BINPATH@/components/feeds.xpt
@@ -589,16 +590,20 @@
 
 @BINPATH@/components/DownloadsAPI.js
 @BINPATH@/components/DownloadsAPI.manifest
 
 ; InputMethod API
 @BINPATH@/components/MozKeyboard.js
 @BINPATH@/components/InputMethod.manifest
 
+@BINPATH@/components/EngineeringMode.manifest
+@BINPATH@/components/EngineeringModeAPI.js
+@BINPATH@/components/EngineeringModeService.js
+
 #ifdef MOZ_DEBUG
 @BINPATH@/components/TestInterfaceJS.js
 @BINPATH@/components/TestInterfaceJS.manifest
 #endif
 
 ; Modules
 @BINPATH@/modules/*
 
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -301,16 +301,25 @@ var gPluginHandler = {
   hideNotificationBar: function (browser, name) {
     let notificationBox = gBrowser.getNotificationBox(browser);
     let notification = notificationBox.getNotificationWithValue(name);
     if (notification)
       notificationBox.removeNotification(notification, true);
   },
 
   updateHiddenPluginUI: function (browser, haveInsecure, actions, principal, host) {
+    // It is possible that we've received a message from the frame script to show
+    // the hidden plugin notification for a principal that no longer matches the one
+    // that the browser's content now has assigned (ie, the browser has browsed away
+    // after the message was sent, but before the message was received). In that case,
+    // we should just ignore the message.
+    if (!principal.equals(browser.contentPrincipal)) {
+      return;
+    }
+
     // Set up the icon
     document.getElementById("plugins-notification-icon").classList.
       toggle("plugin-blocked", haveInsecure);
 
     // Now configure the notification bar
     let notificationBox = gBrowser.getNotificationBox(browser);
 
     function hideNotification() {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -8,16 +8,18 @@ let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/NotificationDB.jsm");
 Cu.import("resource:///modules/RecentWindow.jsm");
 Cu.import("resource://gre/modules/WindowsPrefSync.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
                                   "resource:///modules/BrowserUITelemetry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
+                                  "resource:///modules/E10SUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                   "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
                                   "resource://gre/modules/CharsetMenu.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
                                   "resource://gre/modules/ShortcutUtils.jsm");
@@ -161,16 +163,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource:///modules/translation/Translation.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
   "resource:///modules/SitePermissions.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
   "resource:///modules/sessionstore/SessionStore.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "TabState",
+  "resource:///modules/sessionstore/TabState.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
   "resource://gre/modules/FxAccounts.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "gWebRTCUI",
   "resource:///modules/webrtcUI.jsm", "webrtcUI");
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter",
@@ -761,16 +766,46 @@ function gKeywordURIFixup({ target: brow
                                          notificationBox.PRIORITY_INFO_HIGH,
                                          buttons);
     notification.persistence = 1;
   };
 
   gDNSService.asyncResolve(hostName, 0, onLookupComplete, Services.tm.mainThread);
 }
 
+// Called when a docshell has attempted to load a page in an incorrect process.
+// This function is responsible for loading the page in the correct process.
+function RedirectLoad({ target: browser, data }) {
+  let tab = gBrowser._getTabForBrowser(browser);
+  // Flush the tab state before getting it
+  TabState.flush(browser);
+  let tabState = JSON.parse(SessionStore.getTabState(tab));
+
+  if (data.historyIndex < 0) {
+    // Add a pseudo-history state for the new url to load
+    let newEntry = {
+      url: data.uri,
+      referrer: data.referrer,
+    };
+
+    tabState.entries = tabState.entries.slice(0, tabState.index);
+    tabState.entries.push(newEntry);
+    tabState.index++;
+    tabState.userTypedValue = null;
+  }
+  else {
+    // Update the history state to point to the requested index
+    tabState.index = data.historyIndex + 1;
+  }
+
+  // SessionStore takes care of setting the browser remoteness before restoring
+  // history into it.
+  SessionStore.setTabState(tab, JSON.stringify(tabState));
+}
+
 var gBrowserInit = {
   delayedStartupFinished: false,
 
   onLoad: function() {
     var mustLoadSidebar = false;
 
     gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
 
@@ -1059,16 +1094,17 @@ var gBrowserInit = {
 
     Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
     window.messageManager.addMessageListener("Browser:URIFixup", gKeywordURIFixup);
+    window.messageManager.addMessageListener("Browser:LoadURI", RedirectLoad);
 
     BrowserOffline.init();
     OfflineApps.init();
     IndexedDBPromptHelper.init();
     gRemoteTabsUI.init();
 
     // Initialize the full zoom setting.
     // We do this before the session restore service gets initialized so we can
@@ -1367,16 +1403,17 @@ var gBrowserInit = {
 
       Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
       window.messageManager.removeMessageListener("Browser:URIFixup", gKeywordURIFixup);
+      window.messageManager.removeMessageListener("Browser:LoadURI", RedirectLoad);
 
       try {
         gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
       } catch (ex) {
         Cu.reportError(ex);
       }
 
       if (typeof WindowsPrefSync !== 'undefined') {
@@ -3546,16 +3583,38 @@ var XULBrowserWindow = {
 
   // Called before links are navigated to to allow us to retarget them if needed.
   onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
     let target = BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
     SocialUI.closeSocialPanelForLinkTraversal(target, linkNode);
     return target;
   },
 
+  // Check whether this URI should load in the current process
+  shouldLoadURI: function(aDocShell, aURI, aReferrer) {
+    if (!gMultiProcessBrowser)
+      return true;
+
+    let browser = aDocShell.QueryInterface(Ci.nsIDocShellTreeItem)
+                           .sameTypeRootTreeItem
+                           .QueryInterface(Ci.nsIDocShell)
+                           .chromeEventHandler;
+
+    // Ignore loads that aren't in the main tabbrowser
+    if (browser.localName != "browser" || browser.getTabBrowser() != gBrowser)
+      return true;
+
+    if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
+      E10SUtils.redirectLoad(aDocShell, aURI, aReferrer);
+      return false;
+    }
+
+    return true;
+  },
+
   onProgressChange: function (aWebProgress, aRequest,
                               aCurSelfProgress, aMaxSelfProgress,
                               aCurTotalProgress, aMaxTotalProgress) {
     // Do nothing.
   },
 
   onProgressChange64: function (aWebProgress, aRequest,
                                 aCurSelfProgress, aMaxSelfProgress,
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -4,16 +4,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/ContentWebRTC.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
+  "resource:///modules/E10SUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
   "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
   "resource:///modules/ContentLinkHandler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
   "resource://gre/modules/LoginManagerContent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
   "resource://gre/modules/InsecurePasswordUtils.jsm");
@@ -678,16 +680,26 @@ addEventListener("unload", () => {
 addMessageListener("Browser:AppTab", function(message) {
   docShell.isAppTab = message.data.isAppTab;
 });
 
 let WebBrowserChrome = {
   onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
     return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
   },
+
+  // Check whether this URI should load in the current process
+  shouldLoadURI: function(aDocShell, aURI, aReferrer) {
+    if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
+      E10SUtils.redirectLoad(aDocShell, aURI, aReferrer);
+      return false;
+    }
+
+    return true;
+  },
 };
 
 if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
   let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsITabChild);
   tabchild.webBrowserChrome = WebBrowserChrome;
 }
 
--- a/browser/base/content/newtab/intro.js
+++ b/browser/base/content/newtab/intro.js
@@ -36,17 +36,17 @@ let gIntro = {
       // Point the panel at the 'what' menu item
       this._nodes.panel.openPopup(nodes.what);
     });
   },
 
   _setUpPanel: function() {
     // Build the panel if necessary
     if (this._nodes.panel.childNodes.length == 1) {
-      ['<a href="' + TILES_EXPLAIN_LINK + '">' + newTabString("learn.link") + "</a>",
+      ['<a href="' + TILES_INTRO_LINK + '">' + newTabString("learn.link") + "</a>",
        '<a href="' + TILES_PRIVACY_LINK + '">' + newTabString("privacy.link") + "</a>",
        '<input type="button" class="newtab-customize"/>',
       ].forEach((arg, index) => {
         let paragraph = document.createElementNS(HTML_NAMESPACE, "p");
         this._nodes.panel.appendChild(paragraph);
         paragraph.innerHTML = newTabString("intro.paragraph" + (index + 1), [arg]);
       });
     }
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -181,33 +181,32 @@ input[type=button] {
 .newtab-title {
   font-size: 13px;
   left: 0;
   padding-top: 14px;
   text-overflow: ellipsis;
 }
 
 .newtab-sponsored {
-  background-color: #f9f9f9;
   border: 1px solid #dcdcdc;
   border-radius: 2px;
-  color: #9b9b9b;
   cursor: pointer;
   display: none;
   font-family: Arial;
   font-size: 10px;
   height: 17px;
   line-height: 17px;
   margin-bottom: -1px;
   padding: 0 4px;
 }
 
 .newtab-sponsored:-moz-any(:hover, [active]) {
-  background-color: #dcdcdc;
-  color: #666666;
+  background-color: #3a72b1;
+  border: 0;
+  color: white;
 }
 
 .newtab-sponsored:-moz-locale-dir(rtl) {
   left: 0;
   right: auto;
 }
 
 .newtab-site:-moz-any([type=enhanced], [type=sponsored]) .newtab-sponsored {
--- a/browser/base/content/newtab/newTab.js
+++ b/browser/base/content/newtab/newTab.js
@@ -45,17 +45,18 @@ function newTabString(name, args) {
 
 function inPrivateBrowsingMode() {
   return PrivateBrowsingUtils.isWindowPrivate(window);
 }
 
 const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
 const XUL_NAMESPACE = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
-const TILES_EXPLAIN_LINK = "https://support.mozilla.org/kb/how-do-sponsored-tiles-work";
+const TILES_EXPLAIN_LINK = "https://support.mozilla.org/kb/how-do-tiles-work-firefox";
+const TILES_INTRO_LINK = "https://www.mozilla.org/firefox/tiles/";
 const TILES_PRIVACY_LINK = "https://www.mozilla.org/privacy/";
 
 #include transformations.js
 #include page.js
 #include grid.js
 #include cells.js
 #include sites.js
 #include drag.js
--- a/browser/base/content/newtab/page.js
+++ b/browser/base/content/newtab/page.js
@@ -137,16 +137,19 @@ let gPage = {
     }
   },
 
   /**
    * Handles all page events.
    */
   handleEvent: function Page_handleEvent(aEvent) {
     switch (aEvent.type) {
+      case "load":
+        this.onPageVisibleAndLoaded();
+        break;
       case "unload":
         gAllPages.unregister(this);
         break;
       case "click":
         let {button, target} = aEvent;
         // Go up ancestors until we find a Site or not
         while (target) {
           if (target.hasOwnProperty("_newtabSite")) {
@@ -178,44 +181,46 @@ let gPage = {
     Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true);
 
     for (let site of gGrid.sites) {
       if (site) {
         site.captureIfMissing();
       }
     }
 
-    // Allow the document to reflow so the page has sizing info
-    let i = 0;
-    let checkSizing = _ => setTimeout(_ => {
-      if (document.documentElement.clientWidth == 0) {
-        checkSizing();
-      }
-      else {
-        this.onPageFirstSized();
-      }
-    });
-    checkSizing();
+    if (document.readyState == "complete") {
+      this.onPageVisibleAndLoaded();
+    } else {
+      addEventListener("load", this);
+    }
+  },
+
+  onPageVisibleAndLoaded() {
+    // Send the index of the last visible tile.
+    this.reportLastVisibleTileIndex();
+
+    // Show the panel now that anchors are sized
+    gIntro.showIfNecessary();
   },
 
-  onPageFirstSized: function() {
-    // Work backwards to find the first visible site from the end
-    let {sites} = gGrid;
-    let lastIndex = sites.length;
-    while (lastIndex-- > 0) {
-      let site = sites[lastIndex];
-      if (site) {
-        let {node} = site;
-        let rect = node.getBoundingClientRect();
-        let target = document.elementFromPoint(rect.x + rect.width / 2,
-                                               rect.y + rect.height / 2);
-        if (node.contains(target)) {
-          break;
+  reportLastVisibleTileIndex() {
+    let cwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIDOMWindowUtils);
+
+    let rect = cwu.getBoundsWithoutFlushing(gGrid.node);
+    let nodes = cwu.nodesFromRect(rect.left, rect.top, 0, rect.width,
+                                  rect.height, 0, true, false);
+
+    let i = -1;
+    let lastIndex = -1;
+    let sites = gGrid.sites;
+
+    for (let node of nodes) {
+      if (node.classList && node.classList.contains("newtab-cell")) {
+        if (sites[++i]) {
+          lastIndex = i;
         }
       }
     }
 
-    DirectoryLinksProvider.reportSitesAction(gGrid.sites, "view", lastIndex);
-
-    // Show the panel now that anchors are sized
-    gIntro.showIfNecessary();
+    DirectoryLinksProvider.reportSitesAction(sites, "view", lastIndex);
   }
 };
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -4,21 +4,16 @@
    - 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/. -->
 
 <!DOCTYPE bindings [
 <!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
 %tabBrowserDTD;
 ]>
 
-# MAKE_E10S_WORK surrounds code needed to have the front-end try to be smart
-# about using non-remote browsers for loading certain URIs when remote tabs
-# (browser.tabs.remote) are enabled.
-#define MAKE_E10S_WORK 1
-
 <bindings id="tabBrowserBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="tabbrowser">
     <resources>
       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
@@ -575,19 +570,16 @@
               Array.unshift(arguments, this.mBrowser);
               return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
             },
 
             _shouldShowProgress: function (aRequest) {
               if (this.mBlank)
                 return false;
 
-              if (gMultiProcessBrowser)
-                return true;
-
               // Don't show progress indicators in tabs for about: URIs
               // pointing to local resources.
               try {
                 let channel = aRequest.QueryInterface(Ci.nsIChannel);
                 if (channel.originalURI.schemeIs("about") &&
                     (channel.URI.schemeIs("jar") || channel.URI.schemeIs("file")))
                   return false;
               } catch (e) {}
@@ -1472,59 +1464,28 @@
             if (wasActive)
               aBrowser.focus();
 
             return true;
           ]]>
         </body>
       </method>
 
-#ifdef MAKE_E10S_WORK
       <method name="updateBrowserRemotenessByURL">
         <parameter name="aBrowser"/>
         <parameter name="aURL"/>
         <body>
           <![CDATA[
-            let shouldBeRemote = this._shouldBrowserBeRemote(aURL);
+            let shouldBeRemote = gMultiProcessBrowser &&
+                                 E10SUtils.shouldBrowserBeRemote(aURL);
             return this.updateBrowserRemoteness(aBrowser, shouldBeRemote);
           ]]>
         </body>
       </method>
 
-      <!--
-        Returns true if we want to load the content for this URL in a
-        remote process. Eventually this should just check whether aURL
-        is unprivileged. Right now, though, we would like to load
-        some unprivileged URLs (like about:neterror) in the main
-        process since they interact with chrome code through
-        BrowserOnClick.
-      -->
-      <method name="_shouldBrowserBeRemote">
-        <parameter name="aURL"/>
-        <body>
-          <![CDATA[
-            if (!gMultiProcessBrowser)
-              return false;
-
-            // loadURI in browser.xml treats null as about:blank
-            if (!aURL)
-              aURL = "about:blank";
-
-            if (aURL.startsWith("about:") &&
-                aURL.toLowerCase() != "about:home" &&
-                aURL.toLowerCase() != "about:blank") {
-              return false;
-            }
-
-            return true;
-          ]]>
-        </body>
-      </method>
-#endif
-
       <method name="addTab">
         <parameter name="aURI"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <parameter name="aPostData"/>
         <parameter name="aOwner"/>
         <parameter name="aAllowThirdPartyFixup"/>
         <body>
@@ -1556,21 +1517,17 @@
             var t = document.createElementNS(NS_XUL, "tab");
 
             var uriIsAboutBlank = !aURI || aURI == "about:blank";
 
             t.setAttribute("crop", "end");
             t.setAttribute("onerror", "this.removeAttribute('image');");
             t.className = "tabbrowser-tab";
 
-#ifdef MAKE_E10S_WORK
-            let remote = this._shouldBrowserBeRemote(aURI);
-#else
-            let remote = gMultiProcessBrowser;
-#endif
+            let remote = gMultiProcessBrowser && E10SUtils.shouldBrowserBeRemote(aURI);
             if (remote)
               t.setAttribute("remote", "true");
 
             this.tabContainer._unlockTabSizing();
 
             // When overflowing, new tabs are scrolled into view smoothly, which
             // doesn't go well together with the width transition. So we skip the
             // transition in that case.
@@ -2788,53 +2745,31 @@
 
       <!-- throws exception for unknown schemes -->
       <method name="loadURI">
         <parameter name="aURI"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <body>
           <![CDATA[
-#ifdef MAKE_E10S_WORK
-            this.updateBrowserRemotenessByURL(this.mCurrentBrowser, aURI);
-            try {
-#endif
             return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
-#ifdef MAKE_E10S_WORK
-            } catch (e) {
-              let url = this.mCurrentBrowser.currentURI.spec;
-              this.updateBrowserRemotenessByURL(this.mCurrentBrowser, url);
-              throw e;
-            }
-#endif
           ]]>
         </body>
       </method>
 
       <!-- throws exception for unknown schemes -->
       <method name="loadURIWithFlags">
         <parameter name="aURI"/>
         <parameter name="aFlags"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <parameter name="aPostData"/>
         <body>
           <![CDATA[
-#ifdef MAKE_E10S_WORK
-            this.updateBrowserRemotenessByURL(this.mCurrentBrowser, aURI);
-            try {
-#endif
             return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
-#ifdef MAKE_E10S_WORK
-            } catch (e) {
-              let url = this.mCurrentBrowser.currentURI.spec;
-              this.updateBrowserRemotenessByURL(this.mCurrentBrowser, url);
-              throw e;
-            }
-#endif
           ]]>
         </body>
       </method>
 
       <method name="goHome">
         <body>
           <![CDATA[
             return this.mCurrentBrowser.goHome();
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -283,16 +283,17 @@ skip-if = e10s # Bug 916974 - Session hi
 [browser_bug902156.js]
 skip-if = e10s
 [browser_bug906190.js]
 skip-if = buildapp == "mulet" || e10s # Bug ?????? - test directly manipulates content (strange - gets an element from a child which it tries to treat as a string, but that fails)
 [browser_bug970746.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (directly gets elements from the content)
 [browser_bug1015721.js]
 skip-if = os == 'win' || e10s # Bug 1056146 - FullZoomHelper uses promiseTabLoadEvent() which isn't e10s friendly
+[browser_bug1064280_changeUrlInPinnedTab.js]
 [browser_canonizeURL.js]
 skip-if = e10s # Bug ?????? - [JavaScript Error: "Error in AboutHome.sendAboutHomeData TypeError: target.messageManager is undefined" {file: "resource:///modules/AboutHome.jsm" line: 208}]
 [browser_contentAreaClick.js]
 skip-if = e10s
 [browser_contextSearchTabPosition.js]
 skip-if = os == "mac" || e10s # bug 967013, bug 926729
 [browser_ctrlTab.js]
 skip-if = e10s # Bug ????? - thumbnail captures need e10s love (tabPreviews_capture fails with Argument 1 of CanvasRenderingContext2D.drawWindow does not implement interface Window.)
@@ -477,9 +478,11 @@ skip-if = e10s # Bug 516755 - SessionSto
 [browser_bug1003461-switchtab-override.js]
 skip-if = e10s
 [browser_bug1024133-switchtab-override-keynav.js]
 skip-if = e10s
 [browser_bug1025195_switchToTabHavingURI_ignoreFragment.js]
 [browser_addCertException.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)
 [browser_bug1045809.js]
-skip-if = e10s
+skip-if = e10s # Bug 1068360 - [e10s] Mixed content blocker doorhanger doesn't work
+[browser_e10s_switchbrowser.js]
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function(){
+  // Test that changing the URL in a pinned tab works correctly
+
+  let TEST_LINK_INITIAL = "about:";
+  let TEST_LINK_CHANGED = "about:support";
+
+  let appTab = gBrowser.addTab(TEST_LINK_INITIAL);
+  gBrowser.pinTab(appTab);
+  is(appTab.pinned, true, "Tab was successfully pinned");
+
+  let initialTabsNo = gBrowser.tabs.length;
+
+  let goButton = document.getElementById("urlbar-go-button");
+  gBrowser.selectedTab = appTab;
+  gURLBar.focus();
+  gURLBar.value = TEST_LINK_CHANGED;
+
+  let promisePageload = promiseTabLoadEvent(appTab);
+  goButton.click();
+  yield promisePageload;
+
+  is(appTab.linkedBrowser.currentURI.spec, TEST_LINK_CHANGED,
+     "New page loaded in the app tab");
+  is(gBrowser.tabs.length, initialTabsNo, "No additional tabs were opened");
+});
+
+registerCleanupFunction(function () {
+  gBrowser.removeTab(gBrowser.selectedTab);
+});
--- a/browser/base/content/test/general/browser_bug880101.js
+++ b/browser/base/content/test/general/browser_bug880101.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const URL = "about:robots";
+const URL = "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
 
 function test() {
   let win;
 
   let listener = {
     onLocationChange: (webProgress, request, uri, flags) => {
       ok(webProgress.isTopLevel, "Received onLocationChange from top frame");
       is(uri.spec, URL, "Received onLocationChange for correct URL");
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_e10s_switchbrowser.js
@@ -0,0 +1,104 @@
+const DUMMY_PATH = "browser/browser/base/content/test/general/dummy_page.html";
+
+const gExpectedHistory = {
+  index: -1,
+  entries: []
+};
+
+function check_history() {
+  let webNav = gBrowser.webNavigation;
+  let sessionHistory = webNav.sessionHistory;
+
+  let count = sessionHistory.count;
+  is(count, gExpectedHistory.entries.length, "Should have the right number of history entries");
+  is(sessionHistory.index, gExpectedHistory.index, "Should have the right history index");
+
+  for (let i = 0; i < count; i++) {
+    let entry = sessionHistory.getEntryAtIndex(i, false);
+    is(entry.URI.spec, gExpectedHistory.entries[i].uri, "Should have the right URI");
+    is(entry.title, gExpectedHistory.entries[i].title, "Should have the right title");
+  }
+}
+
+// Waits for a load and updates the known history
+let waitForLoad = Task.async(function*(uri) {
+  info("Loading " + uri);
+  gBrowser.loadURI(uri);
+
+  yield waitForDocLoadComplete();
+  gExpectedHistory.index++;
+  gExpectedHistory.entries.push({
+    uri: gBrowser.currentURI.spec,
+    title: gBrowser.contentTitle
+  });
+});
+
+let back = Task.async(function*() {
+  info("Going back");
+  gBrowser.goBack();
+  yield waitForDocLoadComplete();
+  gExpectedHistory.index--;
+});
+
+let forward = Task.async(function*() {
+  info("Going forward");
+  gBrowser.goForward();
+  yield waitForDocLoadComplete();
+  gExpectedHistory.index++;
+});
+
+// Tests that navigating from a page that should be in the remote process and
+// a page that should be in the main process works and retains history
+add_task(function*() {
+  SimpleTest.requestCompleteLog();
+
+  let remoting = Services.prefs.getBoolPref("browser.tabs.remote.autostart");
+  let expectedRemote = remoting ? "true" : "";
+
+  info("1");
+  // Create a tab and load a remote page in it
+  gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+  yield waitForLoad("http://example.org/" + DUMMY_PATH);
+  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+
+  info("2");
+  // Load another page
+  yield waitForLoad("http://example.com/" + DUMMY_PATH);
+  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  check_history();
+
+  info("3");
+  // Load a non-remote page
+  yield waitForLoad("about:robots");
+  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  check_history();
+
+  info("4");
+  // Load a remote page
+  yield waitForLoad("http://example.org/" + DUMMY_PATH);
+  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  check_history();
+
+  info("5");
+  yield back();
+  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  check_history();
+
+  info("6");
+  yield back();
+  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  check_history();
+
+  info("7");
+  yield forward();
+  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  check_history();
+
+  info("8");
+  yield forward();
+  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  check_history();
+
+  info("9");
+  gBrowser.removeCurrentTab();
+});
--- a/browser/base/content/test/general/browser_tabopen_reflows.js
+++ b/browser/base/content/test/general/browser_tabopen_reflows.js
@@ -18,19 +18,16 @@ const EXPECTED_REFLOWS = [
     "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml|" +
     "onselect@chrome://browser/content/browser.xul|",
 
   // switching focus in openLinkIn() causes reflows
   "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
     "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
     "BrowserOpenTab@chrome://browser/content/browser.js|",
 
-  // unpreloaded newtab pages explicitly waits for reflows for sizing
-  "gPage.onPageFirstVisible/checkSizing/<@chrome://browser/content/newtab/newTab.js|",
-
   // accessing element.scrollPosition in _fillTrailingGap() flushes layout
   "get_scrollPosition@chrome://global/content/bindings/scrollbox.xml|" +
     "_fillTrailingGap@chrome://browser/content/tabbrowser.xml|" +
     "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
     "onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
 
   // The TabView iframe causes reflows in the parent document.
   "iQClass_height@chrome://browser/content/tabview.js|" +
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -449,22 +449,25 @@ function waitForDocLoadAndStopIt(aExpect
  * Waits for the next load to complete in the current browser.
  *
  * @return promise
  */
 function waitForDocLoadComplete(aBrowser=gBrowser) {
   let deferred = Promise.defer();
   let progressListener = {
     onStateChange: function (webProgress, req, flags, status) {
-      let docStart = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
-                     Ci.nsIWebProgressListener.STATE_STOP;
-      info("Saw state " + flags.toString(16));
-      if ((flags & docStart) == docStart) {
+      let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
+                    Ci.nsIWebProgressListener.STATE_STOP;
+      info("Saw state " + flags.toString(16) + " and status " + status.toString(16));
+
+      // When a load needs to be retargetted to a new process it is cancelled
+      // with NS_BINDING_ABORTED so ignore that case
+      if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
         aBrowser.removeProgressListener(progressListener);
-        info("Browser loaded");
+        info("Browser loaded " + aBrowser.contentWindow.location);
         deferred.resolve();
       }
     },
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                            Ci.nsISupportsWeakReference])
   };
   aBrowser.addProgressListener(progressListener);
   info("Waiting for browser load");
--- a/browser/base/content/test/newtab/browser.ini
+++ b/browser/base/content/test/newtab/browser.ini
@@ -23,16 +23,19 @@ skip-if = os == "mac" # Intermittent fai
 [browser_newtab_drag_drop.js]
 [browser_newtab_drag_drop_ext.js]
 [browser_newtab_drop_preview.js]
 [browser_newtab_enhanced.js]
 [browser_newtab_focus.js]
 [browser_newtab_intro.js]
 [browser_newtab_perwindow_private_browsing.js]
 [browser_newtab_reportLinkAction.js]
+[browser_newtab_reflow_load.js]
+support-files =
+  content-reflows.js
 [browser_newtab_reset.js]
 [browser_newtab_search.js]
 support-files =
   searchEngineNoLogo.xml
   searchEngine1xLogo.xml
   searchEngine2xLogo.xml
   searchEngine1x2xLogo.xml
   ../general/searchSuggestionEngine.xml
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_reflow_load.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FRAME_SCRIPT = getRootDirectory(gTestPath) + "content-reflows.js";
+const ADDITIONAL_WAIT_MS = 2000;
+
+/*
+ * Ensure that loading about:newtab doesn't cause uninterruptible reflows.
+ */
+function runTests() {
+  gBrowser.selectedTab = gBrowser.addTab("about:blank", {animate: false});
+  let browser = gBrowser.selectedBrowser;
+  yield whenBrowserLoaded(browser);
+
+  let mm = browser.messageManager;
+  mm.loadFrameScript(FRAME_SCRIPT, true);
+  mm.addMessageListener("newtab-reflow", ({data: stack}) => {
+    ok(false, `unexpected uninterruptible reflow ${stack}`);
+  });
+
+  browser.loadURI("about:newtab");
+  yield whenBrowserLoaded(browser);
+
+  // Wait some more to catch sync reflows after the page has loaded.
+  yield setTimeout(TestRunner.next, ADDITIONAL_WAIT_MS);
+
+  // Clean up.
+  gBrowser.removeCurrentTab({animate: false});
+}
+
+function whenBrowserLoaded(browser) {
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    executeSoon(TestRunner.next);
+  }, true);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/newtab/content-reflows.js
@@ -0,0 +1,26 @@
+/* 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/. */
+
+(function () {
+  "use strict";
+
+  const Ci = Components.interfaces;
+
+  docShell.addWeakReflowObserver({
+    reflow() {
+      // Gather information about the current code path.
+      let path = (new Error().stack).split("\n").slice(1).join("\n");
+      if (path) {
+        sendSyncMessage("newtab-reflow", path);
+      }
+    },
+
+    reflowInterruptible() {
+      // We're not interested in interruptible reflows.
+    },
+
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
+                                           Ci.nsISupportsWeakReference])
+  });
+})();
--- a/browser/base/content/test/plugins/browser.ini
+++ b/browser/base/content/test/plugins/browser.ini
@@ -28,16 +28,17 @@ support-files =
   plugin_clickToPlayDeny.html
   plugin_data_url.html
   plugin_hidden_to_visible.html
   plugin_iframe.html
   plugin_outsideScrollArea.html
   plugin_overlayed.html
   plugin_positioned.html
   plugin_small.html
+  plugin_small_2.html
   plugin_syncRemoved.html
   plugin_test.html
   plugin_test2.html
   plugin_test3.html
   plugin_two_types.html
   plugin_unknown.html
   plugin_crashCommentAndURL.html
   plugin_zoom.html
@@ -57,16 +58,17 @@ run-if = crashreporter
 [browser_CTP_data_urls.js]
 [browser_CTP_drag_drop.js]
 [browser_CTP_hide_overlay.js]
 [browser_CTP_iframe.js]
 [browser_CTP_multi_allow.js]
 [browser_CTP_nonplugins.js]
 [browser_CTP_notificationBar.js]
 [browser_CTP_outsideScrollArea.js]
+[browser_CTP_remove_navigate.js]
 [browser_CTP_resize.js]
 [browser_CTP_zoom.js]
 [browser_globalplugin_crashinfobar.js]
 [browser_pageInfo_plugins.js]
 [browser_pluginnotification.js]
 [browser_pluginplaypreview.js]
 [browser_pluginplaypreview2.js]
 [browser_pluginCrashCommentAndURL.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_remove_navigate.js
@@ -0,0 +1,82 @@
+/* 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/. */
+
+const gTestRoot = getRootDirectory(gTestPath);
+const gHttpTestRoot = gTestRoot.replace("chrome://mochitests/content/",
+                                        "http://127.0.0.1:8888/");
+
+add_task(function* () {
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("plugins.click_to_play");
+  });
+})
+
+/**
+ * Tests that if a plugin is removed just as we transition to
+ * a different page, that we don't show the hidden plugin
+ * notification bar on the new page.
+ */
+add_task(function* () {
+  let newTab = gBrowser.addTab();
+  gBrowser.selectedTab = newTab;
+  let browser = gBrowser.selectedBrowser;
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  // Load up a page with a plugin...
+  let notificationPromise =
+    waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
+  yield loadPage(browser, gHttpTestRoot + "plugin_small.html")
+  yield forcePluginBindingAttached(browser);
+  yield notificationPromise;
+
+  // Trigger the PluginRemoved event to be fired, and then immediately
+  // browse to a new page.
+  let plugin = browser.contentDocument.getElementById("test");
+  plugin.remove();
+  yield loadPage(browser, "about:mozilla");
+
+  // There should be no hidden plugin notification bar at about:mozilla.
+  let notificationBox = gBrowser.getNotificationBox(browser);
+  is(notificationBox.getNotificationWithValue("plugin-hidden"), null,
+     "Expected no notification box");
+  gBrowser.removeTab(newTab);
+});
+
+/**
+ * Tests that if a plugin is removed just as we transition to
+ * a different page with a plugin, that we show the right notification
+ * for the new page.
+ */
+add_task(function* () {
+  let newTab = gBrowser.addTab();
+  gBrowser.selectedTab = newTab;
+  let browser = gBrowser.selectedBrowser;
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY,
+                            "Second Test Plug-in");
+
+  // Load up a page with a plugin...
+  let notificationPromise =
+    waitForNotificationBar("plugin-hidden", browser);
+  yield loadPage(browser, gHttpTestRoot + "plugin_small.html")
+  yield forcePluginBindingAttached(browser);
+  yield notificationPromise;
+
+  // Trigger the PluginRemoved event to be fired, and then immediately
+  // browse to a new page.
+  let plugin = browser.contentDocument.getElementById("test");
+  plugin.remove();
+  yield loadPage(browser, gTestRoot + "plugin_small_2.html");
+  let notification = yield waitForNotificationBar("plugin-hidden", browser);
+  ok(notification, "There should be a notification shown for the new page.");
+
+  // Ensure that the notification is showing information about
+  // the x-second-test plugin.
+  ok(notification.label.contains("Second Test"), "Should mention the second plugin");
+  ok(!notification.label.contains("127.0.0.1"), "Should not refer to old principal");
+  ok(notification.label.contains("null"), "Should refer to the new principal");
+  gBrowser.removeTab(newTab);
+});
--- a/browser/base/content/test/plugins/head.js
+++ b/browser/base/content/test/plugins/head.js
@@ -116,20 +116,83 @@ function waitForNotificationPopup(notifi
     () => {
       ok(notification, `Successfully got the ${notificationID} notification popup`);
       callback(notification);
     },
     `Waited too long for the ${notificationID} notification popup`
   );
 }
 
+/**
+ * Returns a Promise that resolves when a notification bar
+ * for a browser is shown. Alternatively, for old-style callers,
+ * can automatically call a callback before it resolves.
+ *
+ * @param notificationID
+ *        The ID of the notification to look for.
+ * @param browser
+ *        The browser to check for the notification bar.
+ * @param callback (optional)
+ *        A function to be called just before the Promise resolves.
+ *
+ * @return Promise
+ */
 function waitForNotificationBar(notificationID, browser, callback) {
-  let notification;
-  let notificationBox = gBrowser.getNotificationBox(browser);
-  waitForCondition(
-    () => (notification = notificationBox.getNotificationWithValue(notificationID)),
-    () => {
-      ok(notification, `Successfully got the ${notificationID} notification bar`);
-      callback(notification);
-    },
-    `Waited too long for the ${notificationID} notification bar`
-  );
+  return new Promise((resolve, reject) => {
+    let notification;
+    let notificationBox = gBrowser.getNotificationBox(browser);
+    waitForCondition(
+      () => (notification = notificationBox.getNotificationWithValue(notificationID)),
+      () => {
+        ok(notification, `Successfully got the ${notificationID} notification bar`);
+        if (callback) {
+          callback(notification);
+        }
+        resolve(notification);
+      },
+      `Waited too long for the ${notificationID} notification bar`
+    );
+  });
 }
+
+/**
+ * Due to layout being async, "PluginBindAttached" may trigger later.
+ * This returns a Promise that resolves once we've forced a layout
+ * flush, which triggers the PluginBindAttached event to fire.
+ *
+ * @param browser
+ *        The browser to force plugin bindings in.
+ *
+ * @return Promise
+ */
+function forcePluginBindingAttached(browser) {
+  return new Promise((resolve, reject) => {
+    let doc = browser.contentDocument;
+    let elems = doc.getElementsByTagName('embed');
+    if (elems.length < 1) {
+      elems = doc.getElementsByTagName('object');
+    }
+    elems[0].clientTop;
+    executeSoon(resolve);
+  });
+}
+
+/**
+ * Loads a page in a browser, and returns a Promise that
+ * resolves once the "load" event has been fired for that
+ * browser.
+ *
+ * @param browser
+ *        The browser to load the page in.
+ * @param uri
+ *        The URI to load.
+ *
+ * @return Promise
+ */
+function loadPage(browser, uri) {
+  return new Promise((resolve, reject) => {
+    browser.addEventListener("load", function onLoad(event) {
+      browser.removeEventListener("load", onLoad, true);
+      resolve();
+    }, true);
+    browser.loadURI(uri);
+  });
+}
copy from browser/base/content/test/plugins/plugin_small.html
copy to browser/base/content/test/plugins/plugin_small_2.html
--- a/browser/base/content/test/plugins/plugin_small.html
+++ b/browser/base/content/test/plugins/plugin_small_2.html
@@ -1,9 +1,9 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta charset="utf-8">
 </head>
 <body>
-<embed id="test" style="width: 10px; height: 10px" type="application/x-test">
+<embed id="test" style="width: 10px; height: 10px" type="application/x-second-test">
 </body>
 </html>
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -230,22 +230,22 @@
  * but the UI breaks when you pop out
  * Bug 1040985
  */
 .incoming-call {
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: space-between;
-  min-height: 264px;
+  min-height: 230px;
 }
 
 .incoming-call-action-group {
   display: flex;
-  padding: 2.5em 0;
+  padding: 2.5em 0 0 0;
   width: 100%;
   justify-content: space-around;
 }
 
 .incoming-call-action-group > .btn {
   margin-left: .5em;
 }
 
@@ -358,23 +358,26 @@
 }
 
 /* Feedback form */
 
 .feedback {
   padding: 14px;
 }
 
+.feedback p {
+  margin: 0px;
+}
+
 .feedback h3 {
   color: #666;
   font-size: 12px;
   font-weight: 700;
   text-align: center;
-  margin-bottom: 14px;
-  margin-top: 14px;
+  margin: 0 0 1em 0;
 }
 
 .feedback .faces {
   display: flex;
   flex-direction: row;
   align-items: center;
   justify-content: center;
   padding: 20px 0;
--- a/browser/components/loop/content/shared/js/models.js
+++ b/browser/components/loop/content/shared/js/models.js
@@ -361,22 +361,25 @@ loop.shared.models = (function(l10n) {
      *
      * @return {String} message
      */
     error: function(message) {
       this.add({level: "error", message: message});
     },
 
     /**
-     * Adds a l10n rror notification to the stack and renders it.
+     * Adds a l10n error notification to the stack and renders it.
      *
      * @param  {String} messageId L10n message id
+     * @param  {Object} [l10nProps] An object with variables to be interpolated
+     *                  into the translation. All members' values must be
+     *                  strings or numbers.
      */
-    errorL10n: function(messageId) {
-      this.error(l10n.get(messageId));
+    errorL10n: function(messageId, l10nProps) {
+      this.error(l10n.get(messageId, l10nProps));
     }
   });
 
   return {
     ConversationModel: ConversationModel,
     NotificationCollection: NotificationCollection,
     NotificationModel: NotificationModel
   };
--- a/browser/components/loop/content/shared/js/utils.js
+++ b/browser/components/loop/content/shared/js/utils.js
@@ -53,16 +53,25 @@ loop.shared.utils = (function() {
     this._iOSRegex = /^(iPad|iPhone|iPod)/;
   }
 
   Helper.prototype = {
     isFirefox: function(platform) {
       return platform.indexOf("Firefox") !== -1;
     },
 
+    isFirefoxOS: function(platform) {
+      // So far WebActivities are exposed only in FxOS, but they may be
+      // exposed in Firefox Desktop soon, so we check for its existence
+      // and also check if the UA belongs to a mobile platform.
+      // XXX WebActivities are also exposed in WebRT on Firefox for Android,
+      //     so we need a better check. Bug 1065403.
+      return !!window.MozActivity && /mobi/i.test(platform);
+    },
+
     isIOS: function(platform) {
       return this._iOSRegex.test(platform);
     },
 
     locationHash: function() {
       return window.location.hash;
     }
   };
--- a/browser/components/loop/standalone/Makefile
+++ b/browser/components/loop/standalone/Makefile
@@ -68,8 +68,11 @@ remove_old_config:
 # working with that deployment.
 .PHONY: config
 config:
 	@echo "var loop = loop || {};" > content/config.js
 	@echo "loop.config = loop.config || {};" >> content/config.js
 	@echo "loop.config.serverUrl = '`echo $(LOOP_SERVER_URL)`';" >> content/config.js
 	@echo "loop.config.feedbackApiUrl = '`echo $(LOOP_FEEDBACK_API_URL)`';" >> content/config.js
 	@echo "loop.config.feedbackProductName = '`echo $(LOOP_FEEDBACK_PRODUCT_NAME)`';" >> content/config.js
+	@echo "loop.config.fxosApp = loop.config.fxosApp || {};" >> content/config.js
+	@echo "loop.config.fxosApp.name = 'Loop';" >> content/config.js
+	@echo "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';" >> content/config.js
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -1,15 +1,15 @@
 /** @jsx React.DOM */
 
 /* 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/. */
 
-/* global loop:true, React */
+/* global loop:true, React, MozActivity */
 /* jshint newcap:false, maxlen:false */
 
 var loop = loop || {};
 loop.webapp = (function($, _, OT, mozL10n) {
   "use strict";
 
   loop.config = loop.config || {};
   loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
@@ -117,16 +117,105 @@ loop.webapp = (function($, _, OT, mozL10
       return (
         React.DOM.h1({className: "standalone-header-title"}, 
           React.DOM.strong(null, mozL10n.get("brandShortname")), " ", mozL10n.get("clientShortname")
         )
       );
     }
   });
 
+  /**
+   * The Firefox Marketplace exposes a web page that contains a postMesssage
+   * based API that wraps a small set of functionality from the WebApps API
+   * that allow us to request the installation of apps given their manifest
+   * URL. We will be embedding the content of this web page within an hidden
+   * iframe in case that we need to request the installation of the FxOS Loop
+   * client.
+   */
+  var FxOSHiddenMarketplace = React.createClass({displayName: 'FxOSHiddenMarketplace',
+    render: function() {
+      return React.DOM.iframe({id: "marketplace", src: this.props.marketplaceSrc, hidden: true});
+    },
+
+    componentDidUpdate: function() {
+      // This happens only once when we change the 'src' property of the iframe.
+      if (this.props.onMarketplaceMessage) {
+        // The reason for listening on the global window instead of on the
+        // iframe content window is because the Marketplace is doing a
+        // window.top.postMessage.
+        window.addEventListener("message", this.props.onMarketplaceMessage);
+      }
+    }
+  });
+
+  var FxOSConversationModel = Backbone.Model.extend({
+    setupOutgoingCall: function() {
+      // The FxOS Loop client exposes a "loop-call" activity. If we get the
+      // activity onerror callback it means that there is no "loop-call"
+      // activity handler available and so no FxOS Loop client installed.
+      var request = new MozActivity({
+        name: "loop-call",
+        data: {
+          type: "loop/token",
+          token: this.get("loopToken"),
+          callerId: this.get("callerId"),
+          callType: this.get("callType")
+        }
+      });
+
+      request.onsuccess = function() {};
+
+      request.onerror = (function(event) {
+        if (event.target.error.name !== "NO_PROVIDER") {
+          console.error ("Unexpected " + event.target.error.name);
+          this.trigger("session:error", "fxos_app_needed", {
+            fxosAppName: loop.config.fxosApp.name
+          });
+          return;
+        }
+        this.trigger("fxos:app-needed");
+      }).bind(this);
+    },
+
+    onMarketplaceMessage: function(event) {
+      var message = event.data;
+      switch (message.name) {
+        case "loaded":
+          var marketplace = window.document.getElementById("marketplace");
+          // Once we have it loaded, we request the installation of the FxOS
+          // Loop client app. We will be receiving the result of this action
+          // via postMessage from the child iframe.
+          marketplace.contentWindow.postMessage({
+            "name": "install-package",
+            "data": {
+              "product": {
+                "name": loop.config.fxosApp.name,
+                "manifest_url": loop.config.fxosApp.manifestUrl,
+                "is_packaged": true
+              }
+            }
+          }, "*");
+          break;
+        case "install-package":
+          window.removeEventListener("message", this.onMarketplaceMessage);
+          if (message.error) {
+            console.error(message.error.error);
+            this.trigger("session:error", "fxos_app_needed", {
+              fxosAppName: loop.config.fxosApp.name
+            });
+            return;
+          }
+          // We installed the FxOS app \o/, so we can continue with the call
+          // process.
+          this.setupOutgoingCall();
+          break;
+      }
+    }
+  });
+
   var ConversationHeader = React.createClass({displayName: 'ConversationHeader',
     render: function() {
       var cx = React.addons.classSet;
       var conversationUrl = location.href;
 
       var urlCreationDateClasses = cx({
         "light-color-font": true,
         "call-url-date": true, /* Used as a handler in the tests */
@@ -228,18 +317,20 @@ loop.webapp = (function($, _, OT, mozL10
    * as a `model` property.
    *
    * Required properties:
    * - {loop.shared.models.ConversationModel}    model    Conversation model.
    * - {loop.shared.models.NotificationCollection} notifications
    */
   var StartConversationView = React.createClass({displayName: 'StartConversationView',
     propTypes: {
-      model: React.PropTypes.instanceOf(sharedModels.ConversationModel)
-                                       .isRequired,
+      model: React.PropTypes.oneOfType([
+               React.PropTypes.instanceOf(sharedModels.ConversationModel),
+               React.PropTypes.instanceOf(FxOSConversationModel)
+             ]).isRequired,
       // XXX Check more tightly here when we start injecting window.loop.*
       notifications: React.PropTypes.object.isRequired,
       client: React.PropTypes.object.isRequired
     },
 
     getDefaultProps: function() {
       return {showCallOptionsMenu: false};
     },
@@ -252,25 +343,39 @@ loop.webapp = (function($, _, OT, mozL10
       };
     },
 
     componentDidMount: function() {
       // Listen for events & hide dropdown menu if user clicks away
       window.addEventListener("click", this.clickHandler);
       this.props.model.listenTo(this.props.model, "session:error",
                                 this._onSessionError);
+      this.props.model.listenTo(this.props.model, "fxos:app-needed",
+                                this._onFxOSAppNeeded);
       this.props.client.requestCallUrlInfo(this.props.model.get("loopToken"),
                                            this._setConversationTimestamp);
     },
 
-    _onSessionError: function(error) {
-      console.error(error);
-      this.props.notifications.errorL10n("unable_retrieve_call_info");
+    _onSessionError: function(error, l10nProps) {
+      var errorL10n = error || "unable_retrieve_call_info";
+      this.props.notifications.errorL10n(errorL10n, l10nProps);
+      console.error(errorL10n);
     },
 
+    _onFxOSAppNeeded: function() {
+      this.setState({
+        marketplaceSrc: loop.config.marketplaceUrl
+      });
+      this.setState({
+        onMarketplaceMessage: this.props.model.onMarketplaceMessage.bind(
+          this.props.model
+        )
+      });
+     },
+
     /**
      * Initiates the call.
      * Takes in a call type parameter "audio" or "audio-video" and returns
      * a function that initiates the call. React click handler requires a function
      * to be called when that event happenes.
      *
      * @param {string} User call type choice "audio" or "audio-video"
      */
@@ -325,16 +430,20 @@ loop.webapp = (function($, _, OT, mozL10
         "native-dropdown-large-parent": true,
         "standalone-dropdown-menu": true,
         "visually-hidden": !this.state.showCallOptionsMenu
       });
       var tosClasses = React.addons.classSet({
         "terms-service": true,
         hide: (localStorage.getItem("has-seen-tos") === "true")
       });
+      var chevronClasses = React.addons.classSet({
+        "btn-chevron": true,
+        "disabled": this.state.disableCallButton
+      });
 
       return (
         React.DOM.div({className: "container"}, 
           React.DOM.div({className: "container-box"}, 
 
             ConversationHeader({
               urlCreationDateString: this.state.urlCreationDateString}), 
 
@@ -355,17 +464,17 @@ loop.webapp = (function($, _, OT, mozL10
                             disabled: this.state.disableCallButton, 
                             title: mozL10n.get("initiate_audio_video_call_tooltip2")}, 
                       React.DOM.span({className: "standalone-call-btn-text"}, 
                         mozL10n.get("initiate_audio_video_call_button2")
                       ), 
                       React.DOM.span({className: "standalone-call-btn-video-icon"})
                     ), 
 
-                    React.DOM.div({className: "btn-chevron", 
+                    React.DOM.div({className: chevronClasses, 
                          onClick: this._toggleCallOptionsMenu}
                     )
 
                   ), 
 
                   React.DOM.ul({className: dropdownMenuClasses}, 
                     React.DOM.li(null, 
                       /*
@@ -383,16 +492,20 @@ loop.webapp = (function($, _, OT, mozL10
               ), 
               React.DOM.div({className: "flex-padding-1"})
             ), 
 
             React.DOM.p({className: tosClasses, 
                dangerouslySetInnerHTML: {__html: tosHTML}})
           ), 
 
+          FxOSHiddenMarketplace({
+            marketplaceSrc: this.state.marketplaceSrc, 
+            onMarketplaceMessage: this.state.onMarketplaceMessage}), 
+
           ConversationFooter(null)
         )
       );
     }
   });
 
   /**
    * Ended conversation view.
@@ -429,18 +542,20 @@ loop.webapp = (function($, _, OT, mozL10
    * This view manages the outgoing conversation views - from
    * call initiation through to the actual conversation and call end.
    *
    * At the moment, it does more than that, these parts need refactoring out.
    */
   var OutgoingConversationView = React.createClass({displayName: 'OutgoingConversationView',
     propTypes: {
       client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
-                         .isRequired,
+      conversation: React.PropTypes.oneOfType([
+        React.PropTypes.instanceOf(sharedModels.ConversationModel),
+        React.PropTypes.instanceOf(FxOSConversationModel)
+      ]).isRequired,
       helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
       notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
                           .isRequired,
       sdk: React.PropTypes.object.isRequired,
       feedbackApiClient: React.PropTypes.object.isRequired
     },
 
     getInitialState: function() {
@@ -684,18 +799,20 @@ loop.webapp = (function($, _, OT, mozL10
 
   /**
    * Webapp Root View. This is the main, single, view that controls the display
    * of the webapp page.
    */
   var WebappRootView = React.createClass({displayName: 'WebappRootView',
     propTypes: {
       client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
-                         .isRequired,
+      conversation: React.PropTypes.oneOfType([
+        React.PropTypes.instanceOf(sharedModels.ConversationModel),
+        React.PropTypes.instanceOf(FxOSConversationModel)
+      ]).isRequired,
       helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
       notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
                           .isRequired,
       sdk: React.PropTypes.object.isRequired,
       feedbackApiClient: React.PropTypes.object.isRequired
     },
 
     getInitialState: function() {
@@ -731,19 +848,25 @@ loop.webapp = (function($, _, OT, mozL10
    * App initialization.
    */
   function init() {
     var helper = new sharedUtils.Helper();
     var client = new loop.StandaloneClient({
       baseServerUrl: loop.config.serverUrl
     });
     var notifications = new sharedModels.NotificationCollection();
-    var conversation = new sharedModels.ConversationModel({}, {
-      sdk: OT
-    });
+    var conversation
+    if (helper.isFirefoxOS(navigator.userAgent)) {
+      conversation = new FxOSConversationModel();
+    } else {
+      conversation = new sharedModels.ConversationModel({}, {
+        sdk: OT
+      });
+    }
+
     var feedbackApiClient = new loop.FeedbackAPIClient(
       loop.config.feedbackApiUrl, {
         product: loop.config.feedbackProductName,
         user_agent: navigator.userAgent,
         url: document.location.origin
       });
 
     // Obtain the loopToken and pass it to the conversation
@@ -772,11 +895,12 @@ loop.webapp = (function($, _, OT, mozL10
     StartConversationView: StartConversationView,
     OutgoingConversationView: OutgoingConversationView,
     EndedConversationView: EndedConversationView,
     HomeView: HomeView,
     UnsupportedBrowserView: UnsupportedBrowserView,
     UnsupportedDeviceView: UnsupportedDeviceView,
     init: init,
     PromoteFirefoxView: PromoteFirefoxView,
-    WebappRootView: WebappRootView
+    WebappRootView: WebappRootView,
+    FxOSConversationModel: FxOSConversationModel
   };
 })(jQuery, _, window.OT, navigator.mozL10n);
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -1,15 +1,15 @@
 /** @jsx React.DOM */
 
 /* 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/. */
 
-/* global loop:true, React */
+/* global loop:true, React, MozActivity */
 /* jshint newcap:false, maxlen:false */
 
 var loop = loop || {};
 loop.webapp = (function($, _, OT, mozL10n) {
   "use strict";
 
   loop.config = loop.config || {};
   loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
@@ -117,16 +117,105 @@ loop.webapp = (function($, _, OT, mozL10
       return (
         <h1 className="standalone-header-title">
           <strong>{mozL10n.get("brandShortname")}</strong> {mozL10n.get("clientShortname")}
         </h1>
       );
     }
   });
 
+  /**
+   * The Firefox Marketplace exposes a web page that contains a postMesssage
+   * based API that wraps a small set of functionality from the WebApps API
+   * that allow us to request the installation of apps given their manifest
+   * URL. We will be embedding the content of this web page within an hidden
+   * iframe in case that we need to request the installation of the FxOS Loop
+   * client.
+   */
+  var FxOSHiddenMarketplace = React.createClass({
+    render: function() {
+      return <iframe id="marketplace" src={this.props.marketplaceSrc} hidden/>;
+    },
+
+    componentDidUpdate: function() {
+      // This happens only once when we change the 'src' property of the iframe.
+      if (this.props.onMarketplaceMessage) {
+        // The reason for listening on the global window instead of on the
+        // iframe content window is because the Marketplace is doing a
+        // window.top.postMessage.
+        window.addEventListener("message", this.props.onMarketplaceMessage);
+      }
+    }
+  });
+
+  var FxOSConversationModel = Backbone.Model.extend({
+    setupOutgoingCall: function() {
+      // The FxOS Loop client exposes a "loop-call" activity. If we get the
+      // activity onerror callback it means that there is no "loop-call"
+      // activity handler available and so no FxOS Loop client installed.
+      var request = new MozActivity({
+        name: "loop-call",
+        data: {
+          type: "loop/token",
+          token: this.get("loopToken"),
+          callerId: this.get("callerId"),
+          callType: this.get("callType")
+        }
+      });
+
+      request.onsuccess = function() {};
+
+      request.onerror = (function(event) {
+        if (event.target.error.name !== "NO_PROVIDER") {
+          console.error ("Unexpected " + event.target.error.name);
+          this.trigger("session:error", "fxos_app_needed", {
+            fxosAppName: loop.config.fxosApp.name
+          });
+          return;
+        }
+        this.trigger("fxos:app-needed");
+      }).bind(this);
+    },
+
+    onMarketplaceMessage: function(event) {
+      var message = event.data;
+      switch (message.name) {
+        case "loaded":
+          var marketplace = window.document.getElementById("marketplace");
+          // Once we have it loaded, we request the installation of the FxOS
+          // Loop client app. We will be receiving the result of this action
+          // via postMessage from the child iframe.
+          marketplace.contentWindow.postMessage({
+            "name": "install-package",
+            "data": {
+              "product": {
+                "name": loop.config.fxosApp.name,
+                "manifest_url": loop.config.fxosApp.manifestUrl,
+                "is_packaged": true
+              }
+            }
+          }, "*");
+          break;
+        case "install-package":
+          window.removeEventListener("message", this.onMarketplaceMessage);
+          if (message.error) {
+            console.error(message.error.error);
+            this.trigger("session:error", "fxos_app_needed", {
+              fxosAppName: loop.config.fxosApp.name
+            });
+            return;
+          }
+          // We installed the FxOS app \o/, so we can continue with the call
+          // process.
+          this.setupOutgoingCall();
+          break;
+      }
+    }
+  });
+
   var ConversationHeader = React.createClass({
     render: function() {
       var cx = React.addons.classSet;
       var conversationUrl = location.href;
 
       var urlCreationDateClasses = cx({
         "light-color-font": true,
         "call-url-date": true, /* Used as a handler in the tests */
@@ -228,18 +317,20 @@ loop.webapp = (function($, _, OT, mozL10
    * as a `model` property.
    *
    * Required properties:
    * - {loop.shared.models.ConversationModel}    model    Conversation model.
    * - {loop.shared.models.NotificationCollection} notifications
    */
   var StartConversationView = React.createClass({
     propTypes: {
-      model: React.PropTypes.instanceOf(sharedModels.ConversationModel)
-                                       .isRequired,
+      model: React.PropTypes.oneOfType([
+               React.PropTypes.instanceOf(sharedModels.ConversationModel),
+               React.PropTypes.instanceOf(FxOSConversationModel)
+             ]).isRequired,
       // XXX Check more tightly here when we start injecting window.loop.*
       notifications: React.PropTypes.object.isRequired,
       client: React.PropTypes.object.isRequired
     },
 
     getDefaultProps: function() {
       return {showCallOptionsMenu: false};
     },
@@ -252,25 +343,39 @@ loop.webapp = (function($, _, OT, mozL10
       };
     },
 
     componentDidMount: function() {
       // Listen for events & hide dropdown menu if user clicks away
       window.addEventListener("click", this.clickHandler);
       this.props.model.listenTo(this.props.model, "session:error",
                                 this._onSessionError);
+      this.props.model.listenTo(this.props.model, "fxos:app-needed",
+                                this._onFxOSAppNeeded);
       this.props.client.requestCallUrlInfo(this.props.model.get("loopToken"),
                                            this._setConversationTimestamp);
     },
 
-    _onSessionError: function(error) {
-      console.error(error);
-      this.props.notifications.errorL10n("unable_retrieve_call_info");
+    _onSessionError: function(error, l10nProps) {
+      var errorL10n = error || "unable_retrieve_call_info";
+      this.props.notifications.errorL10n(errorL10n, l10nProps);
+      console.error(errorL10n);
     },
 
+    _onFxOSAppNeeded: function() {
+      this.setState({
+        marketplaceSrc: loop.config.marketplaceUrl
+      });
+      this.setState({
+        onMarketplaceMessage: this.props.model.onMarketplaceMessage.bind(
+          this.props.model
+        )
+      });
+     },
+
     /**
      * Initiates the call.
      * Takes in a call type parameter "audio" or "audio-video" and returns
      * a function that initiates the call. React click handler requires a function
      * to be called when that event happenes.
      *
      * @param {string} User call type choice "audio" or "audio-video"
      */
@@ -325,16 +430,20 @@ loop.webapp = (function($, _, OT, mozL10
         "native-dropdown-large-parent": true,
         "standalone-dropdown-menu": true,
         "visually-hidden": !this.state.showCallOptionsMenu
       });
       var tosClasses = React.addons.classSet({
         "terms-service": true,
         hide: (localStorage.getItem("has-seen-tos") === "true")
       });
+      var chevronClasses = React.addons.classSet({
+        "btn-chevron": true,
+        "disabled": this.state.disableCallButton
+      });
 
       return (
         <div className="container">
           <div className="container-box">
 
             <ConversationHeader
               urlCreationDateString={this.state.urlCreationDateString} />
 
@@ -355,17 +464,17 @@ loop.webapp = (function($, _, OT, mozL10
                             disabled={this.state.disableCallButton}
                             title={mozL10n.get("initiate_audio_video_call_tooltip2")} >
                       <span className="standalone-call-btn-text">
                         {mozL10n.get("initiate_audio_video_call_button2")}
                       </span>
                       <span className="standalone-call-btn-video-icon"></span>
                     </button>
 
-                    <div className="btn-chevron"
+                    <div className={chevronClasses}
                          onClick={this._toggleCallOptionsMenu}>
                     </div>
 
                   </div>
 
                   <ul className={dropdownMenuClasses}>
                     <li>
                       {/*
@@ -383,16 +492,20 @@ loop.webapp = (function($, _, OT, mozL10
               </div>
               <div className="flex-padding-1"></div>
             </div>
 
             <p className={tosClasses}
                dangerouslySetInnerHTML={{__html: tosHTML}}></p>
           </div>
 
+          <FxOSHiddenMarketplace
+            marketplaceSrc={this.state.marketplaceSrc}
+            onMarketplaceMessage= {this.state.onMarketplaceMessage} />
+
           <ConversationFooter />
         </div>
       );
     }
   });
 
   /**
    * Ended conversation view.
@@ -429,18 +542,20 @@ loop.webapp = (function($, _, OT, mozL10
    * This view manages the outgoing conversation views - from
    * call initiation through to the actual conversation and call end.
    *
    * At the moment, it does more than that, these parts need refactoring out.
    */
   var OutgoingConversationView = React.createClass({
     propTypes: {
       client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
-                         .isRequired,
+      conversation: React.PropTypes.oneOfType([
+        React.PropTypes.instanceOf(sharedModels.ConversationModel),
+        React.PropTypes.instanceOf(FxOSConversationModel)
+      ]).isRequired,
       helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
       notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
                           .isRequired,
       sdk: React.PropTypes.object.isRequired,
       feedbackApiClient: React.PropTypes.object.isRequired
     },
 
     getInitialState: function() {
@@ -684,18 +799,20 @@ loop.webapp = (function($, _, OT, mozL10
 
   /**
    * Webapp Root View. This is the main, single, view that controls the display
    * of the webapp page.
    */
   var WebappRootView = React.createClass({
     propTypes: {
       client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
-                         .isRequired,
+      conversation: React.PropTypes.oneOfType([
+        React.PropTypes.instanceOf(sharedModels.ConversationModel),
+        React.PropTypes.instanceOf(FxOSConversationModel)
+      ]).isRequired,
       helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
       notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
                           .isRequired,
       sdk: React.PropTypes.object.isRequired,
       feedbackApiClient: React.PropTypes.object.isRequired
     },
 
     getInitialState: function() {
@@ -731,19 +848,25 @@ loop.webapp = (function($, _, OT, mozL10
    * App initialization.
    */
   function init() {
     var helper = new sharedUtils.Helper();
     var client = new loop.StandaloneClient({
       baseServerUrl: loop.config.serverUrl
     });
     var notifications = new sharedModels.NotificationCollection();
-    var conversation = new sharedModels.ConversationModel({}, {
-      sdk: OT
-    });
+    var conversation
+    if (helper.isFirefoxOS(navigator.userAgent)) {
+      conversation = new FxOSConversationModel();
+    } else {
+      conversation = new sharedModels.ConversationModel({}, {
+        sdk: OT
+      });
+    }
+
     var feedbackApiClient = new loop.FeedbackAPIClient(
       loop.config.feedbackApiUrl, {
         product: loop.config.feedbackProductName,
         user_agent: navigator.userAgent,
         url: document.location.origin
       });
 
     // Obtain the loopToken and pass it to the conversation
@@ -772,11 +895,12 @@ loop.webapp = (function($, _, OT, mozL10
     StartConversationView: StartConversationView,
     OutgoingConversationView: OutgoingConversationView,
     EndedConversationView: EndedConversationView,
     HomeView: HomeView,
     UnsupportedBrowserView: UnsupportedBrowserView,
     UnsupportedDeviceView: UnsupportedDeviceView,
     init: init,
     PromoteFirefoxView: PromoteFirefoxView,
-    WebappRootView: WebappRootView
+    WebappRootView: WebappRootView,
+    FxOSConversationModel: FxOSConversationModel
   };
 })(jQuery, _, window.OT, navigator.mozL10n);
--- a/browser/components/loop/standalone/content/l10n/loop.en-US.properties
+++ b/browser/components/loop/standalone/content/l10n/loop.en-US.properties
@@ -41,16 +41,17 @@ legal_text_and_links=By using this produ
 terms_of_use_link_text=Terms of use
 privacy_notice_link_text=Privacy notice
 brandShortname=Firefox
 clientShortname=WebRTC!
 ## LOCALIZATION NOTE (call_url_creation_date_label): Example output: (from May 26, 2014)
 call_url_creation_date_label=(from {{call_url_creation_date}})
 call_progress_connecting_description=Connecting…
 call_progress_ringing_description=Ringing…
+fxos_app_needed=Please install the {{fxosAppName}} app from the Firefox Marketplace.
 
 feedback_call_experience_heading2=How was your conversation?
 feedback_what_makes_you_sad=What makes you sad?
 feedback_thank_you_heading=Thank you for your feedback!
 feedback_category_audio_quality=Audio quality
 feedback_category_video_quality=Video quality
 feedback_category_was_disconnected=Was disconnected
 feedback_category_confusing=Confusing
--- a/browser/components/loop/standalone/server.js
+++ b/browser/components/loop/standalone/server.js
@@ -16,16 +16,22 @@ function getConfigFile(req, res) {
 
   res.set('Content-Type', 'text/javascript');
   res.send([
     "var loop = loop || {};",
     "loop.config = loop.config || {};",
     "loop.config.serverUrl = 'http://localhost:" + loopServerPort + "';",
     "loop.config.feedbackApiUrl = '" + feedbackApiUrl + "';",
     "loop.config.feedbackProductName = '" + feedbackProductName + "';",
+    // XXX Update with the real marketplace url once the FxOS Loop app is
+    //     uploaded to the marketplace bug 1053424
+    "loop.config.marketplaceUrl = 'http://fake-market.herokuapp.com/iframe-install.html'",
+    "loop.config.fxosApp = loop.config.fxosApp || {};",
+    "loop.config.fxosApp.name = 'Loop';",
+    "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';"
   ].join("\n"));
 }
 
 app.get('/content/config.js', getConfigFile);
 
 // This lets /test/ be mapped to the right place for running tests
 app.use('/', express.static(__dirname + '/../'));
 
--- a/browser/components/loop/test/shared/models_test.js
+++ b/browser/components/loop/test/shared/models_test.js
@@ -350,18 +350,18 @@ describe("loop.shared.models", function(
     });
   });
 
   describe("NotificationCollection", function() {
     var collection, notifData, testNotif;
 
     beforeEach(function() {
       collection = new sharedModels.NotificationCollection();
-      sandbox.stub(l10n, "get", function(x) {
-        return "translated:" + x;
+      sandbox.stub(l10n, "get", function(x, y) {
+        return "translated:" + x + (y ? ':' + y : '');
       });
       notifData = {level: "error", message: "plop"};
       testNotif = new sharedModels.NotificationModel(notifData);
     });
 
     describe("#warn", function() {
       it("should add a warning notification to the stack", function() {
         collection.warn("watch out");
@@ -395,12 +395,21 @@ describe("loop.shared.models", function(
     describe("#errorL10n", function() {
       it("should notify an error using a l10n string id", function() {
         collection.errorL10n("fakeId");
 
         expect(collection).to.have.length.of(1);
         expect(collection.at(0).get("level")).eql("error");
         expect(collection.at(0).get("message")).eql("translated:fakeId");
       });
+
+      it("should notify an error using a l10n string id + l10n properties",
+        function() {
+          collection.errorL10n("fakeId", "fakeProp");
+
+          expect(collection).to.have.length.of(1);
+          expect(collection.at(0).get("level")).eql("error");
+          expect(collection.at(0).get("message")).eql("translated:fakeId:fakeProp");
+      });
     });
 
   });
 });
--- a/browser/components/loop/test/shared/utils_test.js
+++ b/browser/components/loop/test/shared/utils_test.js
@@ -48,16 +48,49 @@ describe("loop.shared.utils", function()
         expect(helper.isFirefox("Firefox/Gecko")).eql(true);
         expect(helper.isFirefox("Gecko/Firefox/Chuck Norris")).eql(true);
       });
 
       it("shouldn't detect Firefox with other platforms", function() {
         expect(helper.isFirefox("Opera")).eql(false);
       });
     });
+
+    describe("#isFirefoxOS", function() {
+      describe("without mozActivities", function() {
+        it("shouldn't detect FirefoxOS on mobile platform", function() {
+          expect(helper.isFirefoxOS("mobi")).eql(false);
+        });
+
+        it("shouldn't detect FirefoxOS on non mobile platform", function() {
+          expect(helper.isFirefoxOS("whatever")).eql(false);
+        });
+      });
+
+      describe("with mozActivities", function() {
+        var realMozActivity;
+
+        before(function() {
+          realMozActivity = window.MozActivity;
+          window.MozActivity = {};
+        });
+
+        after(function() {
+          window.MozActivity = realMozActivity;
+        });
+
+        it("should detect FirefoxOS on mobile platform", function() {
+          expect(helper.isFirefoxOS("mobi")).eql(true);
+        });
+
+        it("shouldn't detect FirefoxOS on non mobile platform", function() {
+          expect(helper.isFirefoxOS("whatever")).eql(false);
+        });
+      });
+    });
   });
 
   describe("#getBoolPreference", function() {
     afterEach(function() {
       navigator.mozLoop = undefined;
       localStorage.removeItem("test.true");
     });
 
--- a/browser/components/loop/test/shared/views_test.js
+++ b/browser/components/loop/test/shared/views_test.js
@@ -15,16 +15,19 @@ describe("loop.shared.views", function()
   var sharedModels = loop.shared.models,
       sharedViews = loop.shared.views,
       getReactElementByClass = TestUtils.findRenderedDOMComponentWithClass,
       sandbox;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     sandbox.useFakeTimers(); // exposes sandbox.clock as a fake timer
+    sandbox.stub(l10n, "get", function(x) {
+      return "translated:" + x;
+    });
   });
 
   afterEach(function() {
     $("#fixtures").empty();
     sandbox.restore();
   });
 
   describe("MediaControlButton", function() {
@@ -419,19 +422,16 @@ describe("loop.shared.views", function()
       });
     });
   });
 
   describe("FeedbackView", function() {
     var comp, fakeFeedbackApiClient;
 
     beforeEach(function() {
-      sandbox.stub(l10n, "get", function(x) {
-        return x;
-      });
       fakeFeedbackApiClient = {send: sandbox.stub()};
       comp = TestUtils.renderIntoDocument(sharedViews.FeedbackView({
         feedbackApiClient: fakeFeedbackApiClient
       }));
     });
 
     // local test helpers
     function clickHappyFace(comp) {
@@ -596,19 +596,16 @@ describe("loop.shared.views", function()
   describe("NotificationListView", function() {
     var coll, view, testNotif;
 
     function mountTestComponent(props) {
       return TestUtils.renderIntoDocument(sharedViews.NotificationListView(props));
     }
 
     beforeEach(function() {
-      sandbox.stub(l10n, "get", function(x) {
-        return "translated:" + x;
-      });
       coll = new sharedModels.NotificationCollection();
       view = mountTestComponent({notifications: coll});
       testNotif = {level: "warning", message: "foo"};
       sinon.spy(view, "render");
     });
 
     afterEach(function() {
       view.render.restore();
--- a/browser/components/loop/test/standalone/webapp_test.js
+++ b/browser/components/loop/test/standalone/webapp_test.js
@@ -379,17 +379,17 @@ describe("loop.webapp", function() {
         describe("subscribedStream", function() {
           it("should not notify the websocket if only one stream is up",
             function() {
               conversation.set("subscribedStream", true);
 
               sinon.assert.notCalled(ocView._websocket.mediaUp);
             });
 
-          it("should notify the websocket that media is up if both streams" +
+          it("should notify tloadhe websocket that media is up if both streams" +
              "are connected", function() {
               conversation.set("publishedStream", true);
               conversation.set("subscribedStream", true);
 
               sinon.assert.calledOnce(ocView._websocket.mediaUp);
             });
         });
       });
@@ -686,50 +686,79 @@ describe("loop.webapp", function() {
 
       beforeEach(function() {
         conversation = new sharedModels.ConversationModel({
           loopToken: "fake"
         }, {
           sdk: {}
         });
 
-        sandbox.spy(conversation, "listenTo");
+        conversation.onMarketplaceMessage = function() {};
+        sandbox.stub(notifications, "errorL10n");
         requestCallUrlInfo = sandbox.stub();
 
         view = React.addons.TestUtils.renderIntoDocument(
             loop.webapp.StartConversationView({
               model: conversation,
               notifications: notifications,
               client: {requestCallUrlInfo: requestCallUrlInfo}
             })
           );
+
+        loop.config.marketplaceUrl = "http://market/";
       });
 
       it("should call requestCallUrlInfo", function() {
         sinon.assert.calledOnce(requestCallUrlInfo);
         sinon.assert.calledWithExactly(requestCallUrlInfo,
                                        sinon.match.string,
                                        sinon.match.func);
       });
 
-      it("should listen for session:error events", function() {
-        sinon.assert.calledOnce(conversation.listenTo);
-        sinon.assert.calledWithExactly(conversation.listenTo, conversation,
-                                       "session:error", sinon.match.func);
+      it("should add a notification when a session:error model event is " +
+         " received without an argument", function() {
+        conversation.trigger("session:error");
+
+        sinon.assert.calledOnce(notifications.errorL10n);
+        sinon.assert.calledWithExactly(notifications.errorL10n,
+          sinon.match.string, undefined);
       });
 
-      it("should trigger a notication when a session:error model event is " +
-         " received", function() {
-        sandbox.stub(notifications, "errorL10n");
-        conversation.trigger("session:error", "tech error");
+      it("should add a notification with the custom message id when a " +
+         "session:error event is fired with an argument", function() {
+        conversation.trigger("session:error", "tech_error");
 
         sinon.assert.calledOnce(notifications.errorL10n);
         sinon.assert.calledWithExactly(notifications.errorL10n,
-                                       "unable_retrieve_call_info");
+                                       "tech_error", undefined);
+      });
+
+      it("should add a notification with the custom message id when a " +
+         "session:error event is fired with an argument and parameters",
+         function() {
+          conversation.trigger("session:error", "tech_error", {param: "value"});
+
+          sinon.assert.calledOnce(notifications.errorL10n);
+          sinon.assert.calledWithExactly(notifications.errorL10n,
+                                         "tech_error", { param: "value" });
       });
+
+      it("should set marketplace hidden iframe src when fxos:app-needed is " +
+         "triggered", function(done) {
+        var marketplace = view.getDOMNode().querySelector("#marketplace");
+        expect(marketplace.src).to.be.equal("");
+
+        conversation.trigger("fxos:app-needed");
+
+        view.forceUpdate(function() {
+          expect(marketplace.src).to.be.equal(loop.config.marketplaceUrl);
+          done();
+        });
+      });
+
     });
 
     describe("#render", function() {
       var conversation, view, requestCallUrlInfo, oldLocalStorageValue;
 
       beforeEach(function() {
         oldLocalStorageValue = localStorage.getItem("has-seen-tos");
         localStorage.removeItem("has-seen-tos");
@@ -821,9 +850,203 @@ describe("loop.webapp", function() {
         var comp = TestUtils.renderIntoDocument(loop.webapp.PromoteFirefoxView({
           helper: {isFirefox: function() { return false; }}
         }));
 
         expect(comp.getDOMNode().querySelectorAll("h3").length).eql(1);
       });
     });
   });
+
+  describe("Firefox OS", function() {
+    var conversation, client;
+
+    before(function() {
+      client = new loop.StandaloneClient({
+        baseServerUrl: "http://fake.example.com"
+      });
+      sandbox.stub(client, "requestCallInfo");
+      conversation = new sharedModels.ConversationModel({}, {
+        sdk: {},
+        pendingCallTimeout: 1000
+      });
+    });
+
+    describe("Setup call", function() {
+      var conversation, setupOutgoingCall, view, requestCallUrlInfo;
+
+      beforeEach(function() {
+        conversation = new loop.webapp.FxOSConversationModel({
+          loopToken: "fakeToken"
+        });
+        setupOutgoingCall = sandbox.stub(conversation, "setupOutgoingCall");
+
+        var standaloneClientStub = {
+          requestCallUrlInfo: function(token, cb) {
+            cb(null, {urlCreationDate: 0});
+          },
+          settings: {baseServerUrl: loop.webapp.baseServerUrl}
+        };
+
+        view = React.addons.TestUtils.renderIntoDocument(
+            loop.webapp.StartConversationView({
+              model: conversation,
+              notifications: notifications,
+              client: standaloneClientStub
+            })
+        );
+      });
+
+      it("should start the conversation establishment process", function() {
+        var button = view.getDOMNode().querySelector("button");
+        React.addons.TestUtils.Simulate.click(button);
+
+        sinon.assert.calledOnce(setupOutgoingCall);
+      });
+    });
+
+    describe("FxOSConversationModel", function() {
+      var model, realMozActivity;
+
+      before(function() {
+        model = new loop.webapp.FxOSConversationModel({
+          loopToken: "fakeToken",
+          callerId: "callerId",
+          callType: "callType"
+        });
+
+        realMozActivity = window.MozActivity;
+
+        loop.config.fxosApp = {
+          name: "Firefox Hello"
+        };
+      });
+
+      after(function() {
+        window.MozActivity = realMozActivity;
+      });
+
+      describe("setupOutgoingCall", function() {
+        var _activityProps, _onerror, trigger;
+
+        function fireError(errorName) {
+          _onerror({
+            target: {
+              error: {
+                name: errorName
+              }
+            }
+          });
+        }
+
+        before(function() {
+          window.MozActivity = function(activityProps) {
+            _activityProps = activityProps;
+            return {
+              set onerror(callback) {
+                _onerror = callback;
+              }
+            };
+          };
+        });
+
+        after(function() {
+          window.MozActivity = realMozActivity;
+        });
+
+        beforeEach(function() {
+          trigger = sandbox.stub(model, "trigger");
+        });
+
+        afterEach(function() {
+          trigger.restore();
+        });
+
+        it("Activity properties", function() {
+          expect(_activityProps).to.not.exist;
+          model.setupOutgoingCall();
+          expect(_activityProps).to.exist;
+          expect(_activityProps).eql({
+            name: "loop-call",
+            data: {
+              type: "loop/token",
+              token: "fakeToken",
+              callerId: "callerId",
+              callType: "callType"
+            }
+          });
+        });
+
+        it("NO_PROVIDER activity error should trigger fxos:app-needed",
+          function() {
+            sinon.assert.notCalled(trigger);
+            model.setupOutgoingCall();
+            fireError("NO_PROVIDER");
+            sinon.assert.calledOnce(trigger);
+            sinon.assert.calledWithExactly(trigger, "fxos:app-needed");
+          }
+        );
+
+        it("Other activity error should trigger session:error",
+          function() {
+            sinon.assert.notCalled(trigger);
+            model.setupOutgoingCall();
+            fireError("whatever");
+            sinon.assert.calledOnce(trigger);
+            sinon.assert.calledWithExactly(trigger, "session:error",
+              "fxos_app_needed", { fxosAppName: loop.config.fxosApp.name });
+          }
+        );
+      });
+
+      describe("onMarketplaceMessage", function() {
+        var view, setupOutgoingCall, trigger;
+
+        before(function() {
+          view = React.addons.TestUtils.renderIntoDocument(
+            loop.webapp.StartConversationView({
+              model: model,
+              notifications: notifications,
+              client: {requestCallUrlInfo: sandbox.stub()}
+            })
+          );
+        });
+
+        beforeEach(function() {
+          setupOutgoingCall = sandbox.stub(model, "setupOutgoingCall");
+          trigger = sandbox.stub(model, "trigger");
+        });
+
+        afterEach(function() {
+          setupOutgoingCall.restore();
+          trigger.restore();
+        });
+
+        it("We should call trigger a FxOS outgoing call if we get " +
+           "install-package message without error", function() {
+          sinon.assert.notCalled(setupOutgoingCall);
+          model.onMarketplaceMessage({
+            data: {
+              name: "install-package"
+            }
+          });
+          sinon.assert.calledOnce(setupOutgoingCall);
+        });
+
+        it("We should trigger a session:error event if we get " +
+           "install-package message with an error", function() {
+          sinon.assert.notCalled(trigger);
+          sinon.assert.notCalled(setupOutgoingCall);
+          model.onMarketplaceMessage({
+            data: {
+              name: "install-package",
+              error: "error"
+            }
+          });
+          sinon.assert.notCalled(setupOutgoingCall);
+          sinon.assert.calledOnce(trigger);
+          sinon.assert.calledWithExactly(trigger, "session:error",
+            "fxos_app_needed", { fxosAppName: loop.config.fxosApp.name });
+        });
+      });
+    });
+  });
 });
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -144,33 +144,33 @@
             ), 
             Example({summary: "Error Notification - authenticated", dashed: "true", style: {width: "332px"}}, 
               PanelView({client: mockClient, notifications: errNotifications, 
                          userProfile: {email: "test@example.com"}})
             )
           ), 
 
           Section({name: "IncomingCallView"}, 
-            Example({summary: "Default / incoming video call", dashed: "true", style: {width: "280px"}}, 
+            Example({summary: "Default / incoming video call", dashed: "true", style: {width: "260px", height: "254px"}}, 
               React.DOM.div({className: "fx-embedded"}, 
                 IncomingCallView({model: mockConversationModel, 
                                   video: true})
               )
             ), 
 
-            Example({summary: "Default / incoming audio only call", dashed: "true", style: {width: "280px"}}, 
+            Example({summary: "Default / incoming audio only call", dashed: "true", style: {width: "260px", height: "254px"}}, 
               React.DOM.div({className: "fx-embedded"}, 
                 IncomingCallView({model: mockConversationModel, 
                                   video: false})
               )
             )
           ), 
 
           Section({name: "IncomingCallView-ActiveState"}, 
-            Example({summary: "Default", dashed: "true", style: {width: "280px"}}, 
+            Example({summary: "Default", dashed: "true", style: {width: "260px", height: "254px"}}, 
               React.DOM.div({className: "fx-embedded"}, 
                 IncomingCallView({model: mockConversationModel, 
                                    showDeclineMenu: true, 
                                    video: true})
               )
             )
           ), 
 
@@ -314,23 +314,23 @@
             )
           ), 
 
           Section({name: "FeedbackView"}, 
             React.DOM.p({className: "note"}, 
               React.DOM.strong(null, "Note:"), " For the useable demo, you can access submitted data at ", 
               React.DOM.a({href: "https://input.allizom.org/"}, "input.allizom.org"), "."
             ), 
-            Example({summary: "Default (useable demo)", dashed: "true", style: {width: "280px"}}, 
+            Example({summary: "Default (useable demo)", dashed: "true", style: {width: "260px"}}, 
               FeedbackView({feedbackApiClient: stageFeedbackApiClient})
             ), 
-            Example({summary: "Detailed form", dashed: "true", style: {width: "280px"}}, 
+            Example({summary: "Detailed form", dashed: "true", style: {width: "260px"}}, 
               FeedbackView({feedbackApiClient: stageFeedbackApiClient, step: "form"})
             ), 
-            Example({summary: "Thank you!", dashed: "true", style: {width: "280px"}}, 
+            Example({summary: "Thank you!", dashed: "true", style: {width: "260px"}}, 
               FeedbackView({feedbackApiClient: stageFeedbackApiClient, step: "finished"})
             )
           ), 
 
           Section({name: "CallUrlExpiredView"}, 
             Example({summary: "Firefox User"}, 
               CallUrlExpiredView({helper: {isFirefox: returnTrue}})
             ), 
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -144,33 +144,33 @@
             </Example>
             <Example summary="Error Notification - authenticated" dashed="true" style={{width: "332px"}}>
               <PanelView client={mockClient} notifications={errNotifications}
                          userProfile={{email: "test@example.com"}} />
             </Example>
           </Section>
 
           <Section name="IncomingCallView">
-            <Example summary="Default / incoming video call" dashed="true" style={{width: "280px"}}>
+            <Example summary="Default / incoming video call" dashed="true" style={{width: "260px", height: "254px"}}>
               <div className="fx-embedded">
                 <IncomingCallView model={mockConversationModel}
                                   video={true} />
               </div>
             </Example>
 
-            <Example summary="Default / incoming audio only call" dashed="true" style={{width: "280px"}}>
+            <Example summary="Default / incoming audio only call" dashed="true" style={{width: "260px", height: "254px"}}>
               <div className="fx-embedded">
                 <IncomingCallView model={mockConversationModel}
                                   video={false} />
               </div>
             </Example>
           </Section>
 
           <Section name="IncomingCallView-ActiveState">
-            <Example summary="Default" dashed="true" style={{width: "280px"}}>
+            <Example summary="Default" dashed="true" style={{width: "260px", height: "254px"}}>
               <div className="fx-embedded" >
                 <IncomingCallView  model={mockConversationModel}
                                    showDeclineMenu={true}
                                    video={true} />
               </div>
             </Example>
           </Section>
 
@@ -314,23 +314,23 @@
             </Example>
           </Section>
 
           <Section name="FeedbackView">
             <p className="note">
               <strong>Note:</strong> For the useable demo, you can access submitted data at&nbsp;
               <a href="https://input.allizom.org/">input.allizom.org</a>.
             </p>
-            <Example summary="Default (useable demo)" dashed="true" style={{width: "280px"}}>
+            <Example summary="Default (useable demo)" dashed="true" style={{width: "260px"}}>
               <FeedbackView feedbackApiClient={stageFeedbackApiClient} />
             </Example>
-            <Example summary="Detailed form" dashed="true" style={{width: "280px"}}>
+            <Example summary="Detailed form" dashed="true" style={{width: "260px"}}>
               <FeedbackView feedbackApiClient={stageFeedbackApiClient} step="form" />
             </Example>
-            <Example summary="Thank you!" dashed="true" style={{width: "280px"}}>
+            <Example summary="Thank you!" dashed="true" style={{width: "260px"}}>
               <FeedbackView feedbackApiClient={stageFeedbackApiClient} step="finished" />
             </Example>
           </Section>
 
           <Section name="CallUrlExpiredView">
             <Example summary="Firefox User">
               <CallUrlExpiredView helper={{isFirefox: returnTrue}} />
             </Example>
--- a/browser/devtools/canvasdebugger/panel.js
+++ b/browser/devtools/canvasdebugger/panel.js
@@ -61,12 +61,14 @@ CanvasDebuggerPanel.prototype = {
 
   destroy: function() {
     // Make sure this panel is not already destroyed.
     if (this._destroyer) {
       return this._destroyer;
     }
 
     return this._destroyer = this.panelWin.shutdownCanvasDebugger().then(() => {
+      // Destroy front to ensure packet handler is removed from client
+      this.panelWin.gFront.destroy();
       this.emit("destroyed");
     });
   }
 };
--- a/browser/devtools/canvasdebugger/test/browser.ini
+++ b/browser/devtools/canvasdebugger/test/browser.ini
@@ -1,10 +1,9 @@
 [DEFAULT]
-skip-if = e10s # Bug 1058879 - canvas debugger tests disabled with e10s
 subsuite = devtools
 support-files =
   doc_simple-canvas.html
   doc_simple-canvas-bitmasks.html
   doc_simple-canvas-deep-stack.html
   doc_simple-canvas-transparent.html
   doc_webgl-bindings.html
   doc_webgl-enum.html
@@ -15,16 +14,17 @@ support-files =
 [browser_canvas-actor-test-03.js]
 [browser_canvas-actor-test-04.js]
 [browser_canvas-actor-test-05.js]
 [browser_canvas-actor-test-06.js]
 [browser_canvas-actor-test-07.js]
 [browser_canvas-actor-test-08.js]
 [browser_canvas-actor-test-09.js]
 [browser_canvas-actor-test-10.js]
+skip-if = e10s # Bug 1058879 - canvas debugger tests disabled with e10s
 [browser_canvas-frontend-call-highlight.js]
 [browser_canvas-frontend-call-list.js]
 [browser_canvas-frontend-call-search.js]
 [browser_canvas-frontend-call-stack-01.js]
 [browser_canvas-frontend-call-stack-02.js]
 [browser_canvas-frontend-call-stack-03.js]
 [browser_canvas-frontend-clear.js]
 [browser_canvas-frontend-img-screenshots.js]
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-01.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-01.js
@@ -2,17 +2,16 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if the canvas debugger leaks on initialization and sudden destruction.
  * You can also use this initialization format as a template for other tests.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, front] = yield initCallWatcherBackend(SIMPLE_CANVAS_URL);
+  let { target, front } = yield initCallWatcherBackend(SIMPLE_CANVAS_URL);
 
   ok(target, "Should have a target available.");
-  ok(debuggee, "Should have a debuggee available.");
   ok(front, "Should have a protocol front available.");
 
   yield removeTab(target.tab);
   finish();
 }
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-02.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-02.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if functions calls are recorded and stored for a canvas context,
  * and that their stack is successfully retrieved.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, front] = yield initCallWatcherBackend(SIMPLE_CANVAS_URL);
+  let { target, front } = yield initCallWatcherBackend(SIMPLE_CANVAS_URL);
 
   let navigated = once(target, "navigate");
 
   yield front.setup({
     tracedGlobals: ["CanvasRenderingContext2D", "WebGLRenderingContext"],
     startRecording: true,
     performReload: true,
     storeCalls: true
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-03.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-03.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if functions inside a single animation frame are recorded and stored
  * for a canvas context.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, front] = yield initCanavsDebuggerBackend(SIMPLE_CANVAS_URL);
+  let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
 
   let navigated = once(target, "navigate");
 
   yield front.setup({ reload: true });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-04.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-04.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if draw calls inside a single animation frame generate and retrieve
  * the correct thumbnails.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, front] = yield initCanavsDebuggerBackend(SIMPLE_CANVAS_URL);
+  let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
 
   let navigated = once(target, "navigate");
 
   yield front.setup({ reload: true });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-05.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-05.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if draw calls inside a single animation frame generate and retrieve
  * the correct "end result" screenshot.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, front] = yield initCanavsDebuggerBackend(SIMPLE_CANVAS_URL);
+  let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
 
   let navigated = once(target, "navigate");
 
   yield front.setup({ reload: true });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-06.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-06.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if screenshots for arbitrary draw calls are generated properly.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, front] = yield initCanavsDebuggerBackend(SIMPLE_CANVAS_TRANSPARENT_URL);
+  let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_TRANSPARENT_URL);
 
   let navigated = once(target, "navigate");
 
   yield front.setup({ reload: true });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-07.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-07.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if screenshots for non-draw calls can still be retrieved properly,
  * by deferring the the most recent previous draw-call.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, front] = yield initCanavsDebuggerBackend(SIMPLE_CANVAS_URL);
+  let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
 
   let navigated = once(target, "navigate");
 
   yield front.setup({ reload: true });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-08.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-08.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that integers used in arguments are not cast to their constant, enum value
  * forms if the method's signature does not expect an enum. Bug 999687.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, front] = yield initCanavsDebuggerBackend(SIMPLE_BITMASKS_URL);
+  let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_BITMASKS_URL);
 
   let navigated = once(target, "navigate");
 
   yield front.setup({ reload: true });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-09.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-09.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that integers used in arguments are not cast to their constant, enum value
  * forms if the method's signature does not expect an enum. Bug 999687.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, front] = yield initCanavsDebuggerBackend(WEBGL_ENUM_URL);
+  let { target, front } = yield initCanvasDebuggerBackend(WEBGL_ENUM_URL);
 
   let navigated = once(target, "navigate");
 
   yield front.setup({ reload: true });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-10.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-10.js
@@ -2,17 +2,19 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the correct framebuffer, renderbuffer and textures are re-bound
  * after generating screenshots using the actor.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, front] = yield initCanavsDebuggerBackend(WEBGL_BINDINGS_URL);
+  let { target, front } = yield initCanvasDebuggerBackend(WEBGL_BINDINGS_URL);
+  // XXX - use of |debuggee| here is incompatible with e10s - bug 1058879.
+  let debuggee = target.window.wrappedJSObject
 
   let navigated = once(target, "navigate");
 
   yield front.setup({ reload: true });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-highlight.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-highlight.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if certain function calls are properly highlighted in the UI.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   SnapshotsListView._onRecordButtonClick();
   yield promise.all([recordingFinished, callListPopulated]);
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-list.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-list.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if all the function calls associated with an animation frame snapshot
  * are properly displayed in the UI.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   SnapshotsListView._onRecordButtonClick();
   yield promise.all([recordingFinished, callListPopulated]);
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-search.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-search.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if filtering the items in the call list works properly.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
   let searchbox = $("#calls-searchbox");
 
   yield reload(target);
 
   let firstRecordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   SnapshotsListView._onRecordButtonClick();
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-01.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-01.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if the a function call's stack is properly displayed in the UI.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
   let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   SnapshotsListView._onRecordButtonClick();
   yield promise.all([recordingFinished, callListPopulated]);
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-02.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-02.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if the a function call's stack is properly displayed in the UI
  * and jumping to source in the debugger for the topmost call item works.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
   let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   SnapshotsListView._onRecordButtonClick();
   yield promise.all([recordingFinished, callListPopulated]);
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-03.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-03.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if the a function call's stack can be shown/hidden by double-clicking
  * on a function call item.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
   let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   SnapshotsListView._onRecordButtonClick();
   yield promise.all([recordingFinished, callListPopulated]);
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-clear.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-clear.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if clearing the snapshots list works as expected.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, EVENTS, SnapshotsListView } = panel.panelWin;
 
   yield reload(target);
 
   let firstRecordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   SnapshotsListView._onRecordButtonClick();
 
   yield firstRecordingFinished;
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-img-screenshots.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-img-screenshots.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if screenshots are properly displayed in the UI.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, $, EVENTS, SnapshotsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   let screenshotDisplayed = once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
   SnapshotsListView._onRecordButtonClick();
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-img-thumbnails-01.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-img-thumbnails-01.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if thumbnails are properly displayed in the UI.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, $, $all, EVENTS, SnapshotsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   let thumbnailsDisplayed = once(window, EVENTS.THUMBNAILS_DISPLAYED);
   SnapshotsListView._onRecordButtonClick();
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-img-thumbnails-02.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-img-thumbnails-02.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if thumbnails are correctly linked with other UI elements like
  * function call items and their respective screenshots.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   let thumbnailsDisplayed = once(window, EVENTS.THUMBNAILS_DISPLAYED);
   let screenshotDisplayed = once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-open.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-open.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the frontend UI is properly configured when opening the tool.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { $ } = panel.panelWin;
 
   is($("#snapshots-pane").hasAttribute("hidden"), false,
     "The snapshots pane should initially be visible.");
   is($("#debugging-pane").hasAttribute("hidden"), false,
     "The debugging pane should initially be visible.");
 
   is($("#record-snapshot").getAttribute("hidden"), "true",
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-record-01.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-record-01.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests whether the frontend behaves correctly while reording a snapshot.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
 
   yield reload(target);
 
   is($("#record-snapshot").hasAttribute("checked"), false,
     "The 'record snapshot' button should initially be unchecked.");
   is($("#record-snapshot").hasAttribute("disabled"), false,
     "The 'record snapshot' button should initially be enabled.");
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-record-02.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-record-02.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests whether the frontend displays a placeholder snapshot while recording.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, EVENTS, L10N, $, SnapshotsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingStarted = once(window, EVENTS.SNAPSHOT_RECORDING_STARTED);
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let recordingSelected = once(window, EVENTS.SNAPSHOT_RECORDING_SELECTED);
   SnapshotsListView._onRecordButtonClick();
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-record-03.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-record-03.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests whether the frontend displays the correct info for a snapshot
  * after finishing recording.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   SnapshotsListView._onRecordButtonClick();
 
   yield recordingFinished;
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-reload-01.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-reload-01.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the frontend UI is properly reconfigured after reloading.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, $, EVENTS } = panel.panelWin;
 
   let reset = once(window, EVENTS.UI_RESET);
   let navigated = reload(target);
 
   yield reset;
   ok(true, "The UI was reset after the refresh button was clicked.");
 
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-reload-02.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-reload-02.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the frontend UI is properly reconfigured after reloading.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
 
   is(SnapshotsListView.itemCount, 0,
     "There should be no snapshots initially displayed in the UI.");
   is(CallsListView.itemCount, 0,
     "There should be no function calls initially displayed in the UI.");
 
   is($("#screenshot-container").hidden, true,
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-slider-01.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-slider-01.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if the slider in the calls list view works as advertised.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   SnapshotsListView._onRecordButtonClick();
   yield promise.all([recordingFinished, callListPopulated]);
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-slider-02.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-slider-02.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if the slider in the calls list view works as advertised.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, $, EVENTS, gFront, SnapshotsListView, CallsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   let thumbnailsDisplayed = once(window, EVENTS.THUMBNAILS_DISPLAYED);
   SnapshotsListView._onRecordButtonClick();
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-snapshot-select.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-snapshot-select.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if selecting snapshots in the frontend displays the appropriate data
  * respective to their recorded animation frame.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
 
   yield reload(target);
 
   yield recordAndWaitForFirstSnapshot();
   info("First snapshot recorded.")
 
   is(SnapshotsListView.selectedIndex, 0,
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-stepping.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-stepping.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if the stepping buttons in the call list toolbar work as advertised.
  */
 
 function ifTestingSupported() {
-  let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
+  let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
   let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
 
   yield reload(target);
 
   let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   SnapshotsListView._onRecordButtonClick();
   yield promise.all([recordingFinished, callListPopulated]);
--- a/browser/devtools/canvasdebugger/test/head.js
+++ b/browser/devtools/canvasdebugger/test/head.js
@@ -180,55 +180,52 @@ function initServer() {
 
 function initCallWatcherBackend(aUrl) {
   info("Initializing a call watcher front.");
   initServer();
 
   return Task.spawn(function*() {
     let tab = yield addTab(aUrl);
     let target = TargetFactory.forTab(tab);
-    let debuggee = target.window.wrappedJSObject;
 
     yield target.makeRemote();
 
     let front = new CallWatcherFront(target.client, target.form);
-    return [target, debuggee, front];
+    return { target, front };
   });
 }
 
-function initCanavsDebuggerBackend(aUrl) {
+function initCanvasDebuggerBackend(aUrl) {
   info("Initializing a canvas debugger front.");
   initServer();
 
   return Task.spawn(function*() {
     let tab = yield addTab(aUrl);
     let target = TargetFactory.forTab(tab);
-    let debuggee = target.window.wrappedJSObject;
 
     yield target.makeRemote();
 
     let front = new CanvasFront(target.client, target.form);
-    return [target, debuggee, front];
+    return { target, front };
   });
 }
 
-function initCanavsDebuggerFrontend(aUrl) {
+function initCanvasDebuggerFrontend(aUrl) {
   info("Initializing a canvas debugger pane.");
 
   return Task.spawn(function*() {
     let tab = yield addTab(aUrl);
     let target = TargetFactory.forTab(tab);
-    let debuggee = target.window.wrappedJSObject;
 
     yield target.makeRemote();
 
     Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", true);
     let toolbox = yield gDevTools.showToolbox(target, "canvasdebugger");
     let panel = toolbox.getCurrentPanel();
-    return [target, debuggee, panel];
+    return { target, panel };
   });
 }
 
 function teardown(aPanel) {
   info("Destroying the specified canvas debugger.");
 
   return promise.all([
     once(aPanel, "destroyed"),
--- a/browser/devtools/debugger/test/browser_dbg_watch-expressions-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_watch-expressions-02.js
@@ -100,17 +100,17 @@ function test() {
       "There should be 0 hidden nodes in the watch expressions container");
     is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 27,
       "There should be 27 visible nodes in the watch expressions container");
   }
 
   function test1(aCallback) {
     gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => {
       checkWatchExpressions(26, {
-        a: "ReferenceError: a is not defined, did you mean 'z'?",
+        a: "ReferenceError: a is not defined",
         this: { type: "object", class: "Object" },
         prop: { type: "object", class: "String" },
         args: { type: "undefined" }
       });
       aCallback();
     });
 
     gDebuggee.test();
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -27,16 +27,17 @@ skip-if = e10s # Bug 1070837 - devtools/
 # [browser_toolbox_raise.js] # Bug 962258
 # skip-if = os == "win"
 [browser_toolbox_ready.js]
 [browser_toolbox_select_event.js]
 skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
 [browser_toolbox_sidebar.js]
 [browser_toolbox_tabsswitch_shortcuts.js]
 [browser_toolbox_tool_ready.js]
+[browser_toolbox_tool_remote_reopen.js]
 [browser_toolbox_window_reload_target.js]
 [browser_toolbox_window_shortcuts.js]
 [browser_toolbox_window_title_changes.js]
 [browser_toolbox_zoom.js]
 [browser_toolbox_custom_host.js]
 [browser_toolbox_theme_registration.js]
 
 # We want this test to run for mochitest-dt as well, so we include it here:
--- a/browser/devtools/framework/test/browser_toolbox_tool_ready.js
+++ b/browser/devtools/framework/test/browser_toolbox_tool_ready.js
@@ -1,41 +1,38 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function test() {
-  addTab("about:blank").then(function(tab) {
-    let target = TargetFactory.forTab(tab);
-    target.makeRemote().then(performChecks.bind(null, target));
-  }).then(null, console.error);
+function performChecks(target) {
+  return Task.spawn(function() {
+    let toolIds = gDevTools.getToolDefinitionArray()
+                           .filter(def => def.isTargetSupported(target))
+                           .map(def => def.id);
 
-  function performChecks(target) {
-    let toolIds = gDevTools.getToolDefinitionArray()
-                    .filter(def => def.isTargetSupported(target))
-                    .map(def => def.id);
-
-    let open = function(index) {
+    let toolbox;
+    for (let index = 0; index < toolIds.length; index++) {
       let toolId = toolIds[index];
 
       info("About to open " + index + "/" + toolId);
-      gDevTools.showToolbox(target, toolId).then(function(toolbox) {
-        ok(toolbox, "toolbox exists for " + toolId);
-        is(toolbox.currentToolId, toolId, "currentToolId should be " + toolId);
+      toolbox = yield gDevTools.showToolbox(target, toolId);
+      ok(toolbox, "toolbox exists for " + toolId);
+      is(toolbox.currentToolId, toolId, "currentToolId should be " + toolId);
 
-        let panel = toolbox.getCurrentPanel();
-        ok(panel.isReady, toolId + " panel should be ready");
+      let panel = toolbox.getCurrentPanel();
+      ok(panel.isReady, toolId + " panel should be ready");
+    }
+
+    yield toolbox.destroy();
+  });
+}
 
-        let nextIndex = index + 1;
-        if (nextIndex >= toolIds.length) {
-          toolbox.destroy().then(function() {
-            gBrowser.removeCurrentTab();
-            finish();
-          });
-        }
-        else {
-          open(nextIndex);
-        }
-      }, console.error);
-    };
-
-    open(0);
-  }
+function test() {
+  Task.spawn(function() {
+    toggleAllTools(true);
+    let tab = yield addTab("about:blank");
+    let target = TargetFactory.forTab(tab);
+    yield target.makeRemote();
+    yield performChecks(target);
+    gBrowser.removeCurrentTab();
+    toggleAllTools(false);
+    finish();
+  }, console.error);
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_toolbox_tool_remote_reopen.js
@@ -0,0 +1,123 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { DebuggerServer } =
+  Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+const { DebuggerClient } =
+  Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
+
+/**
+ * Bug 979536: Ensure fronts are destroyed after toolbox close.
+ *
+ * The fronts need to be destroyed manually to unbind their onPacket handlers.
+ *
+ * When you initialize a front and call |this.manage|, it adds a client actor
+ * pool that the DebuggerClient uses to route packet replies to that actor.
+ *
+ * Most (all?) tools create a new front when they are opened.  When the destroy
+ * step is skipped and the tool is reopened, a second front is created and also
+ * added to the client actor pool.  When a packet reply is received, is ends up
+ * being routed to the first (now unwanted) front that is still in the client
+ * actor pool.  Since this is not the same front that was used to make the
+ * request, an error occurs.
+ *
+ * This problem does not occur with the toolbox for a local tab because the
+ * toolbox target creates its own DebuggerClient for the local tab, and the
+ * client is destroyed when the toolbox is closed, which removes the client
+ * actor pools, and avoids this issue.
+ *
+ * In WebIDE, we do not destroy the DebuggerClient on toolbox close because it
+ * is still used for other purposes like managing apps, etc. that aren't part of
+ * a toolbox.  Thus, the same client gets reused across multiple toolboxes,
+ * which leads to the tools failing if they don't destroy their fronts.
+ */
+
+function runTools(target) {
+  return Task.spawn(function() {
+    let toolIds = gDevTools.getToolDefinitionArray()
+                           .filter(def => def.isTargetSupported(target))
+                           .map(def => def.id);
+
+    let toolbox;
+    for (let index = 0; index < toolIds.length; index++) {
+      let toolId = toolIds[index];
+
+      info("About to open " + index + "/" + toolId);
+      toolbox = yield gDevTools.showToolbox(target, toolId, "window");
+      ok(toolbox, "toolbox exists for " + toolId);
+      is(toolbox.currentToolId, toolId, "currentToolId should be " + toolId);
+
+      let panel = toolbox.getCurrentPanel();
+      ok(panel.isReady, toolId + " panel should be ready");
+    }
+
+    yield toolbox.destroy();
+  });
+}
+
+function getClient() {
+  let deferred = promise.defer();
+
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init(() => true);
+    DebuggerServer.addBrowserActors();
+  }
+
+  let transport = DebuggerServer.connectPipe();
+  let client = new DebuggerClient(transport);
+
+  client.connect(() => {
+    deferred.resolve(client);
+  });
+
+  return deferred.promise;
+}
+
+function getTarget(client) {
+  let deferred = promise.defer();
+
+  let tabList = client.listTabs(tabList => {
+    let target = TargetFactory.forRemoteTab({
+      client: client,
+      form: tabList.tabs[tabList.selected],
+      chrome: false
+    });
+    deferred.resolve(target);
+  });
+
+  return deferred.promise;
+}
+
+function test() {
+  Task.spawn(function() {
+    toggleAllTools(true);
+    yield addTab("about:blank");
+
+    let client = yield getClient();
+    let target = yield getTarget(client);
+    yield runTools(target);
+
+    // Actor fronts should be destroyed now that the toolbox has closed, but
+    // look for any that remain.
+    for (let pool of client.__pools) {
+      if (!pool.__poolMap) {
+        continue;
+      }
+      for (let actor of pool.__poolMap.keys()) {
+        // Bug 1056342: Profiler fails today because of framerate actor, but
+        // this appears more complex to rework, so leave it for that bug to
+        // resolve.
+        if (actor.contains("framerateActor")) {
+          todo(false, "Front for " + actor + " still held in pool!");
+          continue;
+        }
+        ok(false, "Front for " + actor + " still held in pool!");
+      }
+    }
+
+    gBrowser.removeCurrentTab();
+    DebuggerServer.destroy();
+    toggleAllTools(false);
+    finish();
+  }, console.error);
+}
--- a/browser/devtools/framework/test/head.js
+++ b/browser/devtools/framework/test/head.js
@@ -122,8 +122,21 @@ function once(target, eventName, useCapt
   return deferred.promise;
 }
 
 function waitForTick() {
   let deferred = promise.defer();
   executeSoon(deferred.resolve);
   return deferred.promise;
 }
+
+function toggleAllTools(state) {
+  for (let [, tool] of gDevTools._tools) {
+    if (!tool.visibilityswitch) {
+      continue;
+    }
+    if (state) {
+      Services.prefs.setBoolPref(tool.visibilityswitch, true);
+    } else {
+      Services.prefs.clearUserPref(tool.visibilityswitch);
+    }
+  }
+}
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -1537,16 +1537,19 @@ Toolbox.prototype = {
     outstanding.push(this.destroyInspector().then(() => {
       // Removing buttons
       if (this._pickerButton) {
         this._pickerButton.removeEventListener("command", this._togglePicker, false);
         this._pickerButton = null;
       }
     }));
 
+    // We need to grab a reference to win before this._host is destroyed.
+    let win = this.frame.ownerGlobal;
+
     // Remove the host UI
     outstanding.push(this.destroyHost());
 
     if (this._requisition) {
       this._requisition.destroy();
     }
     this._telemetry.toolClosed("toolbox");
     this._telemetry.destroy();
@@ -1564,19 +1567,16 @@ Toolbox.prototype = {
       let target = this._target;
       this._target = null;
       this.highlighterUtils.release();
       target.off("close", this.destroy);
       return target.destroy();
     }, console.error).then(() => {
       this.emit("destroyed");
 
-      // We need to grab a reference to win before this._host is destroyed.
-      let win = this.frame.ownerGlobal;
-
       // Free _host after the call to destroyed in order to let a chance
       // to destroyed listeners to still query toolbox attributes
       this._host = null;
       this._toolPanels.clear();
 
       // Force GC to prevent long GC pauses when running tests and to free up
       // memory in general when the toolbox is closed.
       if (gDevTools.testing) {
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -31,16 +31,17 @@ const MINIMUM_FONT_SIZE = 6;
 const NORMAL_FONT_SIZE = 12;
 
 const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
 const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
 const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
 const SHOW_TRAILING_SPACE = "devtools.scratchpad.showTrailingSpace";
 const ENABLE_AUTOCOMPLETION = "devtools.scratchpad.enableAutocompletion";
 const TAB_SIZE = "devtools.editor.tabsize";
+const FALLBACK_CHARSET_LIST = "intl.fallbackCharsetList.ISO-8859-1";
 
 const VARIABLES_VIEW_URL = "chrome://browser/content/devtools/widgets/VariablesView.xul";
 
 const require   = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 
 const Telemetry = require("devtools/shared/telemetry");
 const Editor    = require("devtools/sourceeditor/editor");
 const TargetFactory = require("devtools/framework/target").TargetFactory;
@@ -1048,16 +1049,60 @@ var Scratchpad = {
       if (aCallback) {
         aCallback.call(this, Components.results.NS_ERROR_UNEXPECTED);
       }
     });
 
   },
 
   /**
+   * Get a list of applicable charsets.
+   * The best charset, defaulting to "UTF-8"
+   *
+   * @param string aBestCharset
+   * @return array of strings
+   */
+  _getApplicableCharsets: function SP__getApplicableCharsets(aBestCharset="UTF-8") {
+    let charsets = Services.prefs.getCharPref(
+      FALLBACK_CHARSET_LIST).split(",").filter(function (value) {
+      return value.length;
+    });
+    charsets.unshift(aBestCharset);
+    return charsets;
+  },
+
+  /**
+   * Get content converted to unicode, using a list of input charset to try.
+   *
+   * @param string aContent
+   * @param array of string aCharsetArray
+   * @return string
+   */
+  _getUnicodeContent: function SP__getUnicodeContent(aContent, aCharsetArray) {
+    let content = null,
+        converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter),
+        success = aCharsetArray.some(charset => {
+          try {
+            converter.charset = charset;
+            content = converter.ConvertToUnicode(aContent);
+            return true;
+          } catch (e) {
+            this.notificationBox.appendNotification(
+              this.strings.formatStringFromName("importFromFile.convert.failed",
+                                                [ charset ], 1),
+              "file-import-convert-failed",
+              null,
+              this.notificationBox.PRIORITY_WARNING_HIGH,
+              null);
+          }
+        });
+    return content;
+  },
+
+  /**
    * Read the content of a file and put it into the textbox.
    *
    * @param nsILocalFile aFile
    *        The file you want to save the textbox content into.
    * @param boolean aSilentError
    *        True if you do not want to display an error when file load fails,
    *        false otherwise.
    * @param function aCallback
@@ -1067,27 +1112,42 @@ var Scratchpad = {
    *        2) the data that was read from the file, if any.
    */
   importFromFile: function SP_importFromFile(aFile, aSilentError, aCallback)
   {
     // Prevent file type detection.
     let channel = NetUtil.newChannel(aFile);
     channel.contentType = "application/javascript";
 
+    this.notificationBox.removeAllNotifications(false);
+
     NetUtil.asyncFetch(channel, (aInputStream, aStatus) => {
       let content = null;
 
       if (Components.isSuccessCode(aStatus)) {
-        let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
-                        createInstance(Ci.nsIScriptableUnicodeConverter);
-        converter.charset = "UTF-8";
+        let charsets = this._getApplicableCharsets();
         content = NetUtil.readInputStreamToString(aInputStream,
                                                   aInputStream.available());
-        content = converter.ConvertToUnicode(content);
-
+        content = this._getUnicodeContent(content, charsets);
+        if (!content) {
+          let message = this.strings.formatStringFromName(
+            "importFromFile.convert.failed",
+            [ charsets.join(", ") ],
+            1);
+          this.notificationBox.appendNotification(
+            message,
+            "file-import-convert-failed",
+            null,
+            this.notificationBox.PRIORITY_CRITICAL_MEDIUM,
+            null);
+          if (aCallback) {
+            aCallback.call(this, aStatus, content);
+          }
+          return;
+        }
         // Check to see if the first line is a mode-line comment.
         let line = content.split("\n")[0];
         let modeline = this._scanModeLine(line);
         let chrome = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED);
 
         if (chrome && modeline["-sp-context"] === "browser") {
           this.setBrowserContext();
         }
@@ -1095,17 +1155,18 @@ var Scratchpad = {
         this.editor.setText(content);
         this.editor.clearHistory();
         this.dirty = false;
         document.getElementById("sp-cmd-revert").setAttribute("disabled", true);
       }
       else if (!aSilentError) {
         window.alert(this.strings.GetStringFromName("openFile.failed"));
       }
-
+      this.setFilename(aFile.path);
+      this.setRecentFile(aFile);
       if (aCallback) {
         aCallback.call(this, aStatus, content);
       }
     });
   },
 
   /**
    * Open a file to edit in the Scratchpad.
@@ -1140,19 +1201,17 @@ var Scratchpad = {
               null,
               this.notificationBox.PRIORITY_WARNING_HIGH,
               null);
 
             this.clearFiles(aIndex, 1);
             return;
           }
 
-          this.setFilename(file.path);
           this.importFromFile(file, false);
-          this.setRecentFile(file);
         }
       });
     };
 
     if (aIndex > -1) {
       promptCallback();
     } else {
       let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/NS_ERROR_ILLEGAL_INPUT.txt
@@ -0,0 +1,2 @@
+Typ	Datum	Uhrzeit	Quelle	Kategorie	Ereignis	Benutzer	Computer
+Informationen	10.08.2012	16:07:11	MSDTC	Datentrger 	2444	Nicht zutreffend	
--- a/browser/devtools/scratchpad/test/browser.ini
+++ b/browser/devtools/scratchpad/test/browser.ini
@@ -28,16 +28,18 @@ skip-if = buildapp == 'mulet'
 [browser_scratchpad_contexts.js]
 [browser_scratchpad_execute_print.js]
 [browser_scratchpad_files.js]
 [browser_scratchpad_initialization.js]
 [browser_scratchpad_inspect.js]
 [browser_scratchpad_inspect_primitives.js]
 [browser_scratchpad_long_string.js]
 [browser_scratchpad_open.js]
+# test file:
+[NS_ERROR_ILLEGAL_INPUT.txt]
 [browser_scratchpad_open_error_console.js]
 [browser_scratchpad_throw_output.js]
 [browser_scratchpad_pprint-02.js]
 [browser_scratchpad_pprint.js]
 [browser_scratchpad_pprint_error_goto_line.js]
 [browser_scratchpad_restore.js]
 [browser_scratchpad_tab_switch.js]
 [browser_scratchpad_ui.js]
--- a/browser/devtools/scratchpad/test/browser_scratchpad_open.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_open.js
@@ -1,30 +1,31 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // only finish() when correct number of tests are done
-const expected = 3;
+const expected = 4;
 var count = 0;
 var lastUniqueName = null;
 
 function done()
 {
   if (++count == expected) {
     finish();
   }
 }
 
 function test()
 {
   waitForExplicitFinish();
   testOpen();
   testOpenWithState();
   testOpenInvalidState();
+  testOpenTestFile();
 }
 
 function testUniqueName(name)
 {
   ok(name, "Scratchpad has a uniqueName");
 
   if (lastUniqueName === null) {
     lastUniqueName = name;
@@ -69,8 +70,32 @@ function testOpenWithState()
 }
 
 function testOpenInvalidState()
 {
   let win = openScratchpad(null, {state: 7});
   ok(!win, "no scratchpad opened if state is not an object");
   done();
 }
+
+function testOpenTestFile()
+{
+  let win = openScratchpad(function(win) {
+    ok(win, "scratchpad opened for file open");
+    try {
+      win.Scratchpad.importFromFile(
+        "http://example.com/browser/browser/devtools/scratchpad/test/NS_ERROR_ILLEGAL_INPUT.txt",
+        "silent",
+        function (aStatus, content) {
+          let nb = win.document.querySelector('#scratchpad-notificationbox');
+          is(nb.querySelectorAll('notification').length, 1, "There is just one notification");
+          let cn = nb.currentNotification;
+          is(cn.priority, nb.PRIORITY_WARNING_HIGH, "notification priority is correct");
+          is(cn.value, "file-import-convert-failed", "notification value is corrent");
+          is(cn.type, "warning", "notification type is correct");
+          done();
+        });
+      ok(true, "importFromFile does not cause exception");
+    } catch (exception) {
+      ok(false, "importFromFile causes exception " + DevToolsUtils.safeErrorString(exception));
+    }
+  }, {noFocus: true});
+}
--- a/browser/devtools/shadereditor/panel.js
+++ b/browser/devtools/shadereditor/panel.js
@@ -61,12 +61,14 @@ ShaderEditorPanel.prototype = {
 
   destroy: function() {
     // Make sure this panel is not already destroyed.
     if (this._destroyer) {
       return this._destroyer;
     }
 
     return this._destroyer = this.panelWin.shutdownShaderEditor().then(() => {
+      // Destroy front to ensure packet handler is removed from client
+      this.panelWin.gFront.destroy();
       this.emit("destroyed");
     });
   }
 };
--- a/browser/devtools/storage/panel.js
+++ b/browser/devtools/storage/panel.js
@@ -59,16 +59,18 @@ StoragePanel.prototype = {
   },
 
   /**
    * Destroy the style editor.
    */
   destroy: function() {
     if (!this._destroyed) {
       this.UI.destroy();
+      // Destroy front to ensure packet handler is removed from client
+      this._front.destroy();
       this._destroyed = true;
 
       this._target.off("close", this.destroy);
       this._target = null;
       this._toolbox = null;
       this._panelDoc = null;
     }
 
--- a/browser/devtools/storage/ui.js
+++ b/browser/devtools/storage/ui.js
@@ -91,17 +91,17 @@ exports.StorageUI = StorageUI;
 
 StorageUI.prototype = {
 
   storageTypes: null,
   shouldResetColumns: true,
 
   destroy: function() {
     this.front.off("stores-update", this.onUpdate);
-    this._panelDoc.removeEventListener("keypress", this.handleKeypress)
+    this._panelDoc.removeEventListener("keypress", this.handleKeypress);
   },
 
   /**
    * Empties and hides the object viewer sidebar
    */
   hideSidebar: function() {
     this.view.empty();
     this.sidebar.hidden = true;
--- a/browser/devtools/timeline/panel.js
+++ b/browser/devtools/timeline/panel.js
@@ -52,12 +52,14 @@ TimelinePanel.prototype = {
 
   destroy: Task.async(function*() {
     // Make sure this panel is not already destroyed.
     if (this._destroyed) {
       return;
     }
 
     yield this.panelWin.shutdownTimeline();
+    // Destroy front to ensure packet handler is removed from client
+    this.panelWin.gFront.destroy();
     this.emit("destroyed");
     this._destroyed = true;
   })
 };
--- a/browser/devtools/webaudioeditor/panel.js
+++ b/browser/devtools/webaudioeditor/panel.js
@@ -56,12 +56,14 @@ WebAudioEditorPanel.prototype = {
 
   destroy: function() {
     // Make sure this panel is not already destroyed.
     if (this._destroyer) {
       return this._destroyer;
     }
 
     return this._destroyer = this.panelWin.shutdownWebAudioEditor().then(() => {
+      // Destroy front to ensure packet handler is removed from client
+      this.panelWin.gFront.destroy();
       this.emit("destroyed");
     });
   }
 };
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-bypass.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-bypass.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test AudioNode#bypass(), AudioNode#isBypassed()
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_CONTEXT_URL);
+  let { target, front } = yield initBackend(SIMPLE_CONTEXT_URL);
   let [_, [destNode, oscNode, gainNode]] = yield Promise.all([
     front.setup({ reload: true }),
     get3(front, "create-node")
   ]);
 
   is((yield gainNode.isBypassed()), false, "Nodes start off unbypassed.");
 
   info("Calling node#bypass(true)");
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-param-flags.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-param-flags.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test AudioNode#getParamFlags()
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
+  let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 14)
   ]);
 
   let allNodeParams = yield Promise.all(nodes.map(node => node.getParams()));
   let nodeTypes = [
     "AudioDestinationNode",
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-params-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-params-01.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test AudioNode#getParams()
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
+  let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 14)
   ]);
 
   let allNodeParams = yield Promise.all(nodes.map(node => node.getParams()));
   let nodeTypes = [
     "AudioDestinationNode",
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-params-02.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-params-02.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that default properties are returned with the correct type
  * from the AudioNode actors.
  */
 
 function spawnTest() {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
+  let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 14)
   ]);
 
   let allParams = yield Promise.all(nodes.map(node => node.getParams()));
   let types = [
     "AudioDestinationNode", "AudioBufferSourceNode", "ScriptProcessorNode",
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-set-param.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-set-param.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test AudioNode#getParam() / AudioNode#setParam()
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_CONTEXT_URL);
+  let { target, front } = yield initBackend(SIMPLE_CONTEXT_URL);
   let [_, [destNode, oscNode, gainNode]] = yield Promise.all([
     front.setup({ reload: true }),
     get3(front, "create-node")
   ]);
 
   let freq = yield oscNode.getParam("frequency");
   info(typeof freq);
   ise(freq, 440, "AudioNode:getParam correctly fetches AudioParam");
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-type.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-type.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test AudioNode#getType()
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
+  let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 14)
   ]);
 
   let actualTypes = yield Promise.all(nodes.map(node => node.getType()));
   let expectedTypes = [
     "AudioDestinationNode",
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-is-source.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-is-source.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test AudioNode#isSource()
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
+  let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 14)
   ]);
 
   let actualTypes = yield Promise.all(nodes.map(node => node.getType()));
   let isSourceResult = yield Promise.all(nodes.map(node => node.isSource()));
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_destroy-node-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_destroy-node-01.js
@@ -5,17 +5,17 @@
  * Tests that the destruction node event is fired and that the nodes are no
  * longer stored internally in the tool, that the graph is updated properly, and
  * that selecting a soon-to-be dead node clears the inspector.
  *
  * All done in one test since this test takes a few seconds to clear GC.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(DESTROY_NODES_URL);
+  let { target, panel } = yield initWebAudioEditor(DESTROY_NODES_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, gAudioNodes } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   let destroyed = getN(gAudioNodes, "remove", 10);
--- a/browser/devtools/webaudioeditor/test/browser_wa_first-run.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_first-run.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the reloading/onContentLoaded hooks work.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { gFront, $ } = panel.panelWin;
 
   is($("#reload-notice").hidden, false,
     "The 'reload this page' notice should initially be visible.");
   is($("#waiting-notice").hidden, true,
     "The 'waiting for an audio context' notice should initially be hidden.");
   is($("#content").hidden, true,
     "The tool's content should initially be hidden.");
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-click.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-click.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the clicking on a node in the GraphView opens and sets
  * the correct node in the InspectorView
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
   let panelWin = panel.panelWin;
   let { gFront, $, $$, InspectorView } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   let [actors, _] = yield Promise.all([
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-markers.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-markers.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the SVG marker styling is updated when devtools theme changes.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, MARKER_STYLING } = panelWin;
 
   let currentTheme = Services.prefs.getCharPref("devtools.theme");
 
   ok(MARKER_STYLING.light, "Marker styling exists for light theme.");
   ok(MARKER_STYLING.dark, "Marker styling exists for dark theme.");
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-render-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-render-01.js
@@ -3,17 +3,17 @@
 
 /**
  * Tests that SVG nodes and edges were created for the Graph View.
  */
 
 let connectCount = 0;
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   gAudioNodes.on("connect", onConnectNode);
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-render-02.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-render-02.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests more edge rendering for complex graphs.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$ } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   let [actors] = yield Promise.all([
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-render-03.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-render-03.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests to ensure that selected nodes stay selected on graph redraw.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(CONNECT_TOGGLE_URL);
+  let { target, panel } = yield initWebAudioEditor(CONNECT_TOGGLE_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
   reload(target);
 
   let [actors] = yield Promise.all([
     getN(gFront, "create-node", 3),
     waitForGraphRendered(panelWin, 3, 2)
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-render-04.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-render-04.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests audio param connection rendering.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(CONNECT_MULTI_PARAM_URL);
+  let { target, panel } = yield initWebAudioEditor(CONNECT_MULTI_PARAM_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   let [actors] = yield Promise.all([
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-render-05.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-render-05.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests to ensure that param connections trigger graph redraws
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(CONNECT_TOGGLE_PARAM_URL);
+  let { target, panel } = yield initWebAudioEditor(CONNECT_TOGGLE_PARAM_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
   reload(target);
 
   let [actors] = yield Promise.all([
     getN(gFront, "create-node", 3),
     waitForGraphRendered(panelWin, 3, 1, 0)
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-selected.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-selected.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that SVG nodes and edges were created for the Graph View.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   let [actors] = yield Promise.all([
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-zoom.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-zoom.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the graph's scale and position is reset on a page reload.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, ContextView } = panelWin;
 
   let started = once(gFront, "start-context");
 
   yield Promise.all([
     reload(target),
     waitForGraphRendered(panelWin, 3, 2)
--- a/browser/devtools/webaudioeditor/test/browser_wa_inspector-toggle.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_inspector-toggle.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the inspector toggle button shows and hides
  * the inspector panel as intended.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_inspector.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_inspector.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that inspector view opens on graph node click, and
  * loads the correct node inside the inspector.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_navigate.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_navigate.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests naviating from a page to another will repopulate
  * the audio graph if both pages have an AudioContext.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $ } = panelWin;
 
   reload(target);
 
   var [actors] = yield Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view-edit-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-edit-01.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that properties are updated when modifying the VariablesView.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view-edit-02.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-edit-02.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that properties are not updated when modifying the VariablesView.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view-media-nodes.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-media-nodes.js
@@ -30,17 +30,17 @@ function waitForDeviceClosed() {
       deferred.resolve();
     }
   });
 
   return deferred.promise;
 }
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(MEDIA_NODES_URL);
+  let { target, panel } = yield initWebAudioEditor(MEDIA_NODES_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   // Auto enable getUserMedia
   let mediaPermissionPref = Services.prefs.getBoolPref(MEDIA_PERMISSION);
   Services.prefs.setBoolPref(MEDIA_PERMISSION, true);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view-params-objects.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-params-objects.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that params view correctly displays non-primitive properties
  * like AudioBuffer and Float32Array in properties of AudioNodes.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(BUFFER_AND_ARRAY_URL);
+  let { target, panel } = yield initWebAudioEditor(BUFFER_AND_ARRAY_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view-params.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-params.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that params view correctly displays all properties for nodes
  * correctly, with default values and correct types.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_NODES_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_NODES_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that params view shows params when they exist, and are hidden otherwise.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_reset-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_reset-01.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that reloading a tab will properly listen for the `start-context`
  * event and reshow the tools after reloading.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { gFront, $ } = panel.panelWin;
 
   is($("#reload-notice").hidden, false,
     "The 'reload this page' notice should initially be visible.");
   is($("#waiting-notice").hidden, true,
     "The 'waiting for an audio context' notice should initially be hidden.");
   is($("#content").hidden, true,
     "The tool's content should initially be hidden.");
--- a/browser/devtools/webaudioeditor/test/browser_wa_reset-02.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_reset-02.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests reloading a tab with the tools open properly cleans up
  * the graph.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $ } = panelWin;
 
   reload(target);
 
   let [actors] = yield Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
--- a/browser/devtools/webaudioeditor/test/browser_wa_reset-03.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_reset-03.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests reloading a tab with the tools open properly cleans up
  * the inspector and selected node.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, InspectorView } = panelWin;
 
   reload(target);
 
   let [actors] = yield Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
--- a/browser/devtools/webaudioeditor/test/browser_wa_reset-04.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_reset-04.js
@@ -3,17 +3,17 @@
 
 /**
  * Tests that switching to an iframe works fine.
  */
 
 function spawnTest() {
   Services.prefs.setBoolPref("devtools.command-button-frames.enabled", true);
 
-  let [target, debuggee, panel, toolbox] = yield initWebAudioEditor(IFRAME_CONTEXT_URL);
+  let { target, panel, toolbox } = yield initWebAudioEditor(IFRAME_CONTEXT_URL);
   let { gFront, $ } = panel.panelWin;
 
   is($("#reload-notice").hidden, false,
     "The 'reload this page' notice should initially be visible.");
   is($("#waiting-notice").hidden, true,
     "The 'waiting for an audio context' notice should initially be hidden.");
   is($("#content").hidden, true,
     "The tool's content should initially be hidden.");
--- a/browser/devtools/webaudioeditor/test/browser_webaudio-actor-connect-param.js
+++ b/browser/devtools/webaudioeditor/test/browser_webaudio-actor-connect-param.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test the `connect-param` event on the web audio actor.
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(CONNECT_PARAM_URL);
+  let { target, front } = yield initBackend(CONNECT_PARAM_URL);
   let [, , [destNode, carrierNode, modNode, gainNode], , connectParam] = yield Promise.all([
     front.setup({ reload: true }),
     once(front, "start-context"),
     getN(front, "create-node", 4),
     get2(front, "connect-node"),
     once(front, "connect-param")
   ]);
 
--- a/browser/devtools/webaudioeditor/test/browser_webaudio-actor-destroy-node.js
+++ b/browser/devtools/webaudioeditor/test/browser_webaudio-actor-destroy-node.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test `destroy-node` event on WebAudioActor.
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(DESTROY_NODES_URL);
+  let { target, front } = yield initBackend(DESTROY_NODES_URL);
 
   let waitUntilDestroyed = getN(front, "destroy-node", 10);
   let [, , created] = yield Promise.all([
     front.setup({ reload: true }),
     once(front, "start-context"),
     // Should create 1 destination node and 10 disposable buffer nodes
     getN(front, "create-node", 13)
   ]);
--- a/browser/devtools/webaudioeditor/test/browser_webaudio-actor-simple.js
+++ b/browser/devtools/webaudioeditor/test/browser_webaudio-actor-simple.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test basic communication of Web Audio actor
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_CONTEXT_URL);
+  let { target, front } = yield initBackend(SIMPLE_CONTEXT_URL);
   let [_, __, [destNode, oscNode, gainNode], [connect1, connect2]] = yield Promise.all([
     front.setup({ reload: true }),
     once(front, "start-context"),
     get3(front, "create-node"),
     get2(front, "connect-node")
   ]);
 
   let destType = yield destNode.getType();
--- a/browser/devtools/webaudioeditor/test/head.js
+++ b/browser/devtools/webaudioeditor/test/head.js
@@ -133,39 +133,37 @@ function initBackend(aUrl) {
   if (!DebuggerServer.initialized) {
     DebuggerServer.init(() => true);
     DebuggerServer.addBrowserActors();
   }
 
   return Task.spawn(function*() {
     let tab = yield addTab(aUrl);
     let target = TargetFactory.forTab(tab);
-    let debuggee = target.window.wrappedJSObject;
 
     yield target.makeRemote();
 
     let front = new WebAudioFront(target.client, target.form);
-    return [target, debuggee, front];
+    return { target, front };
   });
 }
 
 function initWebAudioEditor(aUrl) {
   info("Initializing a web audio editor pane.");
 
   return Task.spawn(function*() {
     let tab = yield addTab(aUrl);
     let target = TargetFactory.forTab(tab);
-    let debuggee = target.window.wrappedJSObject;
 
     yield target.makeRemote();
 
     Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true);
     let toolbox = yield gDevTools.showToolbox(target, "webaudioeditor");
     let panel = toolbox.getCurrentPanel();
-    return [target, debuggee, panel, toolbox];
+    return { target, panel, toolbox };
   });
 }
 
 function teardown(aPanel) {
   info("Destroying the web audio editor.");
 
   return Promise.all([
     once(aPanel, "destroyed"),
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -57,17 +57,17 @@ let UI = {
 
     this.updateCommands();
     this.updateRuntimeList();
 
     this.onfocus = this.onfocus.bind(this);
     window.addEventListener("focus", this.onfocus, true);
 
     AppProjects.load().then(() => {
-      this.openLastProject();
+      this.autoSelectProject();
     });
 
     // Auto install the ADB Addon Helper. Only once.
     // If the user decides to uninstall the addon, we won't install it again.
     let autoInstallADBHelper = Services.prefs.getBoolPref("devtools.webide.autoinstallADBHelper");
     if (autoInstallADBHelper && !Devices.helperAddonInstalled) {
       GetAvailableAddons().then(addons => {
         addons.adb.install();
@@ -75,31 +75,16 @@ let UI = {
     }
     Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
 
     this.lastConnectedRuntime = Services.prefs.getCharPref("devtools.webide.lastConnectedRuntime");
 
     this.setupDeck();
   },
 
-  openLastProject: function() {
-    let lastProjectLocation = Services.prefs.getCharPref("devtools.webide.lastprojectlocation");
-    let shouldRestore = Services.prefs.getBoolPref("devtools.webide.restoreLastProject");
-    if (lastProjectLocation && shouldRestore) {
-      let lastProject = AppProjects.get(lastProjectLocation);
-      if (lastProject) {
-        AppManager.selectedProject = lastProject;
-      } else {
-        AppManager.selectedProject = null;
-      }
-    } else {
-      AppManager.selectedProject = null;
-    }
-  },
-
   uninit: function() {
     window.removeEventListener("focus", this.onfocus, true);
     AppManager.off("app-manager-update", this.appManagerUpdate);
     AppManager.uninit();
     window.removeEventListener("message", this.onMessage);
   },
 
   canWindowClose: function() {
@@ -136,16 +121,17 @@ let UI = {
       case "project":
         this._updatePromise = Task.spawn(function() {
           UI.updateTitle();
           yield UI.destroyToolbox();
           UI.updateCommands();
           UI.updateProjectButton();
           UI.openProject();
           UI.autoStartProject();
+          UI.saveLastSelectedProject();
         });
         return;
       case "project-is-not-running":
       case "project-is-running":
       case "list-tabs-response":
         this.updateCommands();
         break;
       case "runtime":
@@ -155,16 +141,20 @@ let UI = {
       case "project-validated":
         this.updateTitle();
         this.updateCommands();
         this.updateProjectButton();
         this.updateProjectEditorHeader();
         break;
       case "install-progress":
         this.updateProgress(Math.round(100 * details.bytesSent / details.totalBytes));
+        break;
+      case "runtime-apps-found":
+        this.autoSelectProject();
+        break;
     };
     this._updatePromise = promise.resolve();
   },
 
   openInBrowser: function(url) {
     // Open a URL in a Firefox window
     let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
     if (browserWin) {
@@ -492,22 +482,16 @@ let UI = {
 
     // Nothing to show
 
     if (!project) {
       this.resetDeck();
       return;
     }
 
-    // Save last project location
-
-    if (project.location) {
-      Services.prefs.setCharPref("devtools.webide.lastprojectlocation", project.location);
-    }
-
     // Make sure the directory exist before we show Project Editor
 
     let forceDetailsOnly = false;
     if (project.type == "packaged") {
       forceDetailsOnly = !utils.doesFileExist(project.location);
     }
 
     // Show only the details screen
@@ -565,16 +549,99 @@ let UI = {
 
     // Validate project
     yield AppManager.validateProject(project);
 
     // Select project
     AppManager.selectedProject = project;
   }),
 
+  // Remember the last selected project on the runtime
+  saveLastSelectedProject: function() {
+    let shouldRestore = Services.prefs.getBoolPref("devtools.webide.restoreLastProject");
+    if (!shouldRestore) {
+      return;
+    }
+
+    // Ignore unselection of project on runtime disconnection
+    if (AppManager.connection.status != Connection.Status.CONNECTED) {
+      return;
+    }
+
+    let project = "", type = "";
+    let selected = AppManager.selectedProject;
+    if (selected) {
+      if (selected.type == "runtimeApp") {
+        type = "runtimeApp";
+        project = selected.app.manifestURL;
+      } else if (selected.type == "mainProcess") {
+        type = "mainProcess";
+      } else if (selected.type == "packaged" ||
+                 selected.type == "hosted") {
+        type = "local";
+        project = selected.location;
+      }
+    }
+    if (type) {
+      Services.prefs.setCharPref("devtools.webide.lastSelectedProject",
+                                 type + ":" + project);
+    } else {
+      Services.prefs.clearUserPref("devtools.webide.lastSelectedProject");
+    }
+  },
+
+  autoSelectProject: function() {
+    if (AppManager.selectedProject) {
+      return;
+    }
+    let shouldRestore = Services.prefs.getBoolPref("devtools.webide.restoreLastProject");
+    if (!shouldRestore) {
+      return;
+    }
+    let pref = Services.prefs.getCharPref("devtools.webide.lastSelectedProject");
+    if (!pref) {
+      return;
+    }
+    let m = pref.match(/^(\w+):(.*)$/);
+    if (!m) {
+      return;
+    }
+    let [_, type, project] = m;
+
+    if (type == "local") {
+      let lastProject = AppProjects.get(project);
+      if (lastProject) {
+        AppManager.selectedProject = lastProject;
+      }
+    }
+
+    // For other project types, we need to be connected to the runtime
+    if (AppManager.connection.status != Connection.Status.CONNECTED) {
+      return;
+    }
+
+    if (type == "mainProcess" && AppManager.isMainProcessDebuggable()) {
+      AppManager.selectedProject = {
+        type: "mainProcess",
+        name: Strings.GetStringFromName("mainProcess_label"),
+        icon: AppManager.DEFAULT_PROJECT_ICON
+      }
+    } else if (type == "runtimeApp") {
+      let app = AppManager.apps.get(project);
+      if (app) {
+        AppManager.selectedProject = {
+          type: "runtimeApp",
+          app: app.manifest,
+          icon: app.iconURL,
+          name: app.manifest.name
+        };
+      }
+    }
+  },
+
   /********** DECK **********/
 
   setupDeck: function() {
     let iframes = document.querySelectorAll("#deck > iframe");
     for (let iframe of iframes) {
       iframe.tooltip = "aHTMLTooltip";
     }
   },
@@ -882,19 +949,22 @@ let Cmds = {
         panelNode.addEventListener("popupshown", onPopupShown);
         panelNode.openPopup(anchorNode);
         panelVboxNode.scrollTop = 0;
       }, 0);
     }, deferred.reject);
 
 
     let runtimeappsHeaderNode = document.querySelector("#panel-header-runtimeapps");
-    let sortedApps = AppManager.webAppsStore.object.all;
+    let sortedApps = [];
+    for (let [manifestURL, app] of AppManager.apps) {
+      sortedApps.push(app);
+    }
     sortedApps = sortedApps.sort((a, b) => {
-      return a.name > b.name;
+      return a.manifest.name > b.manifest.name;
     });
     let mainProcess = AppManager.isMainProcessDebuggable();
     if (AppManager.connection.status == Connection.Status.CONNECTED &&
         (sortedApps.length > 0 || mainProcess)) {
       runtimeappsHeaderNode.removeAttribute("hidden");
     } else {
       runtimeappsHeaderNode.setAttribute("hidden", "true");
     }
@@ -919,26 +989,26 @@ let Cmds = {
         };
       }, true);
     }
 
     for (let i = 0; i < sortedApps.length; i++) {
       let app = sortedApps[i];
       let panelItemNode = document.createElement("toolbarbutton");
       panelItemNode.className = "panel-item";
-      panelItemNode.setAttribute("label", app.name);
+      panelItemNode.setAttribute("label", app.manifest.name);
       panelItemNode.setAttribute("image", app.iconURL);
       runtimeAppsNode.appendChild(panelItemNode);
       panelItemNode.addEventListener("click", () => {
         UI.hidePanels();
         AppManager.selectedProject = {
           type: "runtimeApp",
-          app: app,
+          app: app.manifest,
           icon: app.iconURL,
-          name: app.name
+          name: app.manifest.name
         };
       }, true);
     }
 
     // Build the tab list right now, so it's fast...
     this._buildProjectPanelTabs();
 
     // But re-list them and rebuild, in case any tabs navigated since the last
--- a/browser/devtools/webide/modules/app-manager.js
+++ b/browser/devtools/webide/modules/app-manager.js
@@ -9,21 +9,20 @@ let { Promise: promise } = Cu.import("re
 const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
 const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
 const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js");
 const {TextEncoder, OS}  = Cu.import("resource://gre/modules/osfile.jsm", {});
 const {AppProjects} = require("devtools/app-manager/app-projects");
-const WebappsStore = require("devtools/app-manager/webapps-store");
 const TabStore = require("devtools/webide/tab-store");
 const {AppValidator} = require("devtools/app-manager/app-validator");
 const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
-const AppActorFront = require("devtools/app-actor-front");
+const {AppActorFront} = require("devtools/app-actor-front");
 const {getDeviceFront} = require("devtools/server/actors/device");
 const {getPreferenceFront} = require("devtools/server/actors/preference");
 const {setTimeout} = require("sdk/timers");
 const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 const {USBRuntime, WiFiRuntime, SimulatorRuntime,
        gLocalRuntime, gRemoteRuntime} = require("devtools/webide/runtimes");
 const discovery = require("devtools/toolkit/discovery/discovery");
 const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
@@ -41,19 +40,16 @@ exports.AppManager = AppManager = {
   init: function() {
     let host = Services.prefs.getCharPref("devtools.debugger.remote-host");
     let port = Services.prefs.getIntPref("devtools.debugger.remote-port");
 
     this.connection = ConnectionManager.createConnection("localhost", port);
     this.onConnectionChanged = this.onConnectionChanged.bind(this);
     this.connection.on(Connection.Events.STATUS_CHANGED, this.onConnectionChanged);
 
-    this.onWebAppsStoreready = this.onWebAppsStoreready.bind(this);
-    this.webAppsStore = new WebappsStore(this.connection);
-    this.webAppsStore.on("store-ready", this.onWebAppsStoreready);
     this.tabStore = new TabStore(this.connection);
     this.onTabNavigate = this.onTabNavigate.bind(this);
     this.onTabClosed = this.onTabClosed.bind(this);
     this.tabStore.on("navigate", this.onTabNavigate);
     this.tabStore.on("closed", this.onTabClosed);
 
     this.runtimeList = {
       usb: [],
@@ -64,35 +60,28 @@ exports.AppManager = AppManager = {
     if (Services.prefs.getBoolPref("devtools.webide.enableLocalRuntime")) {
       this.runtimeList.custom.push(gLocalRuntime);
     }
     this.trackUSBRuntimes();
     this.trackWiFiRuntimes();
     this.trackSimulatorRuntimes();
 
     this.onInstallProgress = this.onInstallProgress.bind(this);
-    AppActorFront.on("install-progress", this.onInstallProgress);
 
     this.observe = this.observe.bind(this);
     Services.prefs.addObserver(WIFI_SCANNING_PREF, this, false);
   },
 
   uninit: function() {
-    AppActorFront.off("install-progress", this.onInstallProgress);
-    this._unlistenToApps();
     this.selectedProject = null;
     this.selectedRuntime = null;
     this.untrackUSBRuntimes();
     this.untrackWiFiRuntimes();
     this.untrackSimulatorRuntimes();
-    this._runningApps.clear();
     this.runtimeList = null;
-    this.webAppsStore.off("store-ready", this.onWebAppsStoreready);
-    this.webAppsStore.destroy();
-    this.webAppsStore = null;
     this.tabStore.off("navigate", this.onTabNavigate);
     this.tabStore.off("closed", this.onTabClosed);
     this.tabStore.destroy();
     this.tabStore = null;
     this.connection.off(Connection.Events.STATUS_CHANGED, this.onConnectionChanged);
     this._listTabsResponse = null;
     this.connection.disconnect();
     this.connection = null;
@@ -131,89 +120,62 @@ exports.AppManager = AppManager = {
 
   onConnectionChanged: function() {
     if (this.connection.status == Connection.Status.DISCONNECTED) {
       this.selectedRuntime = null;
     }
 
     if (this.connection.status != Connection.Status.CONNECTED) {
       console.log("Connection status changed: " + this.connection.status);
-      this._runningApps.clear();
-      this._unlistenToApps();
+      if (this._appsFront) {
+        this._appsFront.off("install-progress", this.onInstallProgress);
+        this._appsFront.unwatchApps();
+        this._appsFront = null;
+      }
       this._listTabsResponse = null;
     } else {
       this.connection.client.listTabs((response) => {
-        this._listenToApps();
+        let front = new AppActorFront(this.connection.client,
+                                      response);
+        front.on("install-progress", this.onInstallProgress);
+        front.watchApps(() => this.checkIfProjectIsRunning())
+             .then(() => front.fetchIcons())
+             .then(() => {
+               this._appsFront = front;
+               this.checkIfProjectIsRunning();
+               this.update("runtime-apps-found");
+             });
         this._listTabsResponse = response;
-        this._getRunningApps();
         this.update("list-tabs-response");
       });
     }
 
     this.update("connection");
   },
 
-  onInstallProgress: function(event, details) {
-    this.update("install-progress", details);
-  },
-
-  onWebAppsStoreready: function() {
-    this.update("runtime-apps-found");
+  get apps() {
+    if (this._appsFront) {
+      return this._appsFront.apps;
+    } else {
+      return new Map();
+    }
   },
 
-  _runningApps: new Set(),
-  _getRunningApps: function() {
-    let client = this.connection.client;
-    if (!this._listTabsResponse.webappsActor) {
-      return;
-    }
-    let request = {
-      to: this._listTabsResponse.webappsActor,
-      type: "listRunningApps"
-    };
-    client.request(request, (res) => {
-      if (res.error) {
-        this.reportError("error_listRunningApps");
-        console.error("listRunningApps error: " + res.error);
-      }
-      for (let m of res.apps) {
-        this._runningApps.add(m);
-      }
-    });
-    this.checkIfProjectIsRunning();
-  },
-  _listenToApps: function() {
-    let client = this.connection.client;
-    client.addListener("appOpen", (type, { manifestURL }) => {
-      this._runningApps.add(manifestURL);
-      this.checkIfProjectIsRunning();
-    });
-
-    client.addListener("appClose", (type, { manifestURL }) => {
-      this._runningApps.delete(manifestURL);
-      this.checkIfProjectIsRunning();
-    });
-
-    client.addListener("appUninstall", (type, { manifestURL }) => {
-      this._runningApps.delete(manifestURL);
-      this.checkIfProjectIsRunning();
-    });
-  },
-  _unlistenToApps: function() {
-    // Is that even possible?
-    // connection.client is null now.
+  onInstallProgress: function(event, details) {
+    this.update("install-progress", details);
   },
 
   isProjectRunning: function() {
     if (this.selectedProject.type == "mainProcess" ||
         this.selectedProject.type == "tab") {
       return true;
     }
-    let manifest = this.getProjectManifestURL(this.selectedProject);
-    return manifest && this._runningApps.has(manifest);
+
+    let app = this._getProjectFront(this.selectedProject);
+    return app && app.running;
   },
 
   checkIfProjectIsRunning: function() {
     if (this.selectedProject) {
       if (this.isProjectRunning()) {
         this.update("project-is-running");
       } else {
         this.update("project-is-not-running");
@@ -255,63 +217,56 @@ exports.AppManager = AppManager = {
   },
 
   reloadTab: function() {
     if (this.selectedProject && this.selectedProject.type != "tab") {
       return promise.reject("tried to reload non-tab project");
     }
     return this.getTarget().then(target => {
       target.activeTab.reload();
-    });
+    }, console.error.bind(console));
   },
 
   getTarget: function() {
-    let client = this.connection.client;
-
     if (this.selectedProject.type == "mainProcess") {
       return devtools.TargetFactory.forRemoteTab({
         form: this._listTabsResponse,
         client: this.connection.client,
         chrome: true
       });
     }
 
     if (this.selectedProject.type == "tab") {
       return this.tabStore.getTargetForTab();
     }
 
-    let manifest = this.getProjectManifestURL(this.selectedProject);
-    if (!manifest) {
-      console.error("Can't find manifestURL for selected project");
-      return promise.reject();
+    let app = this._getProjectFront(this.selectedProject);
+    if (!app) {
+      return promise.reject("Can't find app front for selected project");
     }
 
-    let actor = this._listTabsResponse.webappsActor;
     return Task.spawn(function* () {
       // Once we asked the app to launch, the app isn't necessary completely loaded.
       // launch request only ask the app to launch and immediatly returns.
       // We have to keep trying to get app tab actors required to create its target.
 
       for (let i = 0; i < 10; i++) {
         try {
-          let target = yield AppActorFront.getTargetForApp(client, actor, manifest);
-          // Success
-          return target;
+          return yield app.getTarget();
         } catch(e) {}
         let deferred = promise.defer();
         setTimeout(deferred.resolve, 500);
         yield deferred.promise;
       }
 
-      AppManager.reportError("error_cantConnectToApp", manifest);
+      AppManager.reportError("error_cantConnectToApp", app.manifest.manifestURL);
       throw new Error("can't connect to app");
     });
   },
 
-
   getProjectManifestURL: function(project) {
     let manifest = null;
     if (project.type == "runtimeApp") {
       manifest = project.app.manifestURL;
     }
 
     if (project.type == "hosted") {
       manifest = project.location;
@@ -319,16 +274,24 @@ exports.AppManager = AppManager = {
 
     if (project.type == "packaged" && project.packagedAppOrigin) {
       manifest = "app://" + project.packagedAppOrigin + "/manifest.webapp";
     }
 
     return manifest;
   },
 
+  _getProjectFront: function(project) {
+    let manifest = this.getProjectManifestURL(project);
+    if (manifest && this._appsFront) {
+      return this._appsFront.apps.get(manifest);
+    }
+    return null;
+  },
+
   _selectedProject: null,
   set selectedProject(value) {
     // A regular comparison still sees a difference when equal in some cases
     if (JSON.stringify(this._selectedProject) !==
         JSON.stringify(value)) {
       this._selectedProject = value;
 
       // Clear out tab store's selected state, if any
@@ -440,33 +403,29 @@ exports.AppManager = AppManager = {
     this.connection.disconnect();
     return deferred.promise;
   },
 
   launchRuntimeApp: function() {
     if (this.selectedProject && this.selectedProject.type != "runtimeApp") {
       return promise.reject("attempting to launch a non-runtime app");
     }
-    let client = this.connection.client;
-    let actor = this._listTabsResponse.webappsActor;
-    let manifest = this.getProjectManifestURL(this.selectedProject);
-    return AppActorFront.launchApp(client, actor, manifest);
+    let app = this._getProjectFront(this.selectedProject);
+    return app.launch();
   },
 
   launchOrReloadRuntimeApp: function() {
     if (this.selectedProject && this.selectedProject.type != "runtimeApp") {
       return promise.reject("attempting to launch / reload a non-runtime app");
     }
-    let client = this.connection.client;
-    let actor = this._listTabsResponse.webappsActor;
-    let manifest = this.getProjectManifestURL(this.selectedProject);
-    if (!this.isProjectRunning()) {
-      return AppActorFront.launchApp(client, actor, manifest);
+    let app = this._getProjectFront(this.selectedProject);
+    if (!app.running) {
+      return app.launch();
     } else {
-      return AppActorFront.reloadApp(client, actor, manifest);
+      return app.reload();
     }
   },
 
   installAndRunProject: function() {
     let project = this.selectedProject;
 
     if (!project || (project.type != "packaged" && project.type != "hosted")) {
       console.error("Can't install project. Unknown type of project.");
@@ -483,74 +442,67 @@ exports.AppManager = AppManager = {
 
       yield self.validateProject(project);
 
       if (project.errorsCount > 0) {
         self.reportError("error_cantInstallValidationErrors");
         return;
       }
 
-      let client = self.connection.client;
-      let actor = self._listTabsResponse.webappsActor;
       let installPromise;
 
       if (project.type != "packaged" && project.type != "hosted") {
         return promise.reject("Don't know how to install project");
       }
 
+      let response;
       if (project.type == "packaged") {
-        let {appId} = yield AppActorFront.installPackaged(client,
-                                                          actor,
-                                                          project.location,
-                                                          project.packagedAppOrigin);
+        response = yield self._appsFront.installPackaged(project.location,
+                                                             project.packagedAppOrigin);
+
         // If the packaged app specified a custom origin override,
         // we need to update the local project origin
-        project.packagedAppOrigin = appId;
+        project.packagedAppOrigin = response.appId;
         // And ensure the indexed db on disk is also updated
         AppProjects.update(project);
       }
 
       if (project.type == "hosted") {
         let manifestURLObject = Services.io.newURI(project.location, null, null);
         let origin = Services.io.newURI(manifestURLObject.prePath, null, null);
         let appId = origin.host;
         let metadata = {
           origin: origin.spec,
           manifestURL: project.location
         };
-        yield AppActorFront.installHosted(client,
-                                          actor,
-                                          appId,
-                                          metadata,
-                                          project.manifest);
+        response = yield self._appsFront.installHosted(appId,
+                                            metadata,
+                                            project.manifest);
       }
 
-      let manifest = self.getProjectManifestURL(project);
-      if (!self._runningApps.has(manifest)) {
+      let {app} = response;
+      if (!app.running) {
         let deferred = promise.defer();
         self.on("app-manager-update", function onUpdate(event, what) {
           if (what == "project-is-running") {
             self.off("app-manager-update", onUpdate);
             deferred.resolve();
           }
         });
-        yield AppActorFront.launchApp(client, actor, manifest);
+        yield app.launch();
         yield deferred.promise;
-
       } else {
-        yield AppActorFront.reloadApp(client, actor, manifest);
+        yield app.reload();
       }
     });
   },
 
   stopRunningApp: function() {
-    let client = this.connection.client;
-    let actor = this._listTabsResponse.webappsActor;
-    let manifest = this.getProjectManifestURL(this.selectedProject);
-    return AppActorFront.closeApp(client, actor, manifest);
+    let app = this._getProjectFront(this.selectedProject);
+    return app.close();
   },
 
   /* PROJECT VALIDATION */
 
   validateProject: function(project) {
     if (!project) {
       return promise.reject();
     }
@@ -676,16 +628,17 @@ exports.AppManager = AppManager = {
   trackWiFiRuntimes: function() {
     if (!this.isWiFiScanningEnabled) {
       return;
     }
     this._updateWiFiRuntimes = this._updateWiFiRuntimes.bind(this);
     discovery.on("devtools-device-added", this._updateWiFiRuntimes);
     discovery.on("devtools-device-updated", this._updateWiFiRuntimes);
     discovery.on("devtools-device-removed", this._updateWiFiRuntimes);
+    this._updateWiFiRuntimes();
   },
   untrackWiFiRuntimes: function() {
     if (!this.isWiFiScanningEnabled) {
       return;
     }
     discovery.off("devtools-device-added", this._updateWiFiRuntimes);
     discovery.off("devtools-device-updated", this._updateWiFiRuntimes);
     discovery.off("devtools-device-removed", this._updateWiFiRuntimes);
--- a/browser/devtools/webide/test/test_basic.html
+++ b/browser/devtools/webide/test/test_basic.html
@@ -21,17 +21,16 @@
         Task.spawn(function* () {
             let win = yield openWebIDE();
 
             ok(win, "Found a window");
             ok(win.AppManager, "App Manager accessible");
             let appmgr = win.AppManager;
             ok(appmgr.connection, "App Manager connection ready");
             ok(appmgr.runtimeList, "Runtime list ready");
-            ok(appmgr.webAppsStore, "WebApps store ready");
 
             // test error reporting
             let nbox = win.document.querySelector("#notificationbox");
             let notification =  nbox.getNotificationWithValue("webide:errornotification");
             ok(!notification, "No notification yet");
             let deferred = promise.defer();
             nextTick().then(() => {
               deferred.reject("BOOM!");
--- a/browser/devtools/webide/webide-prefs.js
+++ b/browser/devtools/webide/webide-prefs.js
@@ -1,18 +1,18 @@
 # -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 # 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/.
 
 pref("devtools.webide.showProjectEditor", true);
 pref("devtools.webide.templatesURL", "https://code.cdn.mozilla.net/templates/list.json");
 pref("devtools.webide.autoinstallADBHelper", true);
-pref("devtools.webide.lastprojectlocation", "");
 pref("devtools.webide.restoreLastProject", true);
 pref("devtools.webide.enableLocalRuntime", false);
 pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
 pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
 pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
 pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
 pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
 pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
 pref("devtools.webide.lastConnectedRuntime", "");
+pref("devtools.webide.lastSelectedProject", "");
--- a/browser/extensions/pdfjs/content/pdfjschildbootstrap.js
+++ b/browser/extensions/pdfjs/content/pdfjschildbootstrap.js
@@ -19,17 +19,19 @@
 
 'use strict';
 
 /*
  * pdfjschildbootstrap.js loads into the content process to take care of
  * initializing our built-in version of pdfjs when running remote.
  */
 
+Components.utils.import('resource://gre/modules/Services.jsm');
 Components.utils.import('resource://pdf.js/PdfJs.jsm');
 Components.utils.import('resource://pdf.js/PdfjsContentUtils.jsm');
 
 // init content utils shim pdfjs will use to access privileged apis.
 PdfjsContentUtils.init();
 
-// register various pdfjs factories that hook us into content loading.
-PdfJs.updateRegistration();
-
+if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+  // register various pdfjs factories that hook us into content loading.
+  PdfJs.updateRegistration();
+}
--- a/browser/locales/en-US/chrome/browser/devtools/scratchpad.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/scratchpad.properties
@@ -27,16 +27,20 @@ scratchpadContext.invalid=Scratchpad can
 # LOCALIZATION NOTE  (openFile.title): This is the file picker title, when you
 # open a file from Scratchpad.
 openFile.title=Open File
 
 # LOCALIZATION NOTE  (openFile.failed): This is the message displayed when file
 # open fails.
 openFile.failed=Failed to read the file.
 
+# LOCALIZATION NOTE  (openFile.failed): This is the message displayed when file
+# open fails.
+importFromFile.convert.failed=Failed to convert file to Unicode from %1$S.
+
 # LOCALIZATION NOTE (clearRecentMenuItems.label): This is the label for the
 # menuitem in the 'Open Recent'-menu which clears all recent files.
 clearRecentMenuItems.label=Clear Items
 
 # LOCALIZATION NOTE  (saveFileAs): This is the file picker title, when you save
 # a file in Scratchpad.
 saveFileAs=Save File As
 
new file mode 100644
--- /dev/null
+++ b/browser/modules/E10SUtils.jsm
@@ -0,0 +1,59 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["E10SUtils"];
+
+const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+this.E10SUtils = {
+  shouldBrowserBeRemote: function(aURL) {
+    // loadURI in browser.xml treats null as about:blank
+    if (!aURL)
+      aURL = "about:blank";
+
+    if (aURL.startsWith("about:") &&
+        aURL.toLowerCase() != "about:home" &&
+        aURL.toLowerCase() != "about:blank" &&
+        !aURL.toLowerCase().startsWith("about:neterror")) {
+      return false;
+    }
+
+    return true;
+  },
+
+  shouldLoadURI: function(aDocShell, aURI, aReferrer) {
+    // about:blank is the initial document and can load anywhere
+    if (aURI.spec == "about:blank")
+      return true;
+
+    // Inner frames should always load in the current process
+    if (aDocShell.QueryInterface(Ci.nsIDocShellTreeItem).sameTypeParent)
+      return true;
+
+    // If the URI can be loaded in the current process then continue
+    let isRemote = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+    if (this.shouldBrowserBeRemote(aURI.spec) == isRemote)
+      return true;
+
+    return false;
+  },
+
+  redirectLoad: function(aDocShell, aURI, aReferrer) {
+    // Retarget the load to the correct process
+    let messageManager = aDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                                  .getInterface(Ci.nsIContentFrameMessageManager);
+    let sessionHistory = aDocShell.getInterface(Ci.nsIWebNavigation).sessionHistory;
+
+    messageManager.sendAsyncMessage("Browser:LoadURI", {
+      uri: aURI.spec,
+      referrer: aReferrer ? aReferrer.spec : null,
+      historyIndex: sessionHistory.requestedIndex,
+    });
+    return false;
+  },
+};
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -280,17 +280,17 @@ PluginContent.prototype = {
     let eventType = event.type;
 
     if (eventType == "unload") {
       this.uninit();
       return;
     }
 
     if (eventType == "PluginRemoved") {
-      this.updateNotificationUI();
+      this.updateNotificationUI(event.target);
       return;
     }
 
     if (eventType == "click") {
       this.onOverlayClick(event);
       return;
     }
 
@@ -693,17 +693,39 @@ PluginContent.prototype = {
 
     this.global.sendAsyncMessage("PluginContent:ShowClickToPlayNotification", {
       plugins: [... this.pluginData.values()],
       showNow: showNow,
       host: principalHost,
     }, null, principal);
   },
 
-  updateNotificationUI: function () {
+  /**
+   * Updates the "hidden plugin" notification bar UI.
+   *
+   * @param document (optional)
+   *        Specify the document that is causing the update.
+   *        This is useful when the document is possibly no longer
+   *        the current loaded document (for example, if we're
+   *        responding to a PluginRemoved event for an unloading
+   *        document). If this parameter is omitted, it defaults
+   *        to the current top-level document.
+   */
+  updateNotificationUI: function (document) {
+    let principal;
+
+    if (document) {
+      // We're only interested in the top-level document, since that's
+      // the one that provides the Principal that we send back to the
+      // parent.
+      principal = document.defaultView.top.document.nodePrincipal;
+    } else {
+      principal = this.content.document.nodePrincipal;
+    }
+
     // Make a copy of the actions from the last popup notification.
     let haveInsecure = false;
     let actions = new Map();
     for (let action of this.pluginData.values()) {
       switch (action.fallbackType) {
         // haveInsecure will trigger the red flashing icon and the infobar
         // styling below
         case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
@@ -713,19 +735,18 @@ PluginContent.prototype = {
 
         case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
           actions.set(action.permissionString, action);
           continue;
       }
     }
 
     // Remove plugins that are already active, or large enough to show an overlay.
-    let contentWindow = this.global.content;
-    let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIDOMWindowUtils);
+    let cwu = this.content.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDOMWindowUtils);
     for (let plugin of cwu.plugins) {
       let info = this._getPluginInfo(plugin);
       if (!actions.has(info.permissionString)) {
         continue;
       }
       let fallbackType = info.fallbackType;
       if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
         actions.delete(info.permissionString);
@@ -750,17 +771,16 @@ PluginContent.prototype = {
         if (actions.size == 0) {
           break;
         }
       }
     }
 
     // If there are any items remaining in `actions` now, they are hidden
     // plugins that need a notification bar.
-    let principal = contentWindow.document.nodePrincipal;
     this.global.sendAsyncMessage("PluginContent:UpdateHiddenPluginUI", {
       haveInsecure: haveInsecure,
       actions: [... actions.values()],
       host: this._getHostFromPrincipal(principal),
     }, null, principal);
   },
 
   removeNotification: function (name) {
--- a/browser/modules/TabCrashReporter.jsm
+++ b/browser/modules/TabCrashReporter.jsm
@@ -91,17 +91,16 @@ this.TabCrashReporter = {
     if (browser.isRemoteBrowser)
       return;
 
     let doc = browser.contentDocument;
     if (!doc.documentURI.startsWith("about:tabcrashed"))
       return;
 
     let url = browser.currentURI.spec;
-    browser.getTabBrowser().updateBrowserRemotenessByURL(browser, url);
     browser.loadURIWithFlags(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
   },
 
   onAboutTabCrashedLoad: function (aBrowser) {
     if (!this.childMap)
       return;
 
     let dumpID = this.childMap.get(this.browserMap.get(aBrowser));
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -12,16 +12,17 @@ EXTRA_JS_MODULES += [
     'BrowserNewTabPreloader.jsm',
     'BrowserUITelemetry.jsm',
     'Chat.jsm',
     'ContentClick.jsm',
     'ContentLinkHandler.jsm',
     'ContentSearch.jsm',
     'ContentWebRTC.jsm',
     'CustomizationTabPreloader.jsm',
+    'E10SUtils.jsm',
     'Feeds.jsm',
     'FormSubmitObserver.jsm',
     'FormValidationHandler.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'PanelFrame.jsm',
     'RemotePrompt.jsm',
     'SharedFrame.jsm',
--- a/browser/themes/shared/newtab/controls.svg
+++ b/browser/themes/shared/newtab/controls.svg
@@ -1,28 +1,32 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <svg version="1.1"
      id="icons-enhanced-tiles"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink"
      x="0"
      y="0"
-     width="256"
+     width="288"
      height="32"
-     viewBox="0 0 256 32">
+     viewBox="0 0 288 32">
 
   <defs>
     <style type="text/css"><![CDATA[
       /* Glyph Styles */
 
       .glyphShape-style {
         fill: #737373;
       }
 
+      .glyphShape-style-pin {
+        fill: #b4b4b4;
+      }
+
       .glyphShape-style-hover-gear {
         fill: #4a90e2;
       }
 
       .glyphShape-style-hover-pin {
         fill: #4a90e2;
       }
 
@@ -100,9 +104,13 @@
   </g>
 
   <g id="icon-delete-hover-active" transform="translate(224)">
     <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow" />
     <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle" />
     <use xlink:href="#glyphShape-delete" class="glyphShape-style-hover-active" />
   </g>
 
+  <g id="icon-pin-default" transform="translate(256)">
+    <use xlink:href="#glyphShape-pin"    class="glyphShape-style-pin" />
+  </g>
+
 </svg>
--- a/browser/themes/shared/newtab/newTab.inc.css
+++ b/browser/themes/shared/newtab/newTab.inc.css
@@ -117,40 +117,55 @@
   background-origin: padding-box;
   background-clip: padding-box;
   background-repeat: no-repeat;
   background-size: cover;
   border-radius: inherit;
   transition: opacity 100ms ease-out;
 }
 
-.newtab-thumbnail.enhanced-content:hover {
+.newtab-site:hover .newtab-thumbnail.enhanced-content {
   opacity: 0;
 }
 
 .newtab-site[type=affiliate] .newtab-thumbnail,
 .newtab-site[type=enhanced] .newtab-thumbnail,
 .newtab-site[type=organic] .newtab-thumbnail,
 .newtab-site[type=sponsored] .newtab-thumbnail {
   background-position: center center;
   background-size: auto;
 }
 
 /* TITLES */
+.newtab-sponsored,
 .newtab-title {
-  color: #737373;
+  color: #5c5c5c;
 }
 
 .newtab-site:hover .newtab-title {
-  color: #4a4a4a;
+  color: #222;
 }
 
 .newtab-site[pinned] .newtab-title {
-  color: #2c72c4;
-  font-weight: bold;
+  padding: 0 15px;
+}
+
+.newtab-site[pinned] .newtab-title::before {
+  background-image: -moz-image-rect(url("chrome://browser/skin/newtab/controls.svg"), 7, 278, 28, 266);
+  background-size: 10px;
+  content: "";
+  height: 17px;
+  left: 0;
+  position: absolute;
+  width: 10px;
+}
+
+.newtab-site[pinned] .newtab-title:-moz-locale-dir(rtl)::before {
+  left: auto;
+  right: 0;
 }
 
 /* CONTROLS */
 .newtab-control {
   background-color: transparent;
   background-size: 24px;
   border: none;
   height: 24px;
--- a/content/base/src/nsCSPContext.cpp
+++ b/content/base/src/nsCSPContext.cpp
@@ -168,16 +168,19 @@ nsCSPContext::ShouldLoad(nsContentPolicy
     }
   }
 
   nsAutoString violatedDirective;
   for (uint32_t p = 0; p < mPolicies.Length(); p++) {
     if (!mPolicies[p]->permits(aContentType,
                                aContentLocation,
                                nonce,
+                               // aExtra is only non-null if
+                               // the channel got redirected.
+                               (aExtra != nullptr),
                                violatedDirective)) {
       // If the policy is violated and not report-only, reject the load and
       // report to the console
       if (!mPolicies[p]->getReportOnlyFlag()) {
         CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, nsIContentPolicy::REJECT_SERVER"));
         *outDecision = nsIContentPolicy::REJECT_SERVER;
       }
 
@@ -787,17 +790,18 @@ class CSPReportSenderRunnable MOZ_FINAL 
                             const nsAString& aViolatedDirective,
                             const nsAString& aObserverSubject,
                             const nsAString& aSourceFile,
                             const nsAString& aScriptSample,
                             uint32_t aLineNum,
                             uint64_t aInnerWindowID,
                             nsCSPContext* aCSPContext)
       : mBlockedContentSource(aBlockedContentSource)
-      , mOriginalURI(aOriginalURI) , mViolatedPolicyIndex(aViolatedPolicyIndex)
+      , mOriginalURI(aOriginalURI)
+      , mViolatedPolicyIndex(aViolatedPolicyIndex)
       , mReportOnlyFlag(aReportOnlyFlag)
       , mViolatedDirective(aViolatedDirective)
       , mSourceFile(aSourceFile)
       , mScriptSample(aScriptSample)
       , mLineNum(aLineNum)
       , mInnerWindowID(aInnerWindowID)
       , mCSPContext(aCSPContext)
     {
@@ -1019,16 +1023,17 @@ nsCSPContext::PermitsAncestry(nsIDocShel
       nsAutoCString spec;
       ancestorsArray[a]->GetSpec(spec);
       CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, checking ancestor: %s", spec.get()));
       }
 #endif
       if (!mPolicies[i]->permits(nsIContentPolicy::TYPE_DOCUMENT,
                                  ancestorsArray[a],
                                  EmptyString(), // no nonce
+                                 false, // no redirect
                                  violatedDirective)) {
         // Policy is violated
         // Send reports, but omit the ancestor URI if cross-origin as per spec
         // (it is a violation of the same-origin policy).
         bool okToSendAncestor = NS_SecurityCompareURIs(ancestorsArray[a], mSelfURI, true);
 
         this->AsyncReportViolation((okToSendAncestor ? ancestorsArray[a] : nullptr),
                                    mSelfURI,
--- a/content/base/src/nsCSPParser.cpp
+++ b/content/base/src/nsCSPParser.cpp
@@ -25,28 +25,31 @@ GetCspParserLog()
   if (!gCspParserPRLog)
     gCspParserPRLog = PR_NewLogModule("CSPParser");
   return gCspParserPRLog;
 }
 #endif
 
 #define CSPPARSERLOG(args) PR_LOG(GetCspParserLog(), 4, args)
 
-static const char16_t COLON       = ':';
-static const char16_t SEMICOLON   = ';';
-static const char16_t SLASH       = '/';
-static const char16_t PLUS        = '+';
-static const char16_t DASH        = '-';
-static const char16_t DOT         = '.';
-static const char16_t UNDERLINE   = '_';
-static const char16_t WILDCARD    = '*';
-static const char16_t WHITESPACE  = ' ';
-static const char16_t SINGLEQUOTE = '\'';
-static const char16_t OPEN_CURL   = '{';
-static const char16_t CLOSE_CURL  = '}';
+static const char16_t COLON        = ':';
+static const char16_t SEMICOLON    = ';';
+static const char16_t SLASH        = '/';
+static const char16_t PLUS         = '+';
+static const char16_t DASH         = '-';
+static const char16_t DOT          = '.';
+static const char16_t UNDERLINE    = '_';
+static const char16_t TILDE        = '~';
+static const char16_t WILDCARD     = '*';
+static const char16_t WHITESPACE   = ' ';
+static const char16_t SINGLEQUOTE  = '\'';
+static const char16_t OPEN_CURL    = '{';
+static const char16_t CLOSE_CURL   = '}';
+static const char16_t NUMBER_SIGN  = '#';
+static const char16_t QUESTIONMARK = '?';
 
 static uint32_t kSubHostPathCharacterCutoff = 512;
 
 static const char* kHashSourceValidFns [] = { "sha256", "sha384", "sha512" };
 static const uint32_t kHashSourceValidFnsLen = 3;
 
 /* ===== nsCSPTokenizer ==================== */
 
@@ -140,16 +143,33 @@ isNumberToken(char16_t aSymbol)
 void
 nsCSPParser::resetCurChar(const nsAString& aToken)
 {
   mCurChar = aToken.BeginReading();
   mEndChar = aToken.EndReading();
   resetCurValue();
 }
 
+// The path is terminated by the first question mark ("?") or
+// number sign ("#") character, or by the end of the URI.
+// http://tools.ietf.org/html/rfc3986#section-3.3
+bool
+nsCSPParser::atEndOfPath()
+{
+  return (atEnd() || peek(QUESTIONMARK) || peek(NUMBER_SIGN));
+}
+
+bool
+nsCSPParser::atValidPathChar()
+{
+  return (peek(isCharacterToken) || peek(isNumberToken) ||
+          peek(DASH) || peek(DOT) ||
+          peek(UNDERLINE) || peek(TILDE));
+}
+
 void
 nsCSPParser::logWarningErrorToConsole(uint32_t aSeverityFlag,
                                       const char* aProperty,
                                       const char16_t* aParams[],
                                       uint32_t aParamsLength)
 {
   CSPPARSERLOG(("nsCSPParser::logWarningErrorToConsole: %s", aProperty));
 
@@ -184,35 +204,16 @@ nsCSPParser::schemeChar()
   }
   return accept(isCharacterToken) ||
          accept(isNumberToken) ||
          accept(PLUS) ||
          accept(DASH) ||
          accept(DOT);
 }
 
-bool
-nsCSPParser::fileAndArguments()
-{
-  CSPPARSERLOG(("nsCSPParser::fileAndArguments, mCurToken: %s, mCurValue: %s",
-               NS_ConvertUTF16toUTF8(mCurToken).get(),
-               NS_ConvertUTF16toUTF8(mCurValue).get()));
-
-  // Possibly we already parsed part of the file in path(), therefore accepting "."
-  if (accept(DOT) && !accept(isCharacterToken)) {
-    return false;
-  }
-
-  // From now on, accept pretty much anything to avoid unnecessary errors
-  while (!atEnd()) {
-    advance();
-  }
-  return true;
-}
-
 // port = ":" ( 1*DIGIT / "*" )
 bool
 nsCSPParser::port()
 {
   CSPPARSERLOG(("nsCSPParser::port, mCurToken: %s, mCurValue: %s",
                NS_ConvertUTF16toUTF8(mCurToken).get(),
                NS_ConvertUTF16toUTF8(mCurValue).get()));
 
@@ -248,34 +249,33 @@ nsCSPParser::subPath(nsCSPHostSrc* aCspH
                NS_ConvertUTF16toUTF8(mCurToken).get(),
                NS_ConvertUTF16toUTF8(mCurValue).get()));
 
   // Emergency exit to avoid endless loops in case a path in a CSP policy
   // is longer than 512 characters, or also to avoid endless loops
   // in case we are parsing unrecognized characters in the following loop.
   uint32_t charCounter = 0;
 
-  while (!atEnd() && !peek(DOT)) {
-    ++charCounter;
-    while (hostChar() || accept(UNDERLINE)) {
-      /* consume */
-      ++charCounter;
-    }
-    if (accept(SLASH)) {
-      ++charCounter;
+  while (!atEndOfPath()) {
+    if (peek(SLASH)) {
       aCspHost->appendPath(mCurValue);
       // Resetting current value since we are appending parts of the path
       // to aCspHost, e.g; "http://www.example.com/path1/path2" then the
       // first part is "/path1", second part "/path2"
       resetCurValue();
     }
-    if (atEnd()) {
-      return true;
+    else if (!atValidPathChar()) {
+      const char16_t* params[] = { mCurToken.get() };
+      logWarningErrorToConsole(nsIScriptError::warningFlag,
+                               "couldntParseInvalidSource",
+                               params, ArrayLength(params));
+      return false;
     }
-    if (charCounter > kSubHostPathCharacterCutoff) {
+    advance();
+    if (++charCounter > kSubHostPathCharacterCutoff) {
       return false;
     }
   }
   aCspHost->appendPath(mCurValue);
   resetCurValue();
   return true;
 }
 
@@ -293,17 +293,20 @@ nsCSPParser::path(nsCSPHostSrc* aCspHost
   resetCurValue();
 
   if (!accept(SLASH)) {
     const char16_t* params[] = { mCurToken.get() };
     logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParseInvalidSource",
                              params, ArrayLength(params));
     return false;
   }
-  if (atEnd()) {
+  if (atEndOfPath()) {
+    // one slash right after host [port] is also considered a path, e.g.
+    // www.example.com/ should result in www.example.com/
+    aCspHost->appendPath(mCurValue);
     return true;
   }
   // path can begin with "/" but not "//"
   // see http://tools.ietf.org/html/rfc3986#section-3.3
   if (!hostChar()) {
     const char16_t* params[] = { mCurToken.get() };
     logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParseInvalidSource",
                              params, ArrayLength(params));
@@ -319,17 +322,17 @@ nsCSPParser::subHost()
                NS_ConvertUTF16toUTF8(mCurToken).get(),
                NS_ConvertUTF16toUTF8(mCurValue).get()));
 
   // Emergency exit to avoid endless loops in case a host in a CSP policy
   // is longer than 512 characters, or also to avoid endless loops
   // in case we are parsing unrecognized characters in the following loop.
   uint32_t charCounter = 0;
 
-  while (!atEnd() && !peek(COLON) && !peek(SLASH)) {
+  while (!atEndOfPath() && !peek(COLON) && !peek(SLASH)) {
     ++charCounter;
     while (hostChar()) {
       /* consume */
       ++charCounter;
     }
     if (accept(DOT) && !hostChar()) {
       return false;
     }
@@ -463,38 +466,30 @@ nsCSPParser::hostSource()
   if (peek(COLON)) {
     if (!port()) {
       delete cspHost;
       return nullptr;
     }
     cspHost->setPort(mCurValue);
   }
 
-  if (atEnd()) {
+  if (atEndOfPath()) {
     return cspHost;
   }
 
   // Calling path() to see if there is a path to parse, if an error
   // occurs, path() reports the error; handing cspHost as an argument
   // which simplifies parsing of several paths.
   if (!path(cspHost)) {
     // If the host [port] is followed by a path, it has to be a valid path,
     // otherwise we pass the nullptr, indicating an error, up the callstack.
     // see also http://www.w3.org/TR/CSP11/#source-list
     delete cspHost;
     return nullptr;
   }
-
-  // Calling fileAndArguments to see if there are any files to parse;
-  // if an error occurs, fileAndArguments() reports the error; if
-  // fileAndArguments returns true, we have a valid file, so we add it.
-  if (fileAndArguments()) {
-    cspHost->setFileAndArguments(mCurValue);
-  }
-
   return cspHost;
 }
 
 // scheme-source = scheme ":"
 nsCSPSchemeSrc*
 nsCSPParser::schemeSource()
 {
   CSPPARSERLOG(("nsCSPParser::schemeSource, mCurToken: %s, mCurValue: %s",
--- a/content/base/src/nsCSPParser.h
+++ b/content/base/src/nsCSPParser.h
@@ -121,17 +121,16 @@ class nsCSPParser {
     nsCSPNonceSrc*  nonceSource();
     nsCSPHashSrc*   hashSource();
     nsCSPHostSrc*   appHost(); // helper function to support app specific hosts
     nsCSPHostSrc*   host();
     bool            hostChar();
     bool            schemeChar();
     bool            port();
     bool            path(nsCSPHostSrc* aCspHost);
-    bool            fileAndArguments();
 
     bool subHost();                                       // helper function to parse subDomains
     bool subPath(nsCSPHostSrc* aCspHost);                 // helper function to parse paths
     void reportURIList(nsTArray<nsCSPBaseSrc*>& outSrcs); // helper function to parse report-uris
 
     inline bool atEnd()
     {
       return mCurChar >= mEndChar;
@@ -168,16 +167,19 @@ class nsCSPParser {
       return true;
     }
 
     inline void resetCurValue()
     {
       mCurValue.Truncate();
     }
 
+    bool atEndOfPath();
+    bool atValidPathChar();
+
     void resetCurChar(const nsAString& aToken);
 
     void logWarningErrorToConsole(uint32_t aSeverityFlag,
                                   const char* aProperty,
                                   const char16_t* aParams[],
                                   uint32_t aParamsLength);
 
 /**
--- a/content/base/src/nsCSPUtils.cpp
+++ b/content/base/src/nsCSPUtils.cpp
@@ -209,17 +209,17 @@ nsCSPBaseSrc::nsCSPBaseSrc()
 nsCSPBaseSrc::~nsCSPBaseSrc()
 {
 }
 
 // ::permits is only called for external load requests, therefore:
 // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class
 // implementation which will never allow the load.
 bool
-nsCSPBaseSrc::permits(nsIURI* aUri, const nsAString& aNonce) const
+nsCSPBaseSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPBaseSrc::permits, aUri: %s", spec.get()));
   }
 #endif
@@ -246,17 +246,17 @@ nsCSPSchemeSrc::nsCSPSchemeSrc(const nsA
   ToLowerCase(mScheme);
 }
 
 nsCSPSchemeSrc::~nsCSPSchemeSrc()
 {
 }
 
 bool
-nsCSPSchemeSrc::permits(nsIURI* aUri, const nsAString& aNonce) const
+nsCSPSchemeSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPSchemeSrc::permits, aUri: %s", spec.get()));
   }
 #endif
@@ -283,17 +283,17 @@ nsCSPHostSrc::nsCSPHostSrc(const nsAStri
   ToLowerCase(mHost);
 }
 
 nsCSPHostSrc::~nsCSPHostSrc()
 {
 }
 
 bool
-nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce) const
+nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPHostSrc::permits, aUri: %s", spec.get()));
   }
 #endif
@@ -337,16 +337,44 @@ nsCSPHostSrc::permits(nsIURI* aUri, cons
       return false;
     }
   }
   // Check if hosts match.
   else if (!mHost.Equals(NS_ConvertUTF8toUTF16(uriHost))) {
     return false;
   }
 
+  // If there is a path, we have to enforce path-level matching,
+  // unless the channel got redirected, see:
+  // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
+  if (!aWasRedirected && !mPath.IsEmpty()) {
+    // cloning uri so we can ignore the ref
+    nsCOMPtr<nsIURI> uri;
+    aUri->CloneIgnoringRef(getter_AddRefs(uri));
+
+    nsAutoCString uriPath;
+    rv = uri->GetPath(uriPath);
+    NS_ENSURE_SUCCESS(rv, false);
+    // check if the last character of mPath is '/'; if so
+    // we just have to check loading resource is within
+    // the allowed path.
+    if (mPath.Last() == '/') {
+      if (!StringBeginsWith(NS_ConvertUTF8toUTF16(uriPath), mPath)) {
+        return false;
+      }
+    }
+    // otherwise mPath whitelists a specific file, and we have to
+    // check if the loading resource matches that whitelisted file.
+    else {
+      if (!mPath.Equals(NS_ConvertUTF8toUTF16(uriPath))) {
+        return false;
+      }
+    }
+  }
+
   // If port uses wildcard, allow the load.
   if (mPort.EqualsASCII("*")) {
     return true;
   }
 
   // Check if ports match
   int32_t uriPort;
   rv = aUri->GetPort(&uriPort);
@@ -364,17 +392,17 @@ nsCSPHostSrc::permits(nsIURI* aUri, cons
   else {
     nsString portStr;
     portStr.AppendInt(uriPort);
     if (!mPort.Equals(portStr)) {
       return false;
     }
   }
 
-  // At the end: scheme, host, port, match; allow the load.
+  // At the end: scheme, host, path, and port match -> allow the load.
   return true;
 }
 
 void
 nsCSPHostSrc::toString(nsAString& outStr) const
 {
   // If mHost is a single "*", we append the wildcard and return.
   if (mHost.EqualsASCII("*") &&
@@ -392,19 +420,18 @@ nsCSPHostSrc::toString(nsAString& outStr
   outStr.Append(mHost);
 
   // append port
   if (!mPort.IsEmpty()) {
     outStr.AppendASCII(":");
     outStr.Append(mPort);
   }
 
-  // in CSP 1.1, paths are ignoed
-  // outStr.Append(mPath);
-  // outStr.Append(mFileAndArguments);
+  // append path
+  outStr.Append(mPath);
 }
 
 void
 nsCSPHostSrc::setScheme(const nsAString& aScheme)
 {
   mScheme = aScheme;
   ToLowerCase(mScheme);
 }
@@ -418,23 +445,16 @@ nsCSPHostSrc::setPort(const nsAString& a
 
 void
 nsCSPHostSrc::appendPath(const nsAString& aPath)
 {
   mPath.Append(aPath);
   ToLowerCase(mPath);
 }
 
-void
-nsCSPHostSrc::setFileAndArguments(const nsAString& aFile)
-{
-  mFileAndArguments = aFile;
-  ToLowerCase(mFileAndArguments);
-}
-
 /* ===== nsCSPKeywordSrc ===================== */
 
 nsCSPKeywordSrc::nsCSPKeywordSrc(CSPKeyword aKeyword)
 {
   NS_ASSERTION((aKeyword != CSP_SELF),
                "'self' should have been replaced in the parser");
   mKeyword = aKeyword;
 }
@@ -464,17 +484,17 @@ nsCSPNonceSrc::nsCSPNonceSrc(const nsASt
 {
 }
 
 nsCSPNonceSrc::~nsCSPNonceSrc()
 {
 }
 
 bool
-nsCSPNonceSrc::permits(nsIURI* aUri, const nsAString& aNonce) const
+nsCSPNonceSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPNonceSrc::permits, aUri: %s, aNonce: %s",
                 spec.get(), NS_ConvertUTF16toUTF8(aNonce).get()));
   }
@@ -594,39 +614,39 @@ nsCSPDirective::nsCSPDirective(enum CSPD
 nsCSPDirective::~nsCSPDirective()
 {
   for (uint32_t i = 0; i < mSrcs.Length(); i++) {
     delete mSrcs[i];
   }
 }
 
 bool
-nsCSPDirective::permits(nsIURI* aUri, const nsAString& aNonce) const
+nsCSPDirective::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPDirective::permits, aUri: %s", spec.get()));
   }
 #endif
 
   for (uint32_t i = 0; i < mSrcs.Length(); i++) {
-    if (mSrcs[i]->permits(aUri, aNonce)) {
+    if (mSrcs[i]->permits(aUri, aNonce, aWasRedirected)) {
       return true;
     }
   }
   return false;
 }
 
 bool
 nsCSPDirective::permits(nsIURI* aUri) const
 {
   nsString dummyNonce;
-  return permits(aUri, dummyNonce);
+  return permits(aUri, dummyNonce, false);
 }
 
 bool
 nsCSPDirective::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const
 {
   CSPUTILSLOG(("nsCSPDirective::allows, aKeyWord: %s, a HashOrNonce: %s",
               CSP_EnumToKeyword(aKeyword), NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
 
@@ -749,16 +769,17 @@ nsCSPPolicy::~nsCSPPolicy()
     delete mDirectives[i];
   }
 }
 
 bool
 nsCSPPolicy::permits(nsContentPolicyType aContentType,
                      nsIURI* aUri,
                      const nsAString& aNonce,
+                     bool aWasRedirected,
                      nsAString& outViolatedDirective) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPPolicy::permits, aContentType: %d, aUri: %s, aNonce: %s",
                 aContentType, spec.get(), NS_ConvertUTF16toUTF8(aNonce).get()));
@@ -769,17 +790,17 @@ nsCSPPolicy::permits(nsContentPolicyType
 
   nsCSPDirective* defaultDir = nullptr;
 
   // These directive arrays are short (1-5 elements), not worth using a hashtable.
 
   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
     // Check if the directive name matches
     if (mDirectives[i]->restrictsContentType(aContentType)) {
-      if (!mDirectives[i]->permits(aUri, aNonce)) {
+      if (!mDirectives[i]->permits(aUri, aNonce, aWasRedirected)) {
         mDirectives[i]->toString(outViolatedDirective);
         return false;
       }
       return true;
     }
     if (mDirectives[i]->isDefaultDirective()) {
       defaultDir = mDirectives[i];
     }
@@ -790,17 +811,17 @@ nsCSPPolicy::permits(nsContentPolicyType
   // TODO: currently [frame-ancestors] is mapped to TYPE_DOCUMENT (needs to be fixed)
   if (aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
     return true;
   }
 
   // If the above loop runs through, we haven't found a matching directive.
   // Avoid relooping, just store the result of default-src while looping.
   if (defaultDir) {
-    if (!defaultDir->permits(aUri, aNonce)) {
+    if (!defaultDir->permits(aUri, aNonce, aWasRedirected)) {
       defaultDir->toString(outViolatedDirective);
       return false;
     }
     return true;
   }
 
   // unspecified default-src should default to no restrictions
   // see bug 764937
--- a/content/base/src/nsCSPUtils.h
+++ b/content/base/src/nsCSPUtils.h
@@ -186,56 +186,54 @@ bool CSP_IsQuotelessKeyword(const nsAStr
 
 /* =============== nsCSPSrc ================== */
 
 class nsCSPBaseSrc {
   public:
     nsCSPBaseSrc();
     virtual ~nsCSPBaseSrc();
 
-    virtual bool permits(nsIURI* aUri, const nsAString& aNonce) const;
+    virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
     virtual bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
     virtual void toString(nsAString& outStr) const = 0;
 };
 
 /* =============== nsCSPSchemeSrc ============ */
 
 class nsCSPSchemeSrc : public nsCSPBaseSrc {
   public:
     explicit nsCSPSchemeSrc(const nsAString& aScheme);
     virtual ~nsCSPSchemeSrc();
 
-    bool permits(nsIURI* aUri, const nsAString& aNonce) const;
+    bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
     void toString(nsAString& outStr) const;
 
   private:
     nsString mScheme;
 };
 
 /* =============== nsCSPHostSrc ============== */
 
 class nsCSPHostSrc : public nsCSPBaseSrc {
   public:
     explicit nsCSPHostSrc(const nsAString& aHost);
     virtual ~nsCSPHostSrc();
 
-    bool permits(nsIURI* aUri, const nsAString& aNonce) const;
+    bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
     void toString(nsAString& outStr) const;
 
     void setScheme(const nsAString& aScheme);
     void setPort(const nsAString& aPort);
     void appendPath(const nsAString &aPath);
-    void setFileAndArguments(const nsAString& aFile);
 
   private:
     nsString mScheme;
     nsString mHost;
     nsString mPort;
     nsString mPath;
-    nsString mFileAndArguments;
 };
 
 /* =============== nsCSPKeywordSrc ============ */
 
 class nsCSPKeywordSrc : public nsCSPBaseSrc {
   public:
     explicit nsCSPKeywordSrc(CSPKeyword aKeyword);
     virtual ~nsCSPKeywordSrc();
@@ -249,17 +247,17 @@ class nsCSPKeywordSrc : public nsCSPBase
 
 /* =============== nsCSPNonceSource =========== */
 
 class nsCSPNonceSrc : public nsCSPBaseSrc {
   public:
     explicit nsCSPNonceSrc(const nsAString& aNonce);
     virtual ~nsCSPNonceSrc();
 
-    bool permits(nsIURI* aUri, const nsAString& aNonce) const;
+    bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
     bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
     void toString(nsAString& outStr) const;
 
   private:
     nsString mNonce;
 };
 
 /* =============== nsCSPHashSource ============ */
@@ -293,17 +291,17 @@ class nsCSPReportURI : public nsCSPBaseS
 /* =============== nsCSPDirective ============= */
 
 class nsCSPDirective {
   public:
     nsCSPDirective();
     explicit nsCSPDirective(enum CSPDirective aDirective);
     virtual ~nsCSPDirective();
 
-    bool permits(nsIURI* aUri, const nsAString& aNonce) const;
+    bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
     bool permits(nsIURI* aUri) const;
     bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
     void toString(nsAString& outStr) const;
 
     inline void addSrcs(const nsTArray<nsCSPBaseSrc*>& aSrcs)
       { mSrcs = aSrcs; }
 
     bool restrictsContentType(nsContentPolicyType aContentType) const;
@@ -326,16 +324,17 @@ class nsCSPDirective {
 class nsCSPPolicy {
   public:
     nsCSPPolicy();
     virtual ~nsCSPPolicy();
 
     bool permits(nsContentPolicyType aContentType,
                  nsIURI* aUri,
                  const nsAString& aNonce,
+                 bool aWasRedirected,
                  nsAString& outViolatedDirective) const;
     bool permitsBaseURI(nsIURI* aUri) const;
     bool allows(nsContentPolicyType aContentType,
                 enum CSPKeyword aKeyword,
                 const nsAString& aHashOrNonce) const;
     bool allows(nsContentPolicyType aContentType,
                 enum CSPKeyword aKeyword) const;
     void toString(nsAString& outStr) const;
--- a/content/base/src/nsFrameMessageManager.cpp
+++ b/content/base/src/nsFrameMessageManager.cpp
@@ -1028,17 +1028,17 @@ nsFrameMessageManager::ReceiveMessage(ns
         JS::Rooted<JS::Value> principalValue(cx);
         rv = nsContentUtils::WrapNative(cx, aPrincipal, &NS_GET_IID(nsIPrincipal), &principalValue);
         JS_DefineProperty(cx, param, "principal", principalValue, JSPROP_ENUMERATE);
       }
 
       JS::Rooted<JS::Value> thisValue(cx, JS::UndefinedValue());
 
       JS::Rooted<JS::Value> funval(cx);
-      if (JS_ObjectIsCallable(cx, object)) {
+      if (JS::IsCallable(object)) {
         // If the listener is a JS function:
         funval.setObject(*object);
 
         // A small hack to get 'this' value right on content side where
         // messageManager is wrapped in TabChildGlobal.
         nsCOMPtr<nsISupports> defaultThisValue;
         if (mChrome) {
           defaultThisValue = do_QueryObject(this);
@@ -1050,17 +1050,17 @@ nsFrameMessageManager::ReceiveMessage(ns
         NS_ENSURE_SUCCESS(rv, rv);
       } else {
         // If the listener is a JS object which has receiveMessage function:
         if (!JS_GetProperty(cx, object, "receiveMessage", &funval) ||
             !funval.isObject())
           return NS_ERROR_UNEXPECTED;
 
         // Check if the object is even callable.
-        NS_ENSURE_STATE(JS_ObjectIsCallable(cx, &funval.toObject()));
+        NS_ENSURE_STATE(JS::IsCallable(&funval.toObject()));
         thisValue.setObject(*object);
       }
 
       JS::Rooted<JS::Value> rval(cx, JSVAL_VOID);
       JS::Rooted<JS::Value> argv(cx, JS::ObjectValue(*param));
 
       {
         JS::Rooted<JSObject*> thisObject(cx, thisValue.toObjectOrNull());
--- a/content/base/src/nsInProcessTabChildGlobal.cpp
+++ b/content/base/src/nsInProcessTabChildGlobal.cpp
@@ -300,16 +300,18 @@ nsInProcessTabChildGlobal::PreHandleEven
 #endif
 
   return NS_OK;
 }
 
 nsresult
 nsInProcessTabChildGlobal::InitTabChildGlobal()
 {
+  // If you change this, please change GetCompartmentName() in XPCJSRuntime.cpp
+  // accordingly.
   nsAutoCString id;
   id.AssignLiteral("inProcessTabChildGlobal");
   nsIURI* uri = mOwner->OwnerDoc()->GetDocumentURI();
   if (uri) {
     nsAutoCString u;
     uri->GetSpec(u);
     id.AppendLiteral("?ownedBy=");
     id.Append(u);
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -63,17 +63,17 @@
 #include "nsFrameLoader.h"
 
 #include "nsObjectLoadingContent.h"
 #include "mozAutoDocUpdate.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIChannelPolicy.h"
 #include "nsChannelPolicy.h"
 #include "GeckoProfiler.h"
-#include "nsObjectFrame.h"
+#include "nsPluginFrame.h"
 #include "nsDOMClassInfo.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsDOMJSUtils.h"
 
 #include "nsWidgetsCID.h"
 #include "nsContentCID.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/dom/BindingUtils.h"
@@ -855,17 +855,17 @@ nsObjectLoadingContent::InstantiatePlugi
 
   mInstanceOwner = newOwner;
 
   // Ensure the frame did not change during instantiation re-entry (common).
   // HasNewFrame would not have mInstanceOwner yet, so the new frame would be
   // dangling. (Bug 854082)
   nsIFrame* frame = thisContent->GetPrimaryFrame();
   if (frame && mInstanceOwner) {
-    mInstanceOwner->SetFrame(static_cast<nsObjectFrame*>(frame));
+    mInstanceOwner->SetFrame(static_cast<nsPluginFrame*>(frame));
 
     // Bug 870216 - Adobe Reader renders with incorrect dimensions until it gets
     // a second SetWindow call. This is otherwise redundant.
     mInstanceOwner->CallSetWindow();
   }
 
   // Set up scripting interfaces.
   NotifyContentObjectWrapper();
@@ -1291,17 +1291,17 @@ nsObjectLoadingContent::HasNewFrame(nsIO
     // We are successfully setup as type plugin, but have not spawned an
     // instance due to a lack of a frame.
     AsyncStartPluginInstance();
     return NS_OK;
   }
 
   // Otherwise, we're just changing frames
   // Set up relationship between instance owner and frame.
-  nsObjectFrame *objFrame = static_cast<nsObjectFrame*>(aFrame);
+  nsPluginFrame *objFrame = static_cast<nsPluginFrame*>(aFrame);
   mInstanceOwner->SetFrame(objFrame);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsObjectLoadingContent::GetPluginInstance(nsNPAPIPluginInstance** aInstance)
 {
@@ -2713,23 +2713,23 @@ nsObjectLoadingContent::GetTypeOfContent
   if (caps & eSupportPlugins && PluginExistsForType(aMIMEType.get())) {
     // ShouldPlay will handle checking for disabled plugins
     return eType_Plugin;
   }
 
   return eType_Null;
 }
 
-nsObjectFrame*
+nsPluginFrame*
 nsObjectLoadingContent::GetExistingFrame()
 {
   nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   nsIFrame* frame = thisContent->GetPrimaryFrame();
   nsIObjectFrame* objFrame = do_QueryFrame(frame);
-  return static_cast<nsObjectFrame*>(objFrame);
+  return static_cast<nsPluginFrame*>(objFrame);
 }
 
 void
 nsObjectLoadingContent::CreateStaticClone(nsObjectLoadingContent* aDest) const
 {
   nsImageLoadingContent::CreateStaticImageClone(aDest);
 
   aDest->mType = mType;
@@ -3571,17 +3571,17 @@ nsObjectLoadingContent::SetupProtoChain(
 nsresult
 nsObjectLoadingContent::GetPluginJSObject(JSContext *cx,
                                           JS::Handle<JSObject*> obj,
                                           nsNPAPIPluginInstance *plugin_inst,
                                           JS::MutableHandle<JSObject*> plugin_obj,
                                           JS::MutableHandle<JSObject*> plugin_proto)
 {
   // NB: We need an AutoEnterCompartment because we can be called from
-  // nsObjectFrame when the plugin loads after the JS object for our content
+  // nsPluginFrame when the plugin loads after the JS object for our content
   // node has been created.
   JSAutoCompartment ac(cx, obj);
 
   if (plugin_inst) {
     plugin_inst->GetJSObject(cx, plugin_obj.address());
     if (plugin_obj) {
       if (!::JS_GetPrototype(cx, plugin_obj, plugin_proto)) {
         return NS_ERROR_UNEXPECTED;
--- a/content/base/src/nsObjectLoadingContent.h
+++ b/content/base/src/nsObjectLoadingContent.h
@@ -21,17 +21,17 @@
 #include "nsIRunnable.h"
 #include "nsIThreadInternal.h"
 #include "nsIFrame.h"
 #include "nsIFrameLoader.h"
 
 class nsAsyncInstantiateEvent;
 class nsStopPluginRunnable;
 class AutoSetInstantiatingToFalse;
-class nsObjectFrame;
+class nsPluginFrame;
 class nsFrameLoader;
 class nsXULElement;
 class nsPluginInstanceOwner;
 
 namespace mozilla {
 namespace dom {
 template<typename T> class Sequence;
 struct MozPluginParameter;
@@ -497,17 +497,17 @@ class nsObjectLoadingContent : public ns
      *      click-to-play or other content policy checks
      */
     ObjectType GetTypeOfContent(const nsCString& aMIMEType);
 
     /**
      * Gets the frame that's associated with this content node.
      * Does not flush.
      */
-    nsObjectFrame* GetExistingFrame();
+    nsPluginFrame* GetExistingFrame();
 
     // Helper class for SetupProtoChain
     class SetupProtoChainRunner MOZ_FINAL : public nsIRunnable
     {
       ~SetupProtoChainRunner();
     public:
       NS_DECL_ISUPPORTS
 
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -3975,17 +3975,17 @@ ArrayBufferBuilder::reset()
   mCapacity = mLength = 0;
 }
 
 bool
 ArrayBufferBuilder::setCapacity(uint32_t aNewCap)
 {
   MOZ_ASSERT(!mMapPtr);
 
-  uint8_t *newdata = (uint8_t *) realloc(mDataPtr, aNewCap);
+  uint8_t *newdata = (uint8_t *) js_realloc(mDataPtr, aNewCap);
   if (!newdata) {
     return false;
   }
 
   if (aNewCap > mCapacity) {
     memset(newdata + mCapacity, 0, aNewCap - mCapacity);
   }
 
--- a/content/base/test/TestCSPParser.cpp
+++ b/content/base/test/TestCSPParser.cpp
@@ -282,62 +282,80 @@ nsresult TestIgnoreUpperLowerCasePolicie
 
 nsresult TestIgnorePaths() {
 
   static const PolicyTest policies[] =
   {
     { "script-src http://www.example.com",
       "script-src http://www.example.com" },
     { "script-src http://www.example.com/",
-      "script-src http://www.example.com" },
+      "script-src http://www.example.com/" },
     { "script-src http://www.example.com/path-1",
-      "script-src http://www.example.com" },
+      "script-src http://www.example.com/path-1" },
     { "script-src http://www.example.com/path-1/",
-      "script-src http://www.example.com" },
+      "script-src http://www.example.com/path-1/" },
     { "script-src http://www.example.com/path-1/path_2",
-      "script-src http://www.example.com" },
+      "script-src http://www.example.com/path-1/path_2" },
     { "script-src http://www.example.com/path-1/path_2/",
-      "script-src http://www.example.com" },
+      "script-src http://www.example.com/path-1/path_2/" },
     { "script-src http://www.example.com/path-1/path_2/file.js",
-      "script-src http://www.example.com" },
+      "script-src http://www.example.com/path-1/path_2/file.js" },
     { "script-src http://www.example.com/path-1/path_2/file_1.js",
-      "script-src http://www.example.com" },
+      "script-src http://www.example.com/path-1/path_2/file_1.js" },
     { "script-src http://www.example.com/path-1/path_2/file-2.js",
-      "script-src http://www.example.com" },
+      "script-src http://www.example.com/path-1/path_2/file-2.js" },
     { "script-src http://www.example.com/path-1/path_2/f.js",
-      "script-src http://www.example.com" },
+      "script-src http://www.example.com/path-1/path_2/f.js" },
     { "script-src http://www.example.com:88",
       "script-src http://www.example.com:88" },
     { "script-src http://www.example.com:88/",
-      "script-src http://www.example.com:88" },
+      "script-src http://www.example.com:88/" },
     { "script-src http://www.example.com:88/path-1",
-      "script-src http://www.example.com:88" },
+      "script-src http://www.example.com:88/path-1" },
     { "script-src http://www.example.com:88/path-1/",
-      "script-src http://www.example.com:88" },
+      "script-src http://www.example.com:88/path-1/" },
     { "script-src http://www.example.com:88/path-1/path_2",
-      "script-src http://www.example.com:88" },
+      "script-src http://www.example.com:88/path-1/path_2" },
     { "script-src http://www.example.com:88/path-1/path_2/",
-      "script-src http://www.example.com:88" },
+      "script-src http://www.example.com:88/path-1/path_2/" },
     { "script-src http://www.example.com:88/path-1/path_2/file.js",
-      "script-src http://www.example.com:88" },
+      "script-src http://www.example.com:88/path-1/path_2/file.js" },
     { "script-src http://www.example.com:*",
       "script-src http://www.example.com:*" },
     { "script-src http://www.example.com:*/",
-      "script-src http://www.example.com:*" },
+      "script-src http://www.example.com:*/" },
     { "script-src http://www.example.com:*/path-1",
-      "script-src http://www.example.com:*" },
+      "script-src http://www.example.com:*/path-1" },
     { "script-src http://www.example.com:*/path-1/",
-      "script-src http://www.example.com:*" },
+      "script-src http://www.example.com:*/path-1/" },
     { "script-src http://www.example.com:*/path-1/path_2",
-      "script-src http://www.example.com:*" },
+      "script-src http://www.example.com:*/path-1/path_2" },
     { "script-src http://www.example.com:*/path-1/path_2/",
-      "script-src http://www.example.com:*" },
+      "script-src http://www.example.com:*/path-1/path_2/" },
     { "script-src http://www.example.com:*/path-1/path_2/file.js",
-      "script-src http://www.example.com:*" },
-    { "report-uri http://www.example.com",
+      "script-src http://www.example.com:*/path-1/path_2/file.js" },
+    { "script-src http://www.example.com#foo",
+      "script-src http://www.example.com" },
+    { "script-src http://www.example.com?foo=bar",
+      "script-src http://www.example.com" },
+    { "script-src http://www.example.com:8888#foo",
+      "script-src http://www.example.com:8888" },
+    { "script-src http://www.example.com:8888?foo",
+      "script-src http://www.example.com:8888" },
+    { "script-src http://www.example.com/#foo",
+      "script-src http://www.example.com/" },
+    { "script-src http://www.example.com/?foo",
+      "script-src http://www.example.com/" },
+    { "script-src http://www.example.com/path-1/file.js#foo",
+      "script-src http://www.example.com/path-1/file.js" },
+    { "script-src http://www.example.com/path-1/file.js?foo",
+      "script-src http://www.example.com/path-1/file.js" },
+    { "script-src http://www.example.com/path-1/file.js?foo#bar",
+      "script-src http://www.example.com/path-1/file.js" },
+    { "report-uri http://www.example.com/",
       "report-uri http://www.example.com/" },
     { "report-uri http://www.example.com:8888/asdf",
       "report-uri http://www.example.com:8888/asdf" },
     { "report-uri http://www.example.com:8888/path_1/path_2",
       "report-uri http://www.example.com:8888/path_1/path_2" },
     { "report-uri http://www.example.com:8888/path_1/path_2/report.sjs&301",
       "report-uri http://www.example.com:8888/path_1/path_2/report.sjs&301" },
     { "report-uri /examplepath",
@@ -396,19 +414,19 @@ nsresult TestSimplePolicies() {
       "default-src app://{app-host-is-uid}" },
     { "   ;   default-src abc",
       "default-src http://abc" },
     { " ; ; ; ;     default-src            abc    ; ; ; ;",
       "default-src http://abc" },
     { "script-src 'none' 'none' 'none';",
       "script-src 'none'" },
     { "script-src http://www.example.com/path-1//",
-      "script-src http://www.example.com" },
+      "script-src http://www.example.com/path-1//" },
     { "script-src http://www.example.com/path-1//path_2",
-      "script-src http://www.example.com" },
+      "script-src http://www.example.com/path-1//path_2" },
     { "default-src 127.0.0.1",
       "default-src http://127.0.0.1" },
     { "default-src 127.0.0.1:*",
       "default-src http://127.0.0.1:*" },
     { "default-src -; ",
       "default-src http://-" },
     { "script-src 1",
       "script-src http://1" }
@@ -614,31 +632,31 @@ nsresult TestGoodGeneratedPolicies() {
       "media-src https://self:34" },
     { "frame-src https://bar",
       "frame-src https://bar" },
     { "font-src http://three:81",
       "font-src http://three:81" },
     { "connect-src https://three:81",
       "connect-src https://three:81" },
     { "script-src http://self.com:80/foo",
-      "script-src http://self.com:80" },
+      "script-src http://self.com:80/foo" },
     { "object-src http://self.com/foo",
-      "object-src http://self.com" },
+      "object-src http://self.com/foo" },
     { "report-uri /report.py",
       "report-uri http://www.selfuri.com/report.py"},
     { "img-src http://foo.org:34/report.py",
-      "img-src http://foo.org:34" },
+      "img-src http://foo.org:34/report.py" },
     { "media-src foo/bar/report.py",
-      "media-src http://foo" },
+      "media-src http://foo/bar/report.py" },
     { "report-uri /",
       "report-uri http://www.selfuri.com/"},
     { "font-src https://self.com/report.py",
-      "font-src https://self.com" },
+      "font-src https://self.com/report.py" },
     { "connect-src https://foo.com/report.py",
-      "connect-src https://foo.com" },
+      "connect-src https://foo.com/report.py" },
     { "default-src *; report-uri  http://www.reporturi.com/",
       "default-src *; report-uri http://www.reporturi.com/" },
     { "default-src http://first.com",
       "default-src http://first.com" },
     { "script-src http://second.com",
       "script-src http://second.com" },
     { "object-src http://third.com",
       "object-src http://third.com" },
@@ -646,25 +664,25 @@ nsresult TestGoodGeneratedPolicies() {
       "style-src https://foobar.com:4443" },
     { "img-src http://foobar.com:4443",
       "img-src http://foobar.com:4443" },
     { "media-src bar.com",
       "media-src http://bar.com" },
     { "frame-src http://bar.com",
       "frame-src http://bar.com" },
     { "font-src http://self.com/",
-      "font-src http://self.com" },
+      "font-src http://self.com/" },
     { "script-src 'self'",
       "script-src http://www.selfuri.com" },
     { "default-src http://self.com/foo.png",
-      "default-src http://self.com" },
+      "default-src http://self.com/foo.png" },
     { "script-src http://self.com/foo.js",
-      "script-src http://self.com" },
+      "script-src http://self.com/foo.js" },
     { "object-src http://bar.com/foo.js",
-      "object-src http://bar.com" },
+      "object-src http://bar.com/foo.js" },
     { "style-src http://FOO.COM",
       "style-src http://foo.com" },
     { "img-src HTTP",
       "img-src http://http" },
     { "media-src http",
       "media-src http://http" },
     { "frame-src 'SELF'",
       "frame-src http://www.selfuri.com" },
@@ -692,21 +710,21 @@ nsresult TestGoodGeneratedPolicies() {
       "frame-ancestors http://a.b.c.d.e.f.g.h.i.j.k.l.x.com" },
     { "frame-ancestors https://self.com:34",
       "frame-ancestors https://self.com:34" },
     { "default-src 'none'; frame-ancestors 'self'",
       "default-src 'none'; frame-ancestors http://www.selfuri.com" },
     { "frame-ancestors http://self:80",
       "frame-ancestors http://self:80" },
     { "frame-ancestors http://self.com/bar",
-      "frame-ancestors http://self.com" },
+      "frame-ancestors http://self.com/bar" },
     { "default-src 'self'; frame-ancestors 'self'",
       "default-src http://www.selfuri.com; frame-ancestors http://www.selfuri.com" },
     { "frame-ancestors http://bar.com/foo.png",
-      "frame-ancestors http://bar.com" },
+      "frame-ancestors http://bar.com/foo.png" },
   };
 
   uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
   return runTestSuite(policies, policyCount, 1);
 }
 
 // ============================= TestBadGeneratedPolicies ========================
 
@@ -742,121 +760,121 @@ nsresult TestGoodGeneratedPoliciesForPat
   // Once bug 808292 (Implement path-level host-source matching to CSP)
   // lands we have to update the expected output to include the parsed path
 
   static const PolicyTest policies[] =
   {
     { "img-src http://test1.example.com",
       "img-src http://test1.example.com" },
     { "img-src http://test1.example.com/",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/" },
     { "img-src http://test1.example.com/path-1",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1" },
     { "img-src http://test1.example.com/path-1/",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/" },
     { "img-src http://test1.example.com/path-1/path_2/",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/path_2/" },
     { "img-src http://test1.example.com/path-1/path_2/file.js",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/path_2/file.js" },
     { "img-src http://test1.example.com/path-1/path_2/file_1.js",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/path_2/file_1.js" },
     { "img-src http://test1.example.com/path-1/path_2/file-2.js",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/path_2/file-2.js" },
     { "img-src http://test1.example.com/path-1/path_2/f.js",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/path_2/f.js" },
     { "img-src http://test1.example.com/path-1/path_2/f.oo.js",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/path_2/f.oo.js" },
     { "img-src test1.example.com",
       "img-src http://test1.example.com" },
     { "img-src test1.example.com/",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/" },
     { "img-src test1.example.com/path-1",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1" },
     { "img-src test1.example.com/path-1/",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/" },
     { "img-src test1.example.com/path-1/path_2/",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/path_2/" },
     { "img-src test1.example.com/path-1/path_2/file.js",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/path_2/file.js" },
     { "img-src test1.example.com/path-1/path_2/file_1.js",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/path_2/file_1.js" },
     { "img-src test1.example.com/path-1/path_2/file-2.js",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/path_2/file-2.js" },
     { "img-src test1.example.com/path-1/path_2/f.js",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/path_2/f.js" },
     { "img-src test1.example.com/path-1/path_2/f.oo.js",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/path-1/path_2/f.oo.js" },
     { "img-src *.example.com",
       "img-src http://*.example.com" },
     { "img-src *.example.com/",
-      "img-src http://*.example.com" },
+      "img-src http://*.example.com/" },
     { "img-src *.example.com/path-1",
-      "img-src http://*.example.com" },
+      "img-src http://*.example.com/path-1" },
     { "img-src *.example.com/path-1/",
-      "img-src http://*.example.com" },
+      "img-src http://*.example.com/path-1/" },
     { "img-src *.example.com/path-1/path_2/",
-      "img-src http://*.example.com" },
+      "img-src http://*.example.com/path-1/path_2/" },
     { "img-src *.example.com/path-1/path_2/file.js",
-      "img-src http://*.example.com" },
+      "img-src http://*.example.com/path-1/path_2/file.js" },
     { "img-src *.example.com/path-1/path_2/file_1.js",
-      "img-src http://*.example.com" },
+      "img-src http://*.example.com/path-1/path_2/file_1.js" },
     { "img-src *.example.com/path-1/path_2/file-2.js",
-      "img-src http://*.example.com" },
+      "img-src http://*.example.com/path-1/path_2/file-2.js" },
     { "img-src *.example.com/path-1/path_2/f.js",
-      "img-src http://*.example.com" },
+      "img-src http://*.example.com/path-1/path_2/f.js" },
     { "img-src *.example.com/path-1/path_2/f.oo.js",
-      "img-src http://*.example.com" },
+      "img-src http://*.example.com/path-1/path_2/f.oo.js" },
     { "img-src test1.example.com:80",
       "img-src http://test1.example.com:80" },
     { "img-src test1.example.com:80/",
-      "img-src http://test1.example.com:80" },
+      "img-src http://test1.example.com:80/" },
     { "img-src test1.example.com:80/path-1",
-      "img-src http://test1.example.com:80" },
+      "img-src http://test1.example.com:80/path-1" },
     { "img-src test1.example.com:80/path-1/",
-      "img-src http://test1.example.com:80" },
+      "img-src http://test1.example.com:80/path-1/" },
     { "img-src test1.example.com:80/path-1/path_2",
-      "img-src http://test1.example.com:80" },
+      "img-src http://test1.example.com:80/path-1/path_2" },
     { "img-src test1.example.com:80/path-1/path_2/",
-      "img-src http://test1.example.com:80" },
+      "img-src http://test1.example.com:80/path-1/path_2/" },
     { "img-src test1.example.com:80/path-1/path_2/file.js",
-      "img-src http://test1.example.com:80" },
+      "img-src http://test1.example.com:80/path-1/path_2/file.js" },
     { "img-src test1.example.com:80/path-1/path_2/f.ile.js",
-      "img-src http://test1.example.com:80" },
+      "img-src http://test1.example.com:80/path-1/path_2/f.ile.js" },
     { "img-src test1.example.com:*",
       "img-src http://test1.example.com:*" },
     { "img-src test1.example.com:*/",
-      "img-src http://test1.example.com:*" },
+      "img-src http://test1.example.com:*/" },
     { "img-src test1.example.com:*/path-1",
-      "img-src http://test1.example.com:*" },
+      "img-src http://test1.example.com:*/path-1" },
     { "img-src test1.example.com:*/path-1/",
-      "img-src http://test1.example.com:*" },
+      "img-src http://test1.example.com:*/path-1/" },
     { "img-src test1.example.com:*/path-1/path_2",
-      "img-src http://test1.example.com:*" },
+      "img-src http://test1.example.com:*/path-1/path_2" },
     { "img-src test1.example.com:*/path-1/path_2/",
-      "img-src http://test1.example.com:*" },
+      "img-src http://test1.example.com:*/path-1/path_2/" },
     { "img-src test1.example.com:*/path-1/path_2/file.js",
-      "img-src http://test1.example.com:*" },
+      "img-src http://test1.example.com:*/path-1/path_2/file.js" },
     { "img-src test1.example.com:*/path-1/path_2/f.ile.js",
-      "img-src http://test1.example.com:*" },
+      "img-src http://test1.example.com:*/path-1/path_2/f.ile.js" },
     { "img-src http://test1.example.com/abc//",
-      "img-src http://test1.example.com" },
+      "img-src http://test1.example.com/abc//" },
     { "img-src https://test1.example.com/abc/def//",
-      "img-src https://test1.example.com" },
+      "img-src https://test1.example.com/abc/def//" },
     { "img-src https://test1.example.com/abc/def/ghi//",
-      "img-src https://test1.example.com" },
+      "img-src https://test1.example.com/abc/def/ghi//" },
     { "img-src http://test1.example.com:80/abc//",
-      "img-src http://test1.example.com:80" },
+      "img-src http://test1.example.com:80/abc//" },
     { "img-src https://test1.example.com:80/abc/def//",
-      "img-src https://test1.example.com:80" },
+      "img-src https://test1.example.com:80/abc/def//" },
     { "img-src https://test1.example.com:80/abc/def/ghi//",
-      "img-src https://test1.example.com:80" },
+      "img-src https://test1.example.com:80/abc/def/ghi//" },
     { "img-src https://test1.example.com/abc////////////def/",
-      "img-src https://test1.example.com" },
+      "img-src https://test1.example.com/abc////////////def/" },
     { "img-src https://test1.example.com/abc////////////",
-      "img-src https://test1.example.com" },
+      "img-src https://test1.example.com/abc////////////" },
   };
 
   uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
   return runTestSuite(policies, policyCount, 1);
 }
 
 // ============ TestBadGeneratedPoliciesForPathHandling ============
 
--- a/content/base/test/chrome/cpows_parent.xul
+++ b/content/base/test/chrome/cpows_parent.xul
@@ -218,17 +218,17 @@
       let cpowLocation = Cu.getCompartmentLocation(getUnprivilegedObject);
       ok(/Privileged Junk/.test(cpowLocation),
          "parent->child CPOWs should live in the privileged junk scope: " + cpowLocation);
 
       // Make sure that parent->child CPOWs point through a privileged scope in the child
       // (the privileged junk scope, but we don't have a good way to test for that
       // specifically).
       is(unprivilegedObject.expando, undefined, "parent->child references should get Xrays");
-      todo_is(unprivilegedObject.wrappedJSObject.expando, 42, "parent->child references should get waivable Xrays - see bug 1065811");
+      is(unprivilegedObject.wrappedJSObject.expando, 42, "parent->child references should get waivable Xrays");
 
       // Send an object to the child to let it verify invariants in the other direction.
       function passMe() { return 42; };
       passMe.expando = 42;
       let results = testParentObject(passMe);
       ok(results.length > 0, "Need results");
       results.forEach((x) => is(x.result, "PASS", x.message));
     }
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_csp_path_matching.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Bug 808292 - Implement path-level host-source matching to CSP</title>
+  </head>
+  <body>
+  <div id="testdiv">blocked</div>
+  <script src="http://test1.example.com/tests/content/base/test/csp/file_csp_path_matching.js#foo"></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_csp_path_matching.js
@@ -0,0 +1,1 @@
+document.getElementById("testdiv").innerHTML = "allowed";
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_csp_path_matching_redirect.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Bug 808292 - Implement path-level host-source matching to CSP</title>
+  </head>
+  <body>
+  <div id="testdiv">blocked</div>
+  <script src="http://example.com/tests/content/base/test/csp/file_csp_path_matching_redirect_server.sjs"></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_csp_path_matching_redirect_server.sjs
@@ -0,0 +1,13 @@
+// Redirect server specifically to handle redirects
+// for path-level host-source matching
+// see https://bugzilla.mozilla.org/show_bug.cgi?id=808292
+
+function handleRequest(request, response)
+{
+
+  var newLocation = "http://test1.example.com/tests/content/base/test/csp/file_csp_path_matching.js";
+
+  response.setStatusLine("1.1", 302, "Found");
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Location", newLocation, false);
+}
deleted file mode 100644
--- a/content/base/test/csp/file_csp_regexp_parsing.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-  <head>
-    <title>Bug 916054 - URLs with path are ignored by FF's CSP parser</title>
-  </head>
-  <body>
-  <div id="testdiv">blocked</div>
-  <script src="http://test1.example.com/tests/content/base/test/csp/file_csp_regexp_parsing.js"></script>
-</body>
-</html>
deleted file mode 100644
--- a/content/base/test/csp/file_csp_regexp_parsing.js
+++ /dev/null
@@ -1,1 +0,0 @@
-document.getElementById("testdiv").innerHTML = "allowed";
--- a/content/base/test/csp/mochitest.ini
+++ b/content/base/test/csp/mochitest.ini
@@ -76,19 +76,21 @@ support-files =
   file_nonce_source.html^headers^
   file_CSP_bug941404.html
   file_CSP_bug941404_xhr.html
   file_CSP_bug941404_xhr.html^headers^
   file_hash_source.html
   file_hash_source.html^headers^
   file_self_none_as_hostname_confusion.html
   file_self_none_as_hostname_confusion.html^headers^
+  file_csp_path_matching.html
+  file_csp_path_matching.js
+  file_csp_path_matching_redirect.html
+  file_csp_path_matching_redirect_server.sjs
   file_csp_testserver.sjs
-  file_csp_regexp_parsing.html
-  file_csp_regexp_parsing.js
   file_report_uri_missing_in_report_only_header.html
   file_report_uri_missing_in_report_only_header.html^headers^
   file_csp_report.html
   file_redirect_content.sjs
   file_redirect_report.sjs
   file_subframe_run_js_if_allowed.html
   file_subframe_run_js_if_allowed.html^headers^
   file_leading_wildcard.html
@@ -116,17 +118,18 @@ skip-if = (buildapp == 'b2g' && (toolkit
 [test_CSP_bug909029.html]
 [test_policyuri_regression_from_multipolicy.html]
 [test_nonce_source.html]
 [test_CSP_bug941404.html]
 [test_hash_source.html]
 skip-if = e10s || buildapp == 'b2g' # can't compute hashes in child process (bug 958702)
 [test_self_none_as_hostname_confusion.html]
 [test_bug949549.html]
-[test_csp_regexp_parsing.html]
+[test_csp_path_matching.html]
+[test_csp_path_matching_redirect.html]
 [test_report_uri_missing_in_report_only_header.html]
 [test_csp_report.html]
 skip-if = e10s || buildapp == 'b2g' # http-on-opening-request observer not supported in child process (bug 1009632)
 [test_301_redirect.html]
 skip-if = buildapp == 'b2g' # intermittent orange (bug 1028490)
 [test_302_redirect.html]
 skip-if = buildapp == 'b2g' # intermittent orange (bug 1028490)
 [test_303_redirect.html]
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/test_csp_path_matching.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 808292 - Implement path-level host-source matching to CSP</title>
+  <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+  <p id="display"></p>
+  <div id="content" style="visibility: hidden">
+    <iframe style="width:100%;" id="testframe"></iframe>
+  </div>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/* Description of the test:
+ * We are loading the following url (including a fragment portion):
+ * http://test1.example.com/tests/content/base/test/csp/file_csp_path_matching.js#foo
+ * using different policies and verify that the applied policy is accurately enforced.
+ */
+
+var policies = [
+  ["allowed", "*"],
+  ["allowed", "test1.example.com"],
+  ["allowed", "test1.example.com/"],
+  ["allowed", "test1.example.com/tests/content/base/test/csp/"],
+  ["allowed", "test1.example.com/tests/content/base/test/csp/file_csp_path_matching.js"],
+
+  ["allowed", "test1.example.com?foo=val"],
+  ["allowed", "test1.example.com/?foo=val"],
+  ["allowed", "test1.example.com/tests/content/base/test/csp/?foo=val"],
+  ["allowed", "test1.example.com/tests/content/base/test/csp/file_csp_path_matching.js?foo=val"],
+
+  ["allowed", "test1.example.com#foo"],
+  ["allowed", "test1.example.com/#foo"],
+  ["allowed", "test1.example.com/tests/content/base/test/csp/#foo"],
+  ["allowed", "test1.example.com/tests/content/base/test/csp/file_csp_path_matching.js#foo"],
+
+  ["allowed", "*.example.com"],
+  ["allowed", "*.example.com/"],
+  ["allowed", "*.example.com/tests/content/base/test/csp/"],
+  ["allowed", "*.example.com/tests/content/base/test/csp/file_csp_path_matching.js"],
+
+  ["allowed", "test1.example.com:80"],
+  ["allowed", "test1.example.com:80/"],
+  ["allowed", "test1.example.com:80/tests/content/base/test/csp/"],
+  ["allowed", "test1.example.com:80/tests/content/base/test/csp/file_csp_path_matching.js"],
+
+  ["allowed", "test1.example.com:*"],
+  ["allowed", "test1.example.com:*/"],
+  ["allowed", "test1.example.com:*/tests/content/base/test/csp/"],
+  ["allowed", "test1.example.com:*/tests/content/base/test/csp/file_csp_path_matching.js"],
+
+  ["blocked", "test1.example.com/tests"],
+  ["blocked", "test1.example.com/tests/content/base/test/csp"],
+  ["blocked", "test1.example.com/tests/content/base/test/csp/file_csp_path_matching.py"],
+
+  ["blocked", "test1.example.com:8888/tests"],
+  ["blocked", "test1.example.com:8888/tests/content/base/test/csp"],
+  ["blocked", "test1.example.com:8888/tests/content/base/test/csp/file_csp_path_matching.py"],
+]
+
+var counter = 0;
+var policy;
+
+function loadNextTest() {
+  if (counter == policies.length) {
+    SimpleTest.finish();
+  }
+  else {
+    policy = policies[counter++];
+    var src = "file_csp_testserver.sjs";
+    // append the file that should be served
+    src += "?file=" + escape("tests/content/base/test/csp/file_csp_path_matching.html");
+    // append the CSP that should be used to serve the file
+    src += "&csp=" + escape("default-src 'none'; script-src " + policy[1]);
+
+    document.getElementById("testframe").addEventListener("load", test, false);
+    document.getElementById("testframe").src = src;
+  }
+}
+
+function test() {
+  try {
+    document.getElementById("testframe").removeEventListener('load', test, false);
+    var testframe = document.getElementById("testframe");
+    var divcontent = testframe.contentWindow.document.getElementById('testdiv').innerHTML;
+    is(divcontent, policy[0], "should be " + policy[0] + " in test " + (counter - 1) + "!");
+  }
+  catch (e) {
+    ok(false, "ERROR: could not access content in test " + (counter - 1) + "!");
+  }
+  loadNextTest();
+}
+
+loadNextTest();
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/test_csp_path_matching_redirect.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 808292 - Implement path-level host-source matching to CSP (redirects)</title>
+  <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+  <p id="display"></p>
+  <div id="content" style="visibility: hidden">
+    <iframe style="width:100%;" id="testframe"></iframe>
+  </div>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/* Description of the test:
+ * First, we try to load a script where the *path* does not match.
+ * Second, we try to load a script which is allowed by the CSPs
+ * script-src directive. The script then gets redirected to
+ * an URL where the host matches, but the path wouldn't.
+ * Since 'paths' should not be taken into account after redirects,
+ * that load should succeed. We are using a similar test setup
+ * as described in the spec, see:
+ * http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
+ */
+
+var policy = "script-src http://example.com http://test1.example.com/CSPAllowsScriptsInThatFolder";
+
+var tests = [
+  {
+    // the script in file_csp_path_matching.html
+    // <script src="http://test1.example.com/tests/content/base/..">
+    // is not within the whitelisted path by the csp-policy
+    // hence the script is 'blocked' by CSP.
+    expected: "blocked",
+    uri: "tests/content/base/test/csp/file_csp_path_matching.html"
+  },
+  {
+    // the script in file_csp_path_matching_redirect.html
+    // <script src="http://example.com/tests/content/..">
+    // gets redirected to: http://test1.example.com/tests/content
+    // where after the redirect the path of the policy is not enforced
+    // anymore and hence execution of the script is 'allowed'.
+    expected: "allowed",
+    uri: "tests/content/base/test/csp/file_csp_path_matching_redirect.html"
+  },
+];
+
+var counter = 0;
+var curTest;
+
+function checkResult() {
+  try {
+    document.getElementById("testframe").removeEventListener('load', checkResult, false);
+    var testframe = document.getElementById("testframe");
+    var divcontent = testframe.contentWindow.document.getElementById('testdiv').innerHTML;
+    is(divcontent, curTest.expected, "should be blocked in test " + (counter - 1) + "!");
+  }
+  catch (e) {
+    ok(false, "ERROR: could not access content in test " + (counter - 1) + "!");
+  }
+  loadNextTest();
+}
+
+function loadNextTest() {
+  if (counter == tests.length) {
+    SimpleTest.finish();
+  }
+  else {
+    curTest = tests[counter++];
+    var src = "file_csp_testserver.sjs";
+    // append the file that should be served
+    src += "?file=" + escape(curTest.uri);
+    // append the CSP that should be used to serve the file
+    src += "&csp=" + escape(policy);
+
+    document.getElementById("testframe").addEventListener("load", checkResult, false);
+    document.getElementById("testframe").src = src;
+  }
+}
+
+loadNextTest();
+
+</script>
+</body>
+</html>
deleted file mode 100644
--- a/content/base/test/csp/test_csp_regexp_parsing.html
+++ /dev/null
@@ -1,101 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Bug 916054 - URLs with path are ignored by FF's CSP parser</title>
-  <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-  <p id="display"></p>
-  <div id="content" style="visibility: hidden">
-    <iframe style="width:100%;" id="testframe"></iframe>
-  </div>
-
-<script class="testbody" type="text/javascript">
-
-SimpleTest.waitForExplicitFinish();
-
-var policies = [
-  ["allowed", "*"],
-  ["allowed", "test1.example.com"],
-  ["allowed", "test1.example.com/"],
-  ["allowed", "test1.example.com/path-1"],
-  ["allowed", "test1.example.com/path-1/"],
-  ["allowed", "test1.example.com/path-1/path_2/"],
-  ["allowed", "test1.example.com/path-1/path_2/file.js"],
-  ["allowed", "test1.example.com/path-1/path_2/file_1.js"],
-  ["allowed", "test1.example.com/path-1/path_2/file-2.js"],
-  ["allowed", "test1.example.com/path-1/path_2/f.js"],
-  ["allowed", "test1.example.com/path-1/path_2/f.oo.js"],
-  ["allowed", "*.example.com"],
-  ["allowed", "*.example.com/"],
-  ["allowed", "*.example.com/path-1"],
-  ["allowed", "*.example.com/path-1/"],
-  ["allowed", "*.example.com/path-1/path_2/"],
-  ["allowed", "*.example.com/path-1/path_2/file.js"],
-  ["allowed", "*.example.com/path-1/path_2/file_1.js"],
-  ["allowed", "*.example.com/path-1/path_2/file-2.js"],
-  ["allowed", "*.example.com/path-1/path_2/f.js"],
-  ["allowed", "*.example.com/path-1/path_2/f.oo.js"],
-  ["allowed", "test1.example.com:80"],
-  ["allowed", "test1.example.com:80/"],
-  ["allowed", "test1.example.com:80/path-1"],
-  ["allowed", "test1.example.com:80/path-1/"],
-  ["allowed", "test1.example.com:80/path-1/path_2"],
-  ["allowed", "test1.example.com:80/path-1/path_2/"],
-  ["allowed", "test1.example.com:80/path-1/path_2/file.js"],
-  ["allowed", "test1.example.com:80/path-1/path_2/f.ile.js"],
-  ["allowed", "test1.example.com:*"],
-  ["allowed", "test1.example.com:*/"],
-  ["allowed", "test1.example.com:*/path-1"],
-  ["allowed", "test1.example.com:*/path-1/"],
-  ["allowed", "test1.example.com:*/path-1/path_2"],
-  ["allowed", "test1.example.com:*/path-1/path_2/"],
-  ["allowed", "test1.example.com:*/path-1/path_2/file.js"],
-  ["allowed", "test1.example.com:*/path-1/path_2/f.ile.js"],
-  // the following tests should fail
-  ["blocked", "test1.example.com:88path-1/"],
-  ["blocked", "test1.example.com:80.js"],
-  ["blocked", "test1.example.com:*.js"],
-  ["blocked", "test1.example.com:*."]
-]
-
-var counter = 0;
-var policy;
-
-function loadNextTest() {
-  if (counter == policies.length) {
-    SimpleTest.finish();
-  }
-  else {
-    policy = policies[counter++];
-    var src = "file_csp_testserver.sjs";
-    // append the file that should be served
-    src += "?file=" + escape("tests/content/base/test/csp/file_csp_regexp_parsing.html");
-    // append the CSP that should be used to serve the file
-    src += "&csp=" + escape("default-src 'none'; script-src " + policy[1]);
-
-    document.getElementById("testframe").addEventListener("load", test, false);
-    document.getElementById("testframe").src = src;
-  }
-}
-
-function test() {
-  try {
-    document.getElementById("testframe").removeEventListener('load', test, false);
-    var testframe = document.getElementById("testframe");
-    var divcontent = testframe.contentWindow.document.getElementById('testdiv').innerHTML;
-    is(divcontent, policy[0], "should be " + policy[0] + " in test " + (counter - 1) + "!");
-  }
-  catch (e) {
-    ok(false, "ERROR: could not access content in test " + (counter - 1) + "!");
-  }
-  loadNextTest();
-}
-
-loadNextTest();
-
-</script>
-</body>
-</html>
--- a/content/base/test/test_bug461735.html
+++ b/content/base/test/test_bug461735.html
@@ -28,18 +28,17 @@ window.onerror = function(message, uri, 
 </script>
 <script src="bug461735-redirect1.sjs"></script>
 <script>
   is(errorFired, true, "Should have error in redirected different origin script");
   errorFired = false;
 </script>
 <script type="application/javascript">
 window.onerror = function(message, uri, line) {
-  ok(message.contains("ReferenceError: c is not defined"),
-     "Should have correct error message");
+  is(message, "ReferenceError: c is not defined", "Should have correct error message");
   is(uri,
      "http://mochi.test:8888/tests/content/base/test/bug461735-redirect2.sjs",
      "Unexpected error location URI");
   is(line, 3, "Should have a line here");
   errorFired = true;
 }
 </script>
 <script src="bug461735-redirect2.sjs"></script>
--- a/content/base/test/test_bug696301-1.html
+++ b/content/base/test/test_bug696301-1.html
@@ -33,18 +33,17 @@ window.onerror = function(message, uri, 
   is(errorFired, true, "Should have error in different origin script");
   is(global, "ran", "Different origin script should have run");
 </script>
 
 <script type="application/javascript">
 errorFired = false;
 global = "";
 window.onerror = function(message, uri, line) {
-  ok(message.contains("ReferenceError: c is not defined"),
-     "Should have correct error message");
+  is(message, "ReferenceError: c is not defined", "Should have correct error message");
   is(uri,
      "http://example.com/tests/content/base/test/bug696301-script-1.js",
      "Should also have correct script URI");
   is(line, 3, "Should have a line here");
   errorFired = true;
 }
 </script>
 <script src="http://example.com/tests/content/base/test/bug696301-script-1.js"
--- a/content/base/test/test_bug696301-2.html
+++ b/content/base/test/test_bug696301-2.html
@@ -35,18 +35,17 @@ window.onerror = function(message, uri, 
   is(errorFired, true, "Should have error in different origin script");
   is(global, "ran", "Different origin script should have run");
 </script>
 
 <script type="application/javascript">
 errorFired = false;
 global = "";
 window.onerror = function(message, uri, line) {
-  ok(message.contains("ReferenceError: c is not defined"),
-     "Should have correct error message");
+  is(message, "ReferenceError: c is not defined", "Should have correct error message");
   is(uri,
      "http://example.com/tests/content/base/test/bug696301-script-1.js",
      "Should also have correct script URI");
   is(line, 3, "Should have a line here");
   errorFired = true;
 }
 </script>
 <script xlink:href="http://example.com/tests/content/base/test/bug696301-script-1.js"
--- a/content/media/fmp4/moz.build
+++ b/content/media/fmp4/moz.build
@@ -7,21 +7,21 @@
 EXPORTS += [
     'MP4Decoder.h',
     'MP4Reader.h',
     'PlatformDecoderModule.h',
 ]
 
 UNIFIED_SOURCES += [
     'BlankDecoderModule.cpp',
+    'MP4Decoder.cpp',
     'PlatformDecoderModule.cpp',
 ]
 
 SOURCES += [
-    'MP4Decoder.cpp',
     'MP4Reader.cpp',
 ]
 
 if CONFIG['MOZ_WMF']:
     DIRS += [ 'wmf' ];
 
 if CONFIG['MOZ_EME']:
     DIRS += ['eme']
--- a/content/media/omx/AudioOffloadPlayer.cpp
+++ b/content/media/omx/AudioOffloadPlayer.cpp
@@ -17,16 +17,18 @@
  * limitations under the License.
  */
 
 #include "AudioOffloadPlayer.h"
 #include "nsComponentManagerUtils.h"
 #include "nsITimer.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "VideoUtils.h"
+#include "mozilla/dom/power/PowerManagerService.h"
+#include "mozilla/dom/WakeLock.h"
 
 #include <binder/IPCThreadState.h>
 #include <stagefright/foundation/ADebug.h>
 #include <stagefright/foundation/ALooper.h>
 #include <stagefright/MediaDefs.h>
 #include <stagefright/MediaErrors.h>
 #include <stagefright/MediaSource.h>
 #include <stagefright/MetaData.h>
@@ -210,28 +212,31 @@ status_t AudioOffloadPlayer::ChangeState
     default:
       break;
   }
   return OK;
 }
 
 static void ResetCallback(nsITimer* aTimer, void* aClosure)
 {
+  AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("%s", __FUNCTION__));
   AudioOffloadPlayer* player = static_cast<AudioOffloadPlayer*>(aClosure);
   if (player) {
     player->Reset();
   }
 }
 
 void AudioOffloadPlayer::Pause(bool aPlayPendingSamples)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mStarted) {
     CHECK(mAudioSink.get());
+    WakeLockCreate();
+
     if (aPlayPendingSamples) {
       mAudioSink->Stop();
     } else {
       mAudioSink->Pause();
     }
     mPlaying = false;
   }
 
@@ -247,16 +252,17 @@ void AudioOffloadPlayer::Pause(bool aPla
 
 status_t AudioOffloadPlayer::Play()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mResetTimer) {
     mResetTimer->Cancel();
     mResetTimer = nullptr;
+    WakeLockRelease();
   }
 
   status_t err = OK;
 
   if (!mStarted) {
     // Last pause timed out and offloaded audio sink was reset. Start it again
     err = Start(false);
     if (err != OK) {
@@ -276,16 +282,17 @@ status_t AudioOffloadPlayer::Play()
     }
   }
 
   return err;
 }
 
 void AudioOffloadPlayer::Reset()
 {
+  MOZ_ASSERT(NS_IsMainThread());
   if (!mStarted) {
     return;
   }
 
   CHECK(mAudioSink.get());
 
   AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("reset: mPlaying=%d mReachedEOS=%d",
       mPlaying, mReachedEOS));
@@ -318,16 +325,18 @@ void AudioOffloadPlayer::Reset()
 
   IPCThreadState::self()->flushCommands();
   StopTimeUpdate();
 
   mReachedEOS = false;
   mStarted = false;
   mPlaying = false;
   mStartPosUs = 0;
+
+  WakeLockRelease();
 }
 
 status_t AudioOffloadPlayer::SeekTo(int64_t aTimeUs, bool aDispatchSeekEvents)
 {
   MOZ_ASSERT(NS_IsMainThread());
   CHECK(mAudioSink.get());
 
   android::Mutex::Autolock autoLock(mLock);
@@ -706,9 +715,35 @@ void AudioOffloadPlayer::SendMetaDataToH
 
 void AudioOffloadPlayer::SetVolume(double aVolume)
 {
   MOZ_ASSERT(NS_IsMainThread());
   CHECK(mAudioSink.get());
   mAudioSink->SetVolume((float) aVolume);
 }
 
+void AudioOffloadPlayer::WakeLockCreate()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("%s", __FUNCTION__));
+  if (!mWakeLock) {
+    nsRefPtr<dom::power::PowerManagerService> pmService =
+      dom::power::PowerManagerService::GetInstance();
+    NS_ENSURE_TRUE_VOID(pmService);
+
+    ErrorResult rv;
+    mWakeLock = pmService->NewWakeLock(NS_LITERAL_STRING("cpu"), nullptr, rv);
+  }
+}
+
+void AudioOffloadPlayer::WakeLockRelease()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("%s", __FUNCTION__));
+  if (mWakeLock) {
+    ErrorResult rv;
+    mWakeLock->Unlock(rv);
+    NS_WARN_IF_FALSE(!rv.Failed(), "Failed to unlock the wakelock.");
+    mWakeLock = nullptr;
+  }
+}
+
 } // namespace mozilla
--- a/content/media/omx/AudioOffloadPlayer.h
+++ b/content/media/omx/AudioOffloadPlayer.h
@@ -28,16 +28,20 @@
 
 #include "AudioOutput.h"
 #include "AudioOffloadPlayerBase.h"
 #include "MediaDecoderOwner.h"
 #include "MediaOmxCommonDecoder.h"
 
 namespace mozilla {
 
+namespace dom {
+class WakeLock;
+}
+
 /**
  * AudioOffloadPlayer adds support for audio tunneling to a digital signal
  * processor (DSP) in the device chipset. With tunneling, audio decoding is
  * off-loaded to the DSP, waking the application processor less often and using
  * less battery
  *
  * This depends on offloading capability provided by Android KK AudioTrack class
  *
@@ -188,16 +192,20 @@ private:
   // Timer to trigger position changed events
   nsCOMPtr<nsITimer> mTimeUpdateTimer;
 
   // Timer to reset AudioSink when audio is paused for OFFLOAD_PAUSE_MAX_USECS.
   // It is triggered in Pause() and canceled when there is a Play() within
   // OFFLOAD_PAUSE_MAX_USECS. Used only from main thread so no lock is needed.
   nsCOMPtr<nsITimer> mResetTimer;
 
+  // To avoid device suspend when mResetTimer is going to be triggered.
+  // Used only from main thread so no lock is needed.
+  nsRefPtr<mozilla::dom::WakeLock> mWakeLock;
+
   int64_t GetMediaTimeUs();
 
   // Provide the playback position in microseconds from total number of
   // frames played by audio track
   int64_t GetOutputPlayPositionUs_l() const;
 
   // Fill the buffer given by audio sink with data from compressed audio
   // source. Also handles the seek by seeking audio source and stop the sink in
@@ -235,16 +243,19 @@ private:
   // (>1sec) But when Player UI is visible we need to update progress bar
   // atleast once in 250ms. Start a timer when player UI becomes visible or
   // audio starts playing to send PlaybackPositionChanged events once in 250ms.
   // Stop the timer when UI goes invisible or play state is not playing.
   // Also make sure timer functions are always called from main thread
   nsresult StartTimeUpdate();
   nsresult StopTimeUpdate();
 
+  void WakeLockCreate();
+  void WakeLockRelease();
+
   // Notify end of stream by sending PlaybackEnded event to observer
   // (i.e.MediaDecoder)
   void NotifyAudioEOS();
 
   // Notify position changed event by sending PlaybackPositionChanged event to
   // observer
   void NotifyPositionChanged();
 
--- a/content/media/test/mochitest.ini
+++ b/content/media/test/mochitest.ini
@@ -443,16 +443,17 @@ skip-if = true # bug 1021673
 [test_seekLies.html]
 [test_source.html]
 [test_source_media.html]
 [test_source_null.html]
 [test_source_write.html]
 [test_standalone.html]
 [test_streams_autoplay.html]
 [test_streams_element_capture.html]
+skip-if = e10s && os == 'win' # Bug 1065881 - Crash on child process shutdown in ShadowLayerForwarder::InWorkerThread
 [test_streams_element_capture_createObjectURL.html]
 [test_streams_element_capture_playback.html]
 [test_streams_element_capture_reset.html]
 skip-if = buildapp == 'b2g' # bug 901102
 [test_streams_gc.html]
 skip-if = buildapp == 'b2g' # bug 1021682
 [test_streams_srcObject.html]
 [test_streams_tracks.html]
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3051,39 +3051,39 @@ nsDocShell::AddWeakScrollObserver(nsIScr
 NS_IMETHODIMP
 nsDocShell::RemoveWeakScrollObserver(nsIScrollObserver* aObserver)
 {
     nsWeakPtr obs = do_GetWeakReference(aObserver);
     return mScrollObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 void
-nsDocShell::NotifyAsyncPanZoomStarted()
+nsDocShell::NotifyAsyncPanZoomStarted(const mozilla::CSSIntPoint aScrollPos)
 {
     nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
     while (iter.HasMore()) {
         nsWeakPtr ref = iter.GetNext();
         nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
         if (obs) {
-            obs->AsyncPanZoomStarted();
+            obs->AsyncPanZoomStarted(aScrollPos);
         } else {
             mScrollObservers.RemoveElement(ref);
         }
     }
 }
 
 void
-nsDocShell::NotifyAsyncPanZoomStopped()
+nsDocShell::NotifyAsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos)
 {
     nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
     while (iter.HasMore()) {
         nsWeakPtr ref = iter.GetNext();
         nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
         if (obs) {
-            obs->AsyncPanZoomStopped();
+            obs->AsyncPanZoomStopped(aScrollPos);
         } else {
             mScrollObservers.RemoveElement(ref);
         }
     }
 }
 
 NS_IMETHODIMP
 nsDocShell::NotifyScrollObservers()
@@ -9833,17 +9833,28 @@ nsDocShell::InternalLoad(nsIURI * aURI,
 
             // Inform the favicon service that the favicon for oldURI also
             // applies to aURI.
             CopyFavicon(currentURI, aURI, mInPrivateBrowsing);
 
             return NS_OK;
         }
     }
-    
+
+    // Check if the webbrowser chrome wants the load to proceed; this can be
+    // used to cancel attempts to load URIs in the wrong process.
+    nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
+    if (browserChrome3) {
+        bool shouldLoad;
+        rv = browserChrome3->ShouldLoadURI(this, aURI, aReferrer, &shouldLoad);
+        if (NS_SUCCEEDED(rv) && !shouldLoad) {
+            return NS_OK;
+        }
+    }
+
     // mContentViewer->PermitUnload can destroy |this| docShell, which
     // causes the next call of CanSavePresentation to crash. 
     // Hold onto |this| until we return, to prevent a crash from happening. 
     // (bug#331040)
     nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
 
     // Don't init timing for javascript:, since it generally doesn't
     // actually start a load or anything.  If it does, we'll init
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -44,16 +44,17 @@
 #include "nsILoadContext.h"
 #include "nsIWebShellServices.h"
 #include "nsILinkHandler.h"
 #include "nsIClipboardCommands.h"
 #include "nsITabParent.h"
 #include "nsCRT.h"
 #include "prtime.h"
 #include "nsRect.h"
+#include "Units.h"
 
 namespace mozilla {
 namespace dom {
 class EventTarget;
 class URLSearchParams;
 }
 }
 
@@ -245,20 +246,20 @@ public:
         FireOnLocationChange(this, nullptr, mCurrentURI,
                              LOCATION_CHANGE_SAME_DOCUMENT);
     }
 
     nsresult HistoryTransactionRemoved(int32_t aIndex);
 
     // Notify Scroll observers when an async panning/zooming transform
     // has started being applied
-    void NotifyAsyncPanZoomStarted();
+    void NotifyAsyncPanZoomStarted(const mozilla::CSSIntPoint aScrollPos);
     // Notify Scroll observers when an async panning/zooming transform
     // is no longer applied
-    void NotifyAsyncPanZoomStopped();
+    void NotifyAsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos);
 
     // Add new profile timeline markers to this docShell. This will only add
     // markers if the docShell is currently recording profile timeline markers.
     // See nsIDocShell::recordProfileTimelineMarkers
     void AddProfileTimelineMarker(const char* aName,
                                   TracingMetadata aMetaData);
     void AddProfileTimelineMarker(const char* aName,
                                   ProfilerBacktrace* aCause,
--- a/docshell/base/nsIScrollObserver.h
+++ b/docshell/base/nsIScrollObserver.h
@@ -2,37 +2,40 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsIScrollObserver_h___
 #define nsIScrollObserver_h___
 
 #include "nsISupports.h"
+#include "Units.h"
 
 #define NS_ISCROLLOBSERVER_IID \
-  { 0x03465b77, 0x9ce2, 0x4d19, \
-    { 0xb2, 0xf6, 0x82, 0xae, 0xee, 0x85, 0xc3, 0xbf } }
+  { 0x00bc10e3, 0xaa59, 0x4aa3, \
+    { 0x88, 0xe9, 0x43, 0x0a, 0x01, 0xa3, 0x88, 0x04 } }
 
 class nsIScrollObserver : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCROLLOBSERVER_IID)
 
   /**
    * Called when the scroll position of some element has changed.
    */
   virtual void ScrollPositionChanged() = 0;
 
   /**
-   * Called when an async panning/zooming transform has started being applied.
+   * Called when an async panning/zooming transform has started being applied
+   * and passed the scroll offset
    */
-  virtual void AsyncPanZoomStarted(){};
+  virtual void AsyncPanZoomStarted(const mozilla::CSSIntPoint scrollPos){};
 
   /**
-   * Called when an async panning/zooming transform is no longer applied.
+   * Called when an async panning/zooming transform is no longer applied
+   * and passed the scroll offset
    */
-  virtual void AsyncPanZoomStopped(){};
+  virtual void AsyncPanZoomStopped(const mozilla::CSSIntPoint scrollPos){};
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIScrollObserver, NS_ISCROLLOBSERVER_IID)
 
 #endif /* nsIScrollObserver_h___ */
--- a/dom/activities/ActivitiesService.jsm
+++ b/dom/activities/ActivitiesService.jsm
@@ -217,16 +217,24 @@ let Activities = {
     * - finds a list of matching activities.
     * - calls the UI glue to get the user choice.
     * - fire an system message of type "activity" to this app, sending the
     *   activity data as a payload.
     */
   startActivity: function activities_startActivity(aMsg) {
     debug("StartActivity: " + JSON.stringify(aMsg));
 
+    // The caller app will be killed by |assertAppHasStatus| if it doesn't
+    // fit our permission requirement.
+    let callerApp = this.callers[aMsg.id].mm;
+    if (aMsg.options.name === 'internal-system-engineering-mode' &&
+        !callerApp.assertAppHasStatus(Ci.nsIPrincipal.APP_STATUS_CERTIFIED)) {
+      return;
+    }
+
     let self = this;
     let successCb = function successCb(aResults) {
       debug(JSON.stringify(aResults));
 
       function getActivityChoice(aResultType, aResult) {
         switch(aResultType) {
           case Ci.nsIActivityUIGlueCallback.NATIVE_ACTIVITY: {
             self.callers[aMsg.id].mm.sendAsyncMessage("Activity:FireSuccess", {
@@ -336,16 +344,22 @@ let Activities = {
     };
 
     let errorCb = function errorCb(aError) {
       // Something unexpected happened. Should we send an error back?
       debug("Error in startActivity: " + aError + "\n");
     };
 
     let matchFunc = function matchFunc(aResult) {
+      let calleeApp = DOMApplicationRegistry.getAppByManifestURL(aResult.manifest);
+      // Only allow certified apps to handle this special activity
+      if (aMsg.options.name === 'internal-system-engineering-mode' &&
+          calleeApp.appStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
+        return false;
+      }
       return ActivitiesServiceFilter.match(aMsg.options.data,
                                            aResult.description.filters);
     };
 
     this.db.find(aMsg, successCb, errorCb, matchFunc);
   },
 
   trySendAndCleanup: function activities_trySendAndCleanup(aId, aName, aPayload) {
--- a/dom/activities/ActivityProxy.js
+++ b/dom/activities/ActivityProxy.js
@@ -51,17 +51,25 @@ ActivityProxy.prototype = {
     let manifestURL = (appId != Ci.nsIScriptSecurityManager.NO_APP_ID &&
                        appId != Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID)
                         ? DOMApplicationRegistry.getManifestURLByLocalId(appId)
                         : null;
 
     // Only let certified apps enumerate providers for this filter.
     if (aOptions.getFilterResults === true &&
         principal.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
-      Services.DOMRequest.fireError(this.activity, "SecurityError");
+      Services.DOMRequest.fireErrorAsync(this.activity, "SecurityError");
+      Services.obs.notifyObservers(null, "Activity:Error", null);
+      return;
+    }
+
+    // Only let certified app to initiate this activitiy.
+    if (aOptions.name === 'internal-system-engineering-mode' &&
+        principal.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
+      Services.DOMRequest.fireErrorAsync(this.activity, "SecurityError");
       Services.obs.notifyObservers(null, "Activity:Error", null);
       return;
     }
 
     cpmm.addMessageListener("Activity:FireSuccess", this);
     cpmm.addMessageListener("Activity:FireError", this);
 
     cpmm.sendAsyncMessage("Activity:Start",
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -109,22 +109,18 @@ Animation::GetComputedTimingAt(const Nul
   const TimeDuration& localTime = aLocalTime.Value();
 
   // When we finish exactly at the end of an iteration we need to report
   // the end of the final iteration and not the start of the next iteration
   // so we set up a flag for that case.
   bool isEndOfFinalIteration = false;
 
   // Get the normalized time within the active interval.
-  TimeDuration activeTime;
-  // FIXME: The following check that the active duration is not equal to Forever
-  // is a temporary workaround to avoid overflow and should be removed once
-  // bug 1039924 is fixed.
-  if (result.mActiveDuration != TimeDuration::Forever() &&
-      localTime >= aTiming.mDelay + result.mActiveDuration) {
+  StickyTimeDuration activeTime;
+  if (localTime >= aTiming.mDelay + result.mActiveDuration) {
     result.mPhase = ComputedTiming::AnimationPhase_After;
     if (!aTiming.FillsForwards()) {
       // The animation isn't active or filling at this time.
       result.mTimeFraction = ComputedTiming::kNullTimeFraction;
       return result;
     }
     activeTime = result.mActiveDuration;
     // Note that infinity == floor(infinity) so this will also be true when we
@@ -143,20 +139,20 @@ Animation::GetComputedTimingAt(const Nul
   } else {
     MOZ_ASSERT(result.mActiveDuration != zeroDuration,
                "How can we be in the middle of a zero-duration interval?");
     result.mPhase = ComputedTiming::AnimationPhase_Active;
     activeTime = localTime - aTiming.mDelay;
   }
 
   // Get the position within the current iteration.
-  TimeDuration iterationTime;
+  StickyTimeDuration iterationTime;
   if (aTiming.mIterationDuration != zeroDuration) {
     iterationTime = isEndOfFinalIteration
-                    ? aTiming.mIterationDuration
+                    ? StickyTimeDuration(aTiming.mIterationDuration)
                     : activeTime % aTiming.mIterationDuration;
   } /* else, iterationTime is zero */
 
   // Determine the 0-based index of the current iteration.
   if (isEndOfFinalIteration) {
     result.mCurrentIteration =
       aTiming.mIterationCount == NS_IEEEPositiveInfinity()
       ? UINT64_MAX // FIXME: When we return this via the API we'll need
@@ -210,29 +206,30 @@ Animation::GetComputedTimingAt(const Nul
   }
   if (thisIterationReverse) {
     result.mTimeFraction = 1.0 - result.mTimeFraction;
   }
 
   return result;
 }
 
-TimeDuration
+StickyTimeDuration
 Animation::ActiveDuration(const AnimationTiming& aTiming)
 {
   if (aTiming.mIterationCount == mozilla::PositiveInfinity<float>()) {
     // An animation that repeats forever has an infinite active duration
     // unless its iteration duration is zero, in which case it has a zero
     // active duration.
-    const TimeDuration zeroDuration;
+    const StickyTimeDuration zeroDuration;
     return aTiming.mIterationDuration == zeroDuration
            ? zeroDuration
-           : TimeDuration::Forever();
+           : StickyTimeDuration::Forever();
   }
-  return aTiming.mIterationDuration.MultDouble(aTiming.mIterationCount);
+  return StickyTimeDuration(
+    aTiming.mIterationDuration.MultDouble(aTiming.mIterationCount));
 }
 
 bool
 Animation::HasAnimationOfProperty(nsCSSProperty aProperty) const
 {
   for (size_t propIdx = 0, propEnd = mProperties.Length();
        propIdx != propEnd; ++propIdx) {
     if (aProperty == mProperties[propIdx].mProperty) {
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -6,16 +6,17 @@
 #ifndef mozilla_dom_Animation_h
 #define mozilla_dom_Animation_h
 
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIDocument.h"
 #include "nsWrapperCache.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/StickyTimeDuration.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/Nullable.h"
 #include "nsSMILKeySpline.h"
 #include "nsStyleStruct.h" // for nsTimingFunction
 
 struct JSContext;
 
@@ -56,18 +57,19 @@ struct ComputedTiming
     : mTimeFraction(kNullTimeFraction)
     , mCurrentIteration(0)
     , mPhase(AnimationPhase_Null)
   { }
 
   static const double kNullTimeFraction;
 
   // The total duration of the animation including all iterations.
-  // Will equal TimeDuration::Forever() if the animation repeats indefinitely.
-  TimeDuration mActiveDuration;
+  // Will equal StickyTimeDuration::Forever() if the animation repeats
+  // indefinitely.
+  StickyTimeDuration mActiveDuration;
 
   // Will be kNullTimeFraction if the animation is neither animating nor
   // filling at the sampled time.
   double mTimeFraction;
 
   // Zero-based iteration index (meaningless if mTimeFraction is
   // kNullTimeFraction).
   uint64_t mCurrentIteration;
@@ -200,17 +202,18 @@ public:
   // Shortcut for that gets the computed timing using the current local time as
   // calculated from the timeline time.
   ComputedTiming GetComputedTiming(const AnimationTiming* aTiming
                                      = nullptr) const {
     return GetComputedTimingAt(GetLocalTime(), aTiming ? *aTiming : mTiming);
   }
 
   // Return the duration of the active interval for the given timing parameters.
-  static TimeDuration ActiveDuration(const AnimationTiming& aTiming);
+  static StickyTimeDuration
+  ActiveDuration(const AnimationTiming& aTiming);
 
   // After transitions finish they need to be retained for one throttle-able
   // cycle (for reasons see explanation in
   // layout/style/nsTransitionManager.cpp).
   // In the meantime, however, they should be ignored.
   bool IsFinishedTransition() const {
     return mIsFinishedTransition;
   }
--- a/dom/animation/AnimationUtils.h
+++ b/dom/animation/AnimationUtils.h
@@ -1,13 +1,16 @@
 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#ifndef mozilla_dom_AnimationUtils_h
+#define mozilla_dom_AnimationUtils_h
+
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/Nullable.h"
 
 namespace mozilla {
 namespace dom {
 
 class AnimationUtils
 {
@@ -22,8 +25,10 @@ public:
     }
 
     return result;
   }
 };
 
 } // namespace dom
 } // namespace mozilla
+
+#endif
--- a/dom/animation/moz.build
+++ b/dom/animation/moz.build
@@ -9,17 +9,17 @@ MOCHITEST_CHROME_MANIFESTS += ['test/chr
 
 EXPORTS.mozilla.dom += [
     'Animation.h',
     'AnimationEffect.h',
     'AnimationPlayer.h',
     'AnimationTimeline.h',
 ]
 
-SOURCES += [
+UNIFIED_SOURCES += [
     'Animation.cpp',
     'AnimationEffect.cpp',
     'AnimationPlayer.cpp',
     'AnimationTimeline.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
--- a/dom/apps/PermissionsTable.jsm
+++ b/dom/apps/PermissionsTable.jsm
@@ -482,16 +482,22 @@ this.PermissionsTable =  { geolocation: 
                            },
                            "settings:wallpaper.image": {
                              app: DENY_ACTION,
                              trusted: DENY_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read", "write"],
                              additional: ["settings-api"]
+                           },
+                           "engineering-mode": {
+                             app: DENY_ACTION,
+                             trusted: DENY_ACTION,
+                             privileged: DENY_ACTION,
+                             certified: ALLOW_ACTION
                            }
                          };
 
 /**
  * Append access modes to the permission name as suffixes.
  *   e.g. permission name 'contacts' with ['read', 'write'] =
  *   ['contacts-read', contacts-write']
  * @param string aPermName
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -15,16 +15,17 @@
 
 #include "mozilla/dom/ContentParent.h"
 
 #include "nsThreadUtils.h"
 #include "nsHashPropertyBag.h"
 #include "nsComponentManagerUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsJSUtils.h"
 #include "nsIAudioManager.h"
 #include "SpeakerManagerService.h"
 #define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1"
 #endif
 
@@ -796,58 +797,43 @@ AudioChannelService::Observe(nsISupports
     } else {
       NS_WARNING("ipc:content-shutdown message without childID property");
     }
   }
 #ifdef MOZ_WIDGET_GONK
   // To process the volume control on each audio channel according to
   // change of settings
   else if (!strcmp(aTopic, "mozsettings-changed")) {
-    AutoSafeJSContext cx;
-    nsDependentString dataStr(aData);
-    JS::Rooted<JS::Value> val(cx);
-    if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) ||
-        !val.isObject()) {
-      return NS_OK;
-    }
-
-    JS::Rooted<JSObject*> obj(cx, &val.toObject());
-    JS::Rooted<JS::Value> key(cx);
-    if (!JS_GetProperty(cx, obj, "key", &key) ||
-        !key.isString()) {
+    AutoJSAPI jsapi;
+    jsapi.Init();
+    JSContext* cx = jsapi.cx();
+    RootedDictionary<SettingChangeNotification> setting(cx);
+    if (!WrappedJSToDictionary(cx, aSubject, setting)) {
       return NS_OK;
     }
-
-    JS::Rooted<JSString*> jsKey(cx, JS::ToString(cx, key));
-    if (!jsKey) {
+    if (!StringBeginsWith(setting.mKey, NS_LITERAL_STRING("audio.volume."))) {
       return NS_OK;
     }
-    nsAutoJSString keyStr;
-    if (!keyStr.init(cx, jsKey) || keyStr.Find("audio.volume.", 0, false)) {
+    if (!setting.mValue.isNumber()) {
       return NS_OK;
     }
-
-    JS::Rooted<JS::Value> value(cx);
-    if (!JS_GetProperty(cx, obj, "value", &value) || !value.isInt32()) {
-      return NS_OK;
-    }
-
+    
     nsCOMPtr<nsIAudioManager> audioManager = do_GetService(NS_AUDIOMANAGER_CONTRACTID);
     NS_ENSURE_TRUE(audioManager, NS_OK);
 
-    int32_t index = value.toInt32();
-    if (keyStr.EqualsLiteral("audio.volume.content")) {
+    int32_t index = setting.mValue.toNumber();
+    if (setting.mKey.EqualsLiteral("audio.volume.content")) {
       audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Content, index);
-    } else if (keyStr.EqualsLiteral("audio.volume.notification")) {
+    } else if (setting.mKey.EqualsLiteral("audio.volume.notification")) {
       audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Notification, index);
-    } else if (keyStr.EqualsLiteral("audio.volume.alarm")) {
+    } else if (setting.mKey.EqualsLiteral("audio.volume.alarm")) {
       audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Alarm, index);
-    } else if (keyStr.EqualsLiteral("audio.volume.telephony")) {
+    } else if (setting.mKey.EqualsLiteral("audio.volume.telephony")) {
       audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Telephony, index);
-    } else if (!keyStr.EqualsLiteral("audio.volume.bt_sco")) {
+    } else if (!setting.mKey.EqualsLiteral("audio.volume.bt_sco")) {
       // bt_sco is not a valid audio channel so we manipulate it in
       // AudioManager.cpp. And the others should not be used.
       // We didn't use MOZ_CRASH or MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE here
       // because any web content who has permission of mozSettings can set any
       // names then it can be easy to crash the B2G.
       NS_WARNING("unexpected audio channel for volume control");
     }
   }
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -1530,17 +1530,17 @@ BaseStubConstructor(nsIWeakReference* aW
 
       JS::Rooted<JS::Value> funval(cx);
       if (!JS_GetProperty(cx, thisObject, "constructor", &funval) ||
           !funval.isObject()) {
         return NS_ERROR_UNEXPECTED;
       }
 
       // Check if the object is even callable.
-      NS_ENSURE_STATE(JS_ObjectIsCallable(cx, &funval.toObject()));
+      NS_ENSURE_STATE(JS::IsCallable(&funval.toObject()));
       {
         // wrap parameters in the target compartment
         // we also pass in the calling window as the first argument
         unsigned argc = args.length() + 1;
         JS::AutoValueVector argv(cx);
         if (!argv.resize(argc)) {
           return NS_ERROR_OUT_OF_MEMORY;
         }
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -5269,17 +5269,17 @@ nsGlobalWindow::RequestAnimationFrame(co
   return handle;
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::RequestAnimationFrame(JS::Handle<JS::Value> aCallback,
                                       JSContext* cx,
                                       int32_t* aHandle)
 {
-  if (!aCallback.isObject() || !JS_ObjectIsCallable(cx, &aCallback.toObject())) {
+  if (!aCallback.isObject() || !JS::IsCallable(&aCallback.toObject())) {
     return NS_ERROR_INVALID_ARG;
   }
 
   JS::Rooted<JSObject*> callbackObj(cx, &aCallback.toObject());
   nsRefPtr<FrameRequestCallback> callback =
     new FrameRequestCallback(callbackObj, GetIncumbentGlobal());
 
   ErrorResult rv;
--- a/dom/base/nsPerformance.cpp
+++ b/dom/base/nsPerformance.cpp
@@ -36,16 +36,21 @@ nsPerformanceTiming::nsPerformanceTiming
     mChannel(aChannel),
     mFetchStart(0.0),
     mZeroTime(aZeroTime),
     mTimingAllowed(true),
     mReportCrossOriginRedirect(true)
 {
   MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
   SetIsDOMBinding();
+
+  if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+    mZeroTime = 0;
+  }
+
   // The aHttpChannel argument is null if this nsPerformanceTiming object
   // is being used for the navigation timing (document) and has a non-null
   // value for the resource timing (any resources within the page).
   if (aHttpChannel) {
     mTimingAllowed = CheckAllowedOrigin(aHttpChannel);
     bool redirectsPassCheck = false;
     mChannel->GetAllRedirectsPassTimingAllowCheck(&redirectsPassCheck);
     mReportCrossOriginRedirect = mTimingAllowed && redirectsPassCheck;
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1694,17 +1694,17 @@ NativeToString(JSContext* cx, JS::Handle
   JS::Rooted<JSString*> str(cx);
   {
     JSAutoCompartment ac(cx, obj);
     if (toStringDesc.object()) {
       JS::Rooted<JS::Value> toString(cx, toStringDesc.value());
       if (!JS_WrapValue(cx, &toString)) {
         return false;
       }
-      MOZ_ASSERT(JS_ObjectIsCallable(cx, &toString.toObject()));
+      MOZ_ASSERT(JS::IsCallable(&toString.toObject()));
       JS::Rooted<JS::Value> toStringResult(cx);
       if (JS_CallFunctionValue(cx, obj, toString, JS::HandleValueArray::empty(),
                                &toStringResult)) {
         str = toStringResult.toString();
       } else {
         str = nullptr;
       }
     } else {
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -2972,35 +2972,31 @@ CallerSubsumes(JS::Handle<JS::Value> aVa
   if (!aValue.isObject()) {
     return true;
   }
   return CallerSubsumes(&aValue.toObject());
 }
 
 template<class T>
 inline bool
-WrappedJSToDictionary(nsISupports* aObject, T& aDictionary)
+WrappedJSToDictionary(JSContext* aCx, nsISupports* aObject, T& aDictionary)
 {
   nsCOMPtr<nsIXPConnectWrappedJS> wrappedObj = do_QueryInterface(aObject);
   if (!wrappedObj) {
     return false;
   }
 
-  AutoJSAPI jsapi;
-  jsapi.Init();
-
-  JSContext* cx = jsapi.cx();
-  JS::Rooted<JSObject*> obj(cx, wrappedObj->GetJSObject());
+  JS::Rooted<JSObject*> obj(aCx, wrappedObj->GetJSObject());
   if (!obj) {
     return false;
   }
 
-  JSAutoCompartment ac(cx, obj);
-  JS::Rooted<JS::Value> v(cx, OBJECT_TO_JSVAL(obj));
-  return aDictionary.Init(cx, v);
+  JSAutoCompartment ac(aCx, obj);
+  JS::Rooted<JS::Value> v(aCx, JS::ObjectValue(*obj));
+  return aDictionary.Init(aCx, v);
 }
 
 
 template<class T, class S>
 inline nsRefPtr<T>
 StrongOrRawPtr(already_AddRefed<S>&& aPtr)
 {
   return aPtr.template downcast<T>();
--- a/dom/bindings/CallbackInterface.cpp
+++ b/dom/bindings/CallbackInterface.cpp
@@ -15,17 +15,17 @@ namespace dom {
 bool
 CallbackInterface::GetCallableProperty(JSContext* cx, JS::Handle<jsid> aPropId,
                                        JS::MutableHandle<JS::Value> aCallable)
 {
   if (!JS_GetPropertyById(cx, CallbackPreserveColor(), aPropId, aCallable)) {
     return false;
   }
   if (!aCallable.isObject() ||
-      !JS_ObjectIsCallable(cx, &aCallable.toObject())) {
+      !JS::IsCallable(&aCallable.toObject())) {
     char* propName =
       JS_EncodeString(cx, JS_FORGET_STRING_FLATNESS(JSID_TO_FLAT_STRING(aPropId)));
     nsPrintfCString description("Property '%s'", propName);
     JS_free(cx, propName);
     ThrowErrorMessage(cx, MSG_NOT_CALLABLE, description.get());
     return false;
   }
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -4906,17 +4906,17 @@ def getJSToNativeConversionInfo(type, de
         name = type.unroll().identifier.name
         if type.nullable():
             declType = CGGeneric("nsRefPtr<%s>" % name)
         else:
             declType = CGGeneric("OwningNonNull<%s>" % name)
         conversion = indent(CGCallbackTempRoot(name).define())
 
         if allowTreatNonCallableAsNull and type.treatNonCallableAsNull():
-            haveCallable = "JS_ObjectIsCallable(cx, &${val}.toObject())"
+            haveCallable = "JS::IsCallable(&${val}.toObject())"
             if not isDefinitelyObject:
                 haveCallable = "${val}.isObject() && " + haveCallable
             if defaultValue is not None:
                 assert(isinstance(defaultValue, IDLNullValue))
                 haveCallable = "${haveValue} && " + haveCallable
             template = (
                 ("if (%s) {\n" % haveCallable) +
                 conversion +
@@ -4931,17 +4931,17 @@ def getJSToNativeConversionInfo(type, de
                     haveObject = "${haveValue} && " + haveObject
                 template = CGIfElseWrapper(haveObject,
                                            CGGeneric(conversion),
                                            CGGeneric("${declName} = nullptr;\n")).define()
             else:
                 template = conversion
         else:
             template = wrapObjectTemplate(
-                "if (JS_ObjectIsCallable(cx, &${val}.toObject())) {\n" +
+                "if (JS::IsCallable(&${val}.toObject())) {\n" +
                 conversion +
                 "} else {\n" +
                 indent(onFailureNotCallable(failureCode).define()) +
                 "}\n",
                 type,
                 "${declName} = nullptr;\n",
                 failureCode)
         return JSToNativeConversionInfo(template, declType=declType,
@@ -13412,17 +13412,17 @@ class CGCallback(CGClass):
         CGClass.__init__(self, name,
                          bases=[ClassBase(baseName)],
                          constructors=self.getConstructors(),
                          methods=realMethods+getters+setters)
 
     def getConstructors(self):
         if (not self.idlObject.isInterface() and
             not self.idlObject._treatNonObjectAsNull):
-            body = "MOZ_ASSERT(JS_ObjectIsCallable(nullptr, mCallback));\n"
+            body = "MOZ_ASSERT(JS::IsCallable(mCallback));\n"
         else:
             # Not much we can assert about it, other than not being null, and
             # CallbackObject does that already.
             body = ""
         return [ClassConstructor(
             [Argument("JS::Handle<JSObject*>", "aCallback"),
              Argument("nsIGlobalObject*", "aIncumbentGlobal")],
             bodyInHeader=True,
@@ -13874,17 +13874,17 @@ class CallCallback(CallbackMethod):
     def getCallableDecl(self):
         return "JS::Rooted<JS::Value> callable(cx, JS::ObjectValue(*mCallback));\n"
 
     def getPrettyName(self):
         return self.callback.identifier.name
 
     def getCallGuard(self):
         if self.callback._treatNonObjectAsNull:
-            return "JS_ObjectIsCallable(cx, mCallback) && "
+            return "JS::IsCallable(mCallback) && "
         return ""
 
 
 class CallbackOperationBase(CallbackMethod):
     """
     Common class for implementing various callback operations.
     """
     def __init__(self, signature, jsName, nativeName, descriptor,
@@ -13922,17 +13922,17 @@ class CallbackOperationBase(CallbackMeth
             """,
             methodAtomName=CGDictionary.makeIdName(self.methodName),
             atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms",
             errorReturn=self.getDefaultRetval())
         if not self.singleOperation:
             return 'JS::Rooted<JS::Value> callable(cx);\n' + getCallableFromProp
         return fill(
             """
-            bool isCallable = JS_ObjectIsCallable(cx, mCallback);
+            bool isCallable = JS::IsCallable(mCallback);
             JS::Rooted<JS::Value> callable(cx);
             if (isCallable) {
               callable = JS::ObjectValue(*mCallback);
             } else {
               $*{getCallableFromProp}
             }
             """,
             getCallableFromProp=getCallableFromProp)
--- a/dom/bluetooth/BluetoothInterface.h
+++ b/dom/bluetooth/BluetoothInterface.h
@@ -49,16 +49,18 @@ public:
   virtual void Connect(const nsAString& aBdAddr,
                        BluetoothSocketType aType,
                        const uint8_t aUuid[16],
                        int aChannel, bool aEncrypt, bool aAuth,
                        BluetoothSocketResultHandler* aRes) = 0;
 
   virtual void Accept(int aFd, BluetoothSocketResultHandler* aRes) = 0;
 
+  virtual void Close(BluetoothSocketResultHandler* aRes) = 0;
+
 protected:
   virtual ~BluetoothSocketInterface();
 };
 
 //
 // Handsfree Interface
 //
 
--- a/dom/bluetooth/BluetoothService.cpp
+++ b/dom/bluetooth/BluetoothService.cpp
@@ -29,16 +29,17 @@
 #include "mozilla/ipc/UnixSocket.h"
 #include "nsContentUtils.h"
 #include "nsIObserverService.h"
 #include "nsISettingsService.h"
 #include "nsISystemMessagesInternal.h"
 #include "nsITimer.h"
 #include "nsServiceManagerUtils.h"
 #include "nsXPCOM.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #if defined(MOZ_WIDGET_GONK)
 #include "cutils/properties.h"
 #endif
 
 #if defined(MOZ_B2G_BT)
 #if defined(MOZ_B2G_BT_BLUEZ)
 /**
@@ -555,101 +556,54 @@ BluetoothService::HandleStartup()
 nsresult
 BluetoothService::HandleStartupSettingsCheck(bool aEnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   return StartStopBluetooth(aEnable, true);
 }
 
 nsresult
-BluetoothService::HandleSettingsChanged(const nsAString& aData)
+BluetoothService::HandleSettingsChanged(nsISupports* aSubject)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The string that we're interested in will be a JSON string that looks like:
   //  {"key":"bluetooth.enabled","value":true}
 
-  AutoSafeJSContext cx;
-  if (!cx) {
-    return NS_OK;
-  }
-
-  JS::Rooted<JS::Value> val(cx);
-  if (!JS_ParseJSON(cx, aData.BeginReading(), aData.Length(), &val)) {
-    return JS_ReportPendingException(cx) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (!val.isObject()) {
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  RootedDictionary<SettingChangeNotification> setting(cx);
+  if (!WrappedJSToDictionary(cx, aSubject, setting)) {
     return NS_OK;
   }
-
-  JS::Rooted<JSObject*> obj(cx, &val.toObject());
-
-  JS::Rooted<JS::Value> key(cx);
-  if (!JS_GetProperty(cx, obj, "key", &key)) {
-    MOZ_ASSERT(!JS_IsExceptionPending(cx));
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (!key.isString()) {
-    return NS_OK;
-  }
-
-  // First, check if the string equals to BLUETOOTH_DEBUGGING_SETTING
-  bool match;
-  if (!JS_StringEqualsAscii(cx, key.toString(), BLUETOOTH_DEBUGGING_SETTING, &match)) {
-    MOZ_ASSERT(!JS_IsExceptionPending(cx));
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (match) {
-    JS::Rooted<JS::Value> value(cx);
-    if (!JS_GetProperty(cx, obj, "value", &value)) {
-      MOZ_ASSERT(!JS_IsExceptionPending(cx));
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    if (!value.isBoolean()) {
+  if (setting.mKey.EqualsASCII(BLUETOOTH_DEBUGGING_SETTING)) {
+    if (!setting.mValue.isBoolean()) {
       MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.debugging.enabled'!");
       return NS_ERROR_UNEXPECTED;
     }
-
-    SWITCH_BT_DEBUG(value.toBoolean());
+  
+    SWITCH_BT_DEBUG(setting.mValue.toBoolean());
 
     return NS_OK;
   }
 
   // Second, check if the string is BLUETOOTH_ENABLED_SETTING
-  if (!JS_StringEqualsAscii(cx, key.toString(), BLUETOOTH_ENABLED_SETTING, &match)) {
-    MOZ_ASSERT(!JS_IsExceptionPending(cx));
-    return NS_ERROR_OUT_OF_MEMORY;
+  if (!setting.mKey.EqualsASCII(BLUETOOTH_ENABLED_SETTING)) {
+    return NS_OK;
+  }
+  if (!setting.mValue.isBoolean()) {
+    MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.enabled'!");
+    return NS_ERROR_UNEXPECTED;
   }
 
-  if (match) {
-    JS::Rooted<JS::Value> value(cx);
-    if (!JS_GetProperty(cx, obj, "value", &value)) {
-      MOZ_ASSERT(!JS_IsExceptionPending(cx));
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
+  sToggleInProgress = true;
 
-    if (!value.isBoolean()) {
-      MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.enabled'!");
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    if (sToggleInProgress || value.toBoolean() == IsEnabled()) {
-      // Nothing to do here.
-      return NS_OK;
-    }
-
-    sToggleInProgress = true;
-
-    nsresult rv = StartStopBluetooth(value.toBoolean(), false);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  nsresult rv = StartStopBluetooth(setting.mValue.toBoolean(), false);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 BluetoothService::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -750,17 +704,17 @@ BluetoothService::Observe(nsISupports* a
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!strcmp(aTopic, "profile-after-change")) {
     return HandleStartup();
   }
 
   if (!strcmp(aTopic, MOZSETTINGS_CHANGED_ID)) {
-    return HandleSettingsChanged(nsDependentString(aData));
+    return HandleSettingsChanged(aSubject);
   }
 
   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     return HandleShutdown();
   }
 
   MOZ_ASSERT(false, "BluetoothService got unexpected topic!");
   return NS_ERROR_UNEXPECTED;
--- a/dom/bluetooth/BluetoothService.h
+++ b/dom/bluetooth/BluetoothService.h
@@ -367,17 +367,17 @@ protected:
    */
   nsresult
   HandleStartupSettingsCheck(bool aEnable);
 
   /**
    * Called when "mozsettings-changed" observer topic fires.
    */
   nsresult
-  HandleSettingsChanged(const nsAString& aData);
+  HandleSettingsChanged(nsISupports* aSubject);
 
   /**
    * Called when XPCOM is shutting down.
    */
   virtual nsresult
   HandleShutdown();
 
   // Called by ToggleBtAck.
--- a/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp
@@ -233,17 +233,17 @@ BluetoothA2dpManager::ResetA2dp()
 void
 BluetoothA2dpManager::ResetAvrcp()
 {
   mAvrcpConnected = false;
   mDuration = 0;
   mMediaNumber = 0;
   mTotalMediaCount = 0;
   mPosition = 0;
-  mPlayStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN;
+  mPlayStatus = ControlPlayStatus::PLAYSTATUS_STOPPED;
 }
 
 /*
  * Static functions
  */
 
 static BluetoothA2dpManager::SinkState
 StatusStringToSinkState(const nsAString& aStatus)
--- a/dom/bluetooth/bluedroid/BluetoothSocket.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothSocket.cpp
@@ -390,17 +390,20 @@ public:
   , mFd(aFd)
   { }
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(sBluetoothSocketInterface);
 
-    sBluetoothSocketInterface->Accept(mFd, new AcceptResultHandler(GetIO()));
+    BluetoothSocketResultHandler* res = new AcceptResultHandler(GetIO());
+    GetIO()->mConsumer->SetCurrentResultHandler(res);
+
+    sBluetoothSocketInterface->Accept(mFd, res);
 
     return NS_OK;
   }
 
 private:
   int mFd;
 };
 
@@ -471,16 +474,17 @@ DroidSocketImpl::OnSocketCanConnectWitho
   }
 }
 
 BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver,
                                  BluetoothSocketType aType,
                                  bool aAuth,
                                  bool aEncrypt)
   : mObserver(aObserver)
+  , mCurrentRes(nullptr)
   , mImpl(nullptr)
   , mAuth(aAuth)
   , mEncrypt(aEncrypt)
 {
   MOZ_ASSERT(aObserver);
 
   EnsureBluetoothSocketHalLoad();
   mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
@@ -522,23 +526,25 @@ BluetoothSocket::ConnectSocket(const nsA
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_FALSE(mImpl, false);
 
   SetConnectionStatus(SOCKET_CONNECTING);
 
   mImpl = new DroidSocketImpl(XRE_GetIOMessageLoop(), this);
 
+  BluetoothSocketResultHandler* res = new ConnectSocketResultHandler(mImpl);
+  SetCurrentResultHandler(res);
+
   // TODO: uuid as argument
   sBluetoothSocketInterface->Connect(
     aDeviceAddress,
     BluetoothSocketType::RFCOMM,
     UUID_OBEX_OBJECT_PUSH,
-    aChannel, mEncrypt, mAuth,
-    new ConnectSocketResultHandler(mImpl));
+    aChannel, mEncrypt, mAuth, res);
 
   return true;
 }
 
 class ListenResultHandler MOZ_FINAL : public BluetoothSocketResultHandler
 {
 public:
   ListenResultHandler(DroidSocketImpl* aImpl)
@@ -571,34 +577,42 @@ BluetoothSocket::ListenSocket(int aChann
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_FALSE(mImpl, false);
 
   SetConnectionStatus(SOCKET_LISTENING);
 
   mImpl = new DroidSocketImpl(XRE_GetIOMessageLoop(), this);
 
+  BluetoothSocketResultHandler* res = new ListenResultHandler(mImpl);
+  SetCurrentResultHandler(res);
+
   sBluetoothSocketInterface->Listen(
     BluetoothSocketType::RFCOMM,
     NS_LITERAL_STRING("OBEX Object Push"),
     UUID_OBEX_OBJECT_PUSH,
-    aChannel, mEncrypt, mAuth,
-    new ListenResultHandler(mImpl));
+    aChannel, mEncrypt, mAuth, res);
 
   return true;
 }
 
 void
 BluetoothSocket::CloseSocket()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(sBluetoothSocketInterface);
   if (!mImpl) {
     return;
   }
 
+  // Stop any watching |SocketMessageWatcher|
+  if (mCurrentRes) {
+    sBluetoothSocketInterface->Close(mCurrentRes);
+  }
+
   // From this point on, we consider mImpl as being deleted.
   // We sever the relationship here so any future calls to listen or connect
   // will create a new implementation.
   mImpl->ShutdownOnMainThread();
   XRE_GetIOMessageLoop()->PostTask(
     FROM_HERE, new SocketIOShutdownTask<DroidSocketImpl>(mImpl));
 
   mImpl = nullptr;
@@ -628,24 +642,28 @@ BluetoothSocket::ReceiveSocketData(nsAut
   mObserver->ReceiveSocketData(this, aMessage);
 }
 
 void
 BluetoothSocket::OnConnectSuccess()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mObserver);
+
+  SetCurrentResultHandler(nullptr);
   mObserver->OnSocketConnectSuccess(this);
 }
 
 void
 BluetoothSocket::OnConnectError()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mObserver);
+
+  SetCurrentResultHandler(nullptr);
   mObserver->OnSocketConnectError(this);
 }
 
 void
 BluetoothSocket::OnDisconnect()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mObserver);
--- a/dom/bluetooth/bluedroid/BluetoothSocket.h
+++ b/dom/bluetooth/bluedroid/BluetoothSocket.h
@@ -8,16 +8,17 @@
 #define mozilla_dom_bluetooth_BluetoothSocket_h
 
 #include "BluetoothCommon.h"
 #include "mozilla/ipc/SocketBase.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothSocketObserver;
+class BluetoothSocketResultHandler;
 class DroidSocketImpl;
 
 class BluetoothSocket : public mozilla::ipc::SocketConsumerBase
 {
 public:
   BluetoothSocket(BluetoothSocketObserver* aObserver,
                   BluetoothSocketType aType,
                   bool aAuth,
@@ -42,18 +43,24 @@ public:
     aDeviceAddress = mDeviceAddress;
   }
 
   inline void SetAddress(const nsAString& aDeviceAddress)
   {
     mDeviceAddress = aDeviceAddress;
   }
 
+  inline void SetCurrentResultHandler(BluetoothSocketResultHandler* aRes)
+  {
+    mCurrentRes = aRes;
+  }
+
 private:
   BluetoothSocketObserver* mObserver;
+  BluetoothSocketResultHandler* mCurrentRes;
   DroidSocketImpl* mImpl;
   nsString mDeviceAddress;
   bool mAuth;
   bool mEncrypt;
 };
 
 END_BLUETOOTH_NAMESPACE
 
--- a/dom/bluetooth/bluedroid/BluetoothSocketHALInterface.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothSocketHALInterface.cpp
@@ -4,34 +4,35 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "BluetoothSocketHALInterface.h"
 #include <errno.h>
 #include <unistd.h>
 #include <sys/socket.h>
 #include "BluetoothHALHelpers.h"
+#include "nsClassHashtable.h"
 #include "nsXULAppAPI.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 typedef
   BluetoothHALInterfaceRunnable1<BluetoothSocketResultHandler, void,
-                                       int, int>
+                                 int, int>
   BluetoothSocketHALIntResultRunnable;
 
 typedef
   BluetoothHALInterfaceRunnable3<BluetoothSocketResultHandler, void,
-                                       int, const nsString, int,
-                                       int, const nsAString_internal&, int>
+                                 int, const nsString, int,
+                                 int, const nsAString_internal&, int>
   BluetoothSocketHALIntStringIntResultRunnable;
 
 typedef
   BluetoothHALInterfaceRunnable1<BluetoothSocketResultHandler, void,
-                                       BluetoothStatus, BluetoothStatus>
+                                 BluetoothStatus, BluetoothStatus>
   BluetoothSocketHALErrorRunnable;
 
 static nsresult
 DispatchBluetoothSocketHALResult(
   BluetoothSocketResultHandler* aRes,
   void (BluetoothSocketResultHandler::*aMethod)(int), int aArg,
   BluetoothStatus aStatus)
 {
@@ -73,21 +74,21 @@ DispatchBluetoothSocketHALResult(
   if (NS_FAILED(rv)) {
     BT_WARNING("NS_DispatchToMainThread failed: %X", rv);
   }
   return rv;
 }
 
 void
 BluetoothSocketHALInterface::Listen(BluetoothSocketType aType,
-                                          const nsAString& aServiceName,
-                                          const uint8_t aServiceUuid[16],
-                                          int aChannel, bool aEncrypt,
-                                          bool aAuth,
-                                          BluetoothSocketResultHandler* aRes)
+                                    const nsAString& aServiceName,
+                                    const uint8_t aServiceUuid[16],
+                                    int aChannel, bool aEncrypt,
+                                    bool aAuth,
+                                    BluetoothSocketResultHandler* aRes)
 {
   int fd;
   bt_status_t status;
   btsock_type_t type = BTSOCK_RFCOMM; // silences compiler warning
 
   if (NS_SUCCEEDED(Convert(aType, type))) {
     status = mInterface->listen(type,
                                 NS_ConvertUTF16toUTF8(aServiceName).get(),
@@ -104,16 +105,44 @@ BluetoothSocketHALInterface::Listen(Blue
       ConvertDefault(status, STATUS_FAIL));
   }
 }