Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 27 Jul 2015 10:43:09 -0400
changeset 254744 d576f2cec511a21fef54b61c54b69d5e5d5fc992
parent 254743 8755515ffd2d8c9428118633c25fac33833db4cc (current diff)
parent 254730 21ca97268bae2d746e09ad6c612f4fbf3df0fe6e (diff)
child 254745 d593a1d313c6f365ed66432e4b7f06544a31ac68
child 254747 19002d887cb41b9c777e6c53c7cf017f30bd0505
child 254796 2ea0af589ebd830f4a4c99ae1d2f46e428801322
push id14248
push userryanvm@gmail.com
push dateMon, 27 Jul 2015 16:13:30 +0000
treeherderfx-team@d593a1d313c6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone42.0a1
Merge m-c to b2g-inbound. a=merge
dom/broadcastchannel/tests/broadcastchannel_pref_worker.js
dom/broadcastchannel/tests/test_broadcastchannel_pref.html
dom/push/PushServiceChildPreload.js
testing/web-platform/meta/mixed-content/__dir__.ini
testing/web-platform/meta/mixed-content/allowed/http-csp/same-host-https/form-tag/top-level/no-redirect/allowed.https.html.ini
testing/web-platform/meta/notifications/notification-lang.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/hosting-multiple-shadow-trees-002.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/hosting-multiple-shadow-trees-003.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/hosting-multiple-shadow-trees/test-001.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/hosting-multiple-shadow-trees/test-002.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/hosting-multiple-shadow-trees/test-003.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-006.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-007.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-008.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-009.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-010.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-011.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-012.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-013.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-014.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-015.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-016.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-017.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-018.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-019.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/satisfying-matching-criteria/test-020.html.ini
testing/web-platform/meta/shadow-dom/shadow-trees/text-decoration-001.html.ini
testing/web-platform/meta/shadow-dom/styles/test-002.html.ini
testing/web-platform/tests/conformance-checkers/html-aria/author-requirements/571.html
testing/web-platform/tests/conformance-checkers/html-aria/author-requirements/572.html
testing/web-platform/tests/conformance-checkers/html-aria/author-requirements/573.html
testing/web-platform/tests/conformance-checkers/html-aria/combobox-autocomplete-list/div.html
testing/web-platform/tests/conformance-checkers/html-aria/host-language/implicit-semantics-checkbox-disparity.html
testing/web-platform/tests/conformance-checkers/html-aria/host-language/implicit-semantics-checkbox-role.html
testing/web-platform/tests/content-security-policy/blink-contrib/resources/alert-pass.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/child-src-test.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/dump-as-text.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/frame-ancestors-test.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/mixed-content-with-csp.html
testing/web-platform/tests/content-security-policy/blink-contrib/resources/multiple-iframe-plugin-test.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/multiple-iframe-test.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/referrer-test.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/reflected-xss-and-xss-protection.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/script-src.html
testing/web-platform/tests/content-security-policy/blink-contrib/resources/securitypolicy-tests-base.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/securitypolicyviolation-test.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/shared-worker-make-xhr.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/style.xsl
testing/web-platform/tests/content-security-policy/blink-contrib/resources/transform-to-img.xsl
testing/web-platform/tests/notifications/Notification-permission.html
testing/web-platform/tests/notifications/Notification-requestPermission-denied-manual.html
testing/web-platform/tests/notifications/Notification-requestPermission-granted-manual.html
testing/web-platform/tests/notifications/README.md
testing/web-platform/tests/notifications/notification-body-basic-manual.html
testing/web-platform/tests/notifications/notification-body-empty-manual.html
testing/web-platform/tests/notifications/notification-close-manual.html
testing/web-platform/tests/notifications/notification-constructor-basic.html
testing/web-platform/tests/notifications/notification-constructor-invalid.html
testing/web-platform/tests/notifications/notification-dir-auto-manual.html
testing/web-platform/tests/notifications/notification-dir-ltr-manual.html
testing/web-platform/tests/notifications/notification-dir-rtl-manual.html
testing/web-platform/tests/notifications/notification-icon-basic-manual.html
testing/web-platform/tests/notifications/notification-icon-empty-manual.html
testing/web-platform/tests/notifications/notification-lang.html
testing/web-platform/tests/notifications/notification-tag-different-manual.html
testing/web-platform/tests/notifications/notification-tag-same-manual.html
testing/web-platform/tests/shadow-dom/elements-and-dom-objects/shadowroot-object/shadowroot-methods/test-002.html
testing/web-platform/tests/shadow-dom/elements-and-dom-objects/shadowroot-object/shadowroot-methods/test-003.html
testing/web-platform/tests/shadow-dom/elements-and-dom-objects/shadowroot-object/shadowroot-methods/test-005.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees-002-ref.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees-002.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees-003-ref.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees-003.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees-004-ref.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees-004.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees-005-ref.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees-005.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees-006-ref.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees-006.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees/test-001.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees/test-002.html
testing/web-platform/tests/shadow-dom/shadow-trees/hosting-multiple-shadow-trees/test-003.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-006.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-007.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-008.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-009.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-010.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-011.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-012.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-013.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-014.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-015.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-016.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-017.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-018.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-019.html
testing/web-platform/tests/shadow-dom/shadow-trees/satisfying-matching-criteria/test-020.html
testing/web-platform/tests/shadow-dom/styles/test-002.html
widget/gtk/nsGtkIMModule.cpp
widget/gtk/nsGtkIMModule.h
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -350,20 +350,22 @@ ConvertToNSArray(nsTArray<ProxyAccessibl
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (id)childAt:(uint32_t)i
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
-  AccessibleWrap* accWrap = [self getGeckoAccessible];
-  if (accWrap) {
-    Accessible* acc = accWrap->GetChildAt(i);
-    return acc ? GetNativeFromGeckoAccessible(acc) : nil;
+  if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+    Accessible* child = accWrap->GetChildAt(i);
+    return child ? GetNativeFromGeckoAccessible(child) : nil;
+  } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+    ProxyAccessible* child = proxy->ChildAt(i);
+    return child ? GetNativeFromProxy(child) : nil;
   }
 
   return nil;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (id)accessibilityAttributeValue:(NSString*)attribute
@@ -844,41 +846,46 @@ ConvertToNSArray(nsTArray<ProxyAccessibl
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (NSValue*)position
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
-  AccessibleWrap* accWrap = [self getGeckoAccessible];
-  if (!accWrap)
+  nsIntRect rect;
+  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+    rect = accWrap->Bounds();
+  else if (ProxyAccessible* proxy = [self getProxyAccessible])
+    rect = proxy->Bounds();
+  else
     return nil;
 
-  nsIntRect rect = accWrap->Bounds();
-
   NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
   NSPoint p = NSMakePoint(static_cast<CGFloat>(rect.x) / scaleFactor,
                          [mainView frame].size.height - static_cast<CGFloat>(rect.y + rect.height) / scaleFactor);
 
   return [NSValue valueWithPoint:p];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (NSValue*)size
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
-  AccessibleWrap* accWrap = [self getGeckoAccessible];
-  if (!accWrap)
+  nsIntRect rect;
+  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+    rect = accWrap->Bounds();
+  else if (ProxyAccessible* proxy = [self getProxyAccessible])
+    rect = proxy->Bounds();
+  else
     return nil;
 
-  nsIntRect rect = accWrap->Bounds();
   CGFloat scaleFactor =
     nsCocoaUtils::GetBackingScaleFactor([[NSScreen screens] objectAtIndex:0]);
   return [NSValue valueWithSize:NSMakeSize(static_cast<CGFloat>(rect.width) / scaleFactor,
                                            static_cast<CGFloat>(rect.height) / scaleFactor)];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1113,19 +1113,16 @@ pref("identity.fxaccounts.enabled", true
 // Mobile Identity API.
 pref("services.mobileid.server.uri", "https://msisdn.services.mozilla.com");
 
 // Enable mapped array buffer.
 #ifndef XP_WIN
 pref("dom.mapped_arraybuffer.enabled", true);
 #endif
 
-// BroadcastChannel API
-pref("dom.broadcastChannel.enabled", true);
-
 // SystemUpdate API
 pref("dom.system_update.enabled", true);
 
 // UDPSocket API
 pref("dom.udpsocket.enabled", true);
 
 // Enable TV Manager API
 pref("dom.tv.enabled", true);
--- a/b2g/config/mozconfigs/linux32_gecko/debug
+++ b/b2g/config/mozconfigs/linux32_gecko/debug
@@ -30,11 +30,11 @@ no_sccache=
 #B2G options
 ac_add_options --enable-application=b2g
 ENABLE_MARIONETTE=1
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
-MOZTTDIR=$topsrcdir/moztt
+MOZTTDIR=$topsrcdir/moz-tt
 
 . "$topsrcdir/b2g/config/mozconfigs/common.override"
--- a/b2g/config/mozconfigs/linux32_gecko/nightly
+++ b/b2g/config/mozconfigs/linux32_gecko/nightly
@@ -29,14 +29,14 @@ no_sccache=
 
 #B2G options
 ac_add_options --enable-application=b2g
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
-MOZTTDIR=$topsrcdir/moztt
+MOZTTDIR=$topsrcdir/moz-tt
 
 # Build simulator xpi and phone tweaks for b2g-desktop
 FXOS_SIMULATOR=1
 
 . "$topsrcdir/b2g/config/mozconfigs/common.override"
--- a/b2g/config/mozconfigs/linux64_gecko/debug
+++ b/b2g/config/mozconfigs/linux64_gecko/debug
@@ -30,11 +30,11 @@ no_sccache=
 #B2G options
 ac_add_options --enable-application=b2g
 ENABLE_MARIONETTE=1
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
-MOZTTDIR=$topsrcdir/moztt
+MOZTTDIR=$topsrcdir/moz-tt
 
 . "$topsrcdir/b2g/config/mozconfigs/common.override"
--- a/b2g/config/mozconfigs/linux64_gecko/nightly
+++ b/b2g/config/mozconfigs/linux64_gecko/nightly
@@ -29,14 +29,14 @@ no_sccache=
 
 #B2G options
 ac_add_options --enable-application=b2g
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
-MOZTTDIR=$topsrcdir/moztt
+MOZTTDIR=$topsrcdir/moz-tt
 
 # Build simulator xpi and phone tweaks for b2g-desktop
 FXOS_SIMULATOR=1
 
 . "$topsrcdir/b2g/config/mozconfigs/common.override"
--- a/b2g/config/mozconfigs/macosx64_gecko/debug
+++ b/b2g/config/mozconfigs/macosx64_gecko/debug
@@ -27,11 +27,11 @@ ac_add_options --enable-debug-symbols
 ac_add_options --enable-debug
 ENABLE_MARIONETTE=1
 
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
-MOZTTDIR=$topsrcdir/moztt
+MOZTTDIR=$topsrcdir/moz-tt
 
 . "$topsrcdir/b2g/config/mozconfigs/common.override"
--- a/b2g/config/mozconfigs/macosx64_gecko/nightly
+++ b/b2g/config/mozconfigs/macosx64_gecko/nightly
@@ -25,14 +25,14 @@ ac_add_options --enable-warnings-as-erro
 # B2G Stuff
 ac_add_options --enable-application=b2g
 ac_add_options --enable-debug-symbols
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
-MOZTTDIR=$topsrcdir/moztt
+MOZTTDIR=$topsrcdir/moz-tt
 
 # Build simulator xpi and phone tweaks for b2g-desktop
 FXOS_SIMULATOR=1
 
 . "$topsrcdir/b2g/config/mozconfigs/common.override"
--- a/b2g/config/mozconfigs/win32_gecko/debug
+++ b/b2g/config/mozconfigs/win32_gecko/debug
@@ -24,11 +24,11 @@ fi
 ac_add_options --enable-application=b2g
 ENABLE_MARIONETTE=1
 
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
-MOZTTDIR=$topsrcdir/moztt
+MOZTTDIR=$topsrcdir/moz-tt
 
 . "$topsrcdir/b2g/config/mozconfigs/common.override"
--- a/b2g/config/mozconfigs/win32_gecko/nightly
+++ b/b2g/config/mozconfigs/win32_gecko/nightly
@@ -22,14 +22,14 @@ fi
 
 # B2G Options
 ac_add_options --enable-application=b2g
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
-MOZTTDIR=$topsrcdir/moztt
+MOZTTDIR=$topsrcdir/moz-tt
 
 # Build simulator xpi and phone tweaks for b2g-desktop
 FXOS_SIMULATOR=1
 
 . "$topsrcdir/b2g/config/mozconfigs/common.override"
--- a/b2g/config/tooltool-manifests/linux32/releng.manifest
+++ b/b2g/config/tooltool-manifests/linux32/releng.manifest
@@ -9,15 +9,15 @@
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
-"size": 31057326,
-"digest": "b844c3e52be493d2cacafa58c4a924b89c9be8d2dcc2a7c71aed58c253d8035fba4d51df309f73e3c4342a1f3c3898a9a25c4815e2112888d1280f43c41c8e51",
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
 "algorithm": "sha512",
-"filename": "moztt.tar.bz2",
+"filename": "moz-tt.tar.bz2",
 "unpack": true
 }
 ]
--- a/b2g/config/tooltool-manifests/linux64/releng.manifest
+++ b/b2g/config/tooltool-manifests/linux64/releng.manifest
@@ -9,15 +9,15 @@
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
-"size": 31057326,
-"digest": "b844c3e52be493d2cacafa58c4a924b89c9be8d2dcc2a7c71aed58c253d8035fba4d51df309f73e3c4342a1f3c3898a9a25c4815e2112888d1280f43c41c8e51",
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
 "algorithm": "sha512",
-"filename": "moztt.tar.bz2",
+"filename": "moz-tt.tar.bz2",
 "unpack": true
 }
 ]
--- a/b2g/config/tooltool-manifests/macosx64/releng.manifest
+++ b/b2g/config/tooltool-manifests/macosx64/releng.manifest
@@ -12,15 +12,15 @@
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
-"size": 31057326,
-"digest": "b844c3e52be493d2cacafa58c4a924b89c9be8d2dcc2a7c71aed58c253d8035fba4d51df309f73e3c4342a1f3c3898a9a25c4815e2112888d1280f43c41c8e51",
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
 "algorithm": "sha512",
-"filename": "moztt.tar.bz2",
+"filename": "moz-tt.tar.bz2",
 "unpack": true
 }
 ]
--- a/b2g/config/tooltool-manifests/win32/releng.manifest
+++ b/b2g/config/tooltool-manifests/win32/releng.manifest
@@ -1,20 +1,20 @@
 [
 {
 "size": 266240,
 "digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
 "algorithm": "sha512",
 "filename": "mozmake.exe"
 },
 {
-"size": 31057326,
-"digest": "b844c3e52be493d2cacafa58c4a924b89c9be8d2dcc2a7c71aed58c253d8035fba4d51df309f73e3c4342a1f3c3898a9a25c4815e2112888d1280f43c41c8e51",
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
 "algorithm": "sha512",
-"filename": "moztt.tar.bz2",
+"filename": "moz-tt.tar.bz2",
 "unpack": true
 },
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
--- a/b2g/dev/config/mozconfigs/linux64/mulet
+++ b/b2g/dev/config/mozconfigs/linux64/mulet
@@ -3,9 +3,9 @@ MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
 MOZ_AUTOMATION_UPDATE_PACKAGING=0
 MOZ_AUTOMATION_SDK=0
 . "$topsrcdir/browser/config/mozconfigs/linux64/nightly"
 
 ac_add_options --enable-default-toolkit=cairo-gtk2
 ac_add_options --enable-application=b2g/dev
 
 # Include Firefox OS fonts.
-MOZTTDIR=$topsrcdir/moztt
+MOZTTDIR=$topsrcdir/moz-tt
--- a/b2g/dev/config/mozconfigs/macosx64/mulet
+++ b/b2g/dev/config/mozconfigs/macosx64/mulet
@@ -19,12 +19,12 @@ ac_add_options --with-macbundlename-pref
 
 # Treat warnings as errors in directories with FAIL_ON_WARNINGS.
 ac_add_options --enable-warnings-as-errors
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
 # Include Firefox OS fonts.
-MOZTTDIR=$topsrcdir/moztt
+MOZTTDIR=$topsrcdir/moz-tt
 
 . "$topsrcdir/build/mozconfig.common.override"
 . "$topsrcdir/build/mozconfig.cache"
--- a/b2g/dev/config/mozconfigs/win32/mulet
+++ b/b2g/dev/config/mozconfigs/win32/mulet
@@ -5,9 +5,9 @@ MOZ_AUTOMATION_INSTALLER=0
 MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
 MOZ_AUTOMATION_UPDATE_PACKAGING=0
 MOZ_AUTOMATION_SDK=0
 . "$topsrcdir/browser/config/mozconfigs/win32/nightly"
 
 ac_add_options --enable-application=b2g/dev
 
 # Include Firefox OS fonts.
-MOZTTDIR=$topsrcdir/moztt
+MOZTTDIR=$topsrcdir/moz-tt
--- a/b2g/dev/config/tooltool-manifests/linux64/releng.manifest
+++ b/b2g/dev/config/tooltool-manifests/linux64/releng.manifest
@@ -9,15 +9,15 @@
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
-"size": 31057326,
-"digest": "b844c3e52be493d2cacafa58c4a924b89c9be8d2dcc2a7c71aed58c253d8035fba4d51df309f73e3c4342a1f3c3898a9a25c4815e2112888d1280f43c41c8e51",
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
 "algorithm": "sha512",
-"filename": "moztt.tar.bz2",
+"filename": "moz-tt.tar.bz2",
 "unpack": true
 }
 ]
--- a/b2g/dev/config/tooltool-manifests/macosx64/releng.manifest
+++ b/b2g/dev/config/tooltool-manifests/macosx64/releng.manifest
@@ -12,15 +12,15 @@
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
-"size": 31057326,
-"digest": "b844c3e52be493d2cacafa58c4a924b89c9be8d2dcc2a7c71aed58c253d8035fba4d51df309f73e3c4342a1f3c3898a9a25c4815e2112888d1280f43c41c8e51",
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
 "algorithm": "sha512",
-"filename": "moztt.tar.bz2",
+"filename": "moz-tt.tar.bz2",
 "unpack": true
 }
 ]
--- a/b2g/dev/config/tooltool-manifests/win32/releng.manifest
+++ b/b2g/dev/config/tooltool-manifests/win32/releng.manifest
@@ -8,15 +8,15 @@
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
-"size": 31057326,
-"digest": "b844c3e52be493d2cacafa58c4a924b89c9be8d2dcc2a7c71aed58c253d8035fba4d51df309f73e3c4342a1f3c3898a9a25c4815e2112888d1280f43c41c8e51",
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
 "algorithm": "sha512",
-"filename": "moztt.tar.bz2",
+"filename": "moz-tt.tar.bz2",
 "unpack": true
 }
 ]
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -639,16 +639,17 @@
 @RESPATH@/components/XULStore.js
 @RESPATH@/components/XULStore.manifest
 @RESPATH@/components/Webapps.js
 @RESPATH@/components/Webapps.manifest
 @RESPATH@/components/AppsService.js
 @RESPATH@/components/AppsService.manifest
 @RESPATH@/components/Push.js
 @RESPATH@/components/Push.manifest
+@RESPATH@/components/PushClient.js
 @RESPATH@/components/PushNotificationService.js
 @RESPATH@/components/PushServiceLauncher.js
 
 @RESPATH@/components/InterAppComm.manifest
 @RESPATH@/components/InterAppCommService.js
 @RESPATH@/components/InterAppConnection.js
 @RESPATH@/components/InterAppMessagePort.js
 
@@ -705,16 +706,17 @@
 ; Modules
 @RESPATH@/modules/*
 
 ; Safe Browsing
 @RESPATH@/components/nsURLClassifier.manifest
 @RESPATH@/components/nsUrlClassifierHashCompleter.js
 @RESPATH@/components/nsUrlClassifierListManager.js
 @RESPATH@/components/nsUrlClassifierLib.js
+@RESPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js
 @RESPATH@/components/url-classifier.xpt
 
 ; GNOME hooks
 #ifdef MOZ_ENABLE_GNOME_COMPONENT
 @RESPATH@/components/@DLL_PREFIX@mozgnome@DLL_SUFFIX@
 #endif
 
 ; ANGLE on Win32
--- a/browser/base/content/aboutaccounts/aboutaccounts.js
+++ b/browser/base/content/aboutaccounts/aboutaccounts.js
@@ -143,18 +143,23 @@ let wrapper = {
    *
    * @param accountData the user's account data and credentials
    */
   onLogin: function (accountData) {
     log("Received: 'login'. Data:" + JSON.stringify(accountData));
 
     if (accountData.customizeSync) {
       Services.prefs.setBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION, true);
-      delete accountData.customizeSync;
     }
+    delete accountData.customizeSync;
+    // sessionTokenContext is erroneously sent by the content server.
+    // https://github.com/mozilla/fxa-content-server/issues/2766
+    // To avoid having the FxA storage manager not knowing what to do with
+    // it we delete it here.
+    delete accountData.sessionTokenContext;
 
     // We need to confirm a relink - see shouldAllowRelink for more
     let newAccountEmail = accountData.email;
     // The hosted code may have already checked for the relink situation
     // by sending the can_link_account command. If it did, then
     // it will indicate we don't need to ask twice.
     if (!accountData.verifiedCanLinkAccount && !shouldAllowRelink(newAccountEmail)) {
       // we need to tell the page we successfully received the message, but
--- a/browser/base/content/browser-trackingprotection.js
+++ b/browser/base/content/browser-trackingprotection.js
@@ -111,35 +111,42 @@ let TrackingProtection = {
     // Any scheme turned into https is correct.
     let normalizedUrl = Services.io.newURI(
       "https://" + gBrowser.selectedBrowser.currentURI.hostPort,
       null, null);
 
     // Add the current host in the 'trackingprotection' consumer of
     // the permission manager using a normalized URI. This effectively
     // places this host on the tracking protection allowlist.
-    Services.perms.add(normalizedUrl,
-      "trackingprotection", Services.perms.ALLOW_ACTION);
+    if (PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser)) {
+      PrivateBrowsingUtils.addToTrackingAllowlist(normalizedUrl);
+    } else {
+      Services.perms.add(normalizedUrl,
+        "trackingprotection", Services.perms.ALLOW_ACTION);
+    }
 
     // Telemetry for disable protection.
     this.eventsHistogram.add(1);
 
     BrowserReload();
   },
 
   enableForCurrentPage() {
     // Remove the current host from the 'trackingprotection' consumer
     // of the permission manager. This effectively removes this host
     // from the tracking protection allowlist.
     let normalizedUrl = Services.io.newURI(
       "https://" + gBrowser.selectedBrowser.currentURI.hostPort,
       null, null);
 
-    Services.perms.remove(normalizedUrl,
-      "trackingprotection");
+    if (PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser)) {
+      PrivateBrowsingUtils.removeFromTrackingAllowlist(normalizedUrl);
+    } else {
+      Services.perms.remove(normalizedUrl, "trackingprotection");
+    }
 
     // Telemetry for enable protection.
     this.eventsHistogram.add(2);
 
     BrowserReload();
   },
 
   showIntroPanel: Task.async(function*() {
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -426,16 +426,20 @@ support-files =
   benignPage.html
 [browser_trackingUI_3.js]
 tags = trackingprotection
 [browser_trackingUI_4.js]
 tags = trackingprotection
 support-files =
   trackingPage.html
   benignPage.html
+[browser_trackingUI_5.js]
+tags = trackingprotection
+support-files =
+  trackingPage.html
 [browser_typeAheadFind.js]
 skip-if = buildapp == 'mulet'
 [browser_unknownContentType_title.js]
 [browser_unloaddialogs.js]
 skip-if = e10s # Bug 1100700 - test relies on unload event firing on closed tabs, which it doesn't
 [browser_urlHighlight.js]
 [browser_urlbarAutoFillTrimURLs.js]
 [browser_urlbarCopying.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_5.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that sites added to the Tracking Protection whitelist in private
+// browsing mode don't persist once the private browsing window closes.
+
+const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+let TrackingProtection = null;
+let browser = null;
+let {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+registerCleanupFunction(function() {
+  TrackingProtection = browser = null;
+  UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+function hidden(sel) {
+  let win = browser.ownerGlobal;
+  let el = win.document.querySelector(sel);
+  let display = win.getComputedStyle(el).getPropertyValue("display", null);
+  return display === "none";
+}
+
+function clickButton(sel) {
+  let win = browser.ownerGlobal;
+  let el = win.document.querySelector(sel);
+  el.doCommand();
+}
+
+function testTrackingPage(window) {
+  info("Tracking content must be blocked");
+  ok(!TrackingProtection.container.hidden, "The container is visible");
+  is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
+     'content: state="blocked-tracking-content"');
+  is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content",
+     'icon: state="blocked-tracking-content"');
+
+  ok(!hidden("#tracking-protection-icon"), "icon is visible");
+  ok(hidden("#tracking-action-block"), "blockButton is hidden");
+
+  ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+  ok(!hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is visible");
+
+  // Make sure that the blocked tracking elements message appears
+  ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
+  ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden");
+  ok(!hidden("#tracking-blocked"), "labelTrackingBlocked is visible");
+}
+
+function testTrackingPageUnblocked() {
+  info("Tracking content must be white-listed and not blocked");
+  ok(!TrackingProtection.container.hidden, "The container is visible");
+  is(TrackingProtection.content.getAttribute("state"), "loaded-tracking-content",
+     'content: state="loaded-tracking-content"');
+  is(TrackingProtection.icon.getAttribute("state"), "loaded-tracking-content",
+     'icon: state="loaded-tracking-content"');
+
+  ok(!hidden("#tracking-protection-icon"), "icon is visible");
+  ok(!hidden("#tracking-action-block"), "blockButton is visible");
+  ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+
+  // Make sure that the blocked tracking elements message appears
+  ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
+  ok(!hidden("#tracking-loaded"), "labelTrackingLoaded is visible");
+  ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden");
+}
+
+add_task(function* testExceptionAddition() {
+  yield UrlClassifierTestUtils.addTestTrackers();
+  let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+  browser = privateWin.gBrowser;
+  let tab = browser.selectedTab = browser.addTab();
+
+  TrackingProtection = browser.ownerGlobal.TrackingProtection;
+  yield pushPrefs([PB_PREF, true]);
+
+  ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+  info("Load a test page containing tracking elements");
+  yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+
+  testTrackingPage(tab.ownerDocument.defaultView);
+
+  info("Disable TP for the page (which reloads the page)");
+  let tabReloadPromise = promiseTabLoadEvent(tab);
+  clickButton("#tracking-action-unblock");
+  yield tabReloadPromise;
+  testTrackingPageUnblocked();
+
+  info("Test that the exception is remembered across tabs in the same private window");
+  tab = browser.selectedTab = browser.addTab();
+
+  info("Load a test page containing tracking elements");
+  yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+  testTrackingPageUnblocked();
+
+  yield promiseWindowClosed(privateWin);
+});
+
+add_task(function* testExceptionPersistence() {
+  info("Open another private browsing window");
+  let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+  browser = privateWin.gBrowser;
+  let tab = browser.selectedTab = browser.addTab();
+
+  TrackingProtection = browser.ownerGlobal.TrackingProtection;
+  ok(TrackingProtection.enabled, "TP is still enabled");
+
+  info("Load a test page containing tracking elements");
+  yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+
+  testTrackingPage(tab.ownerDocument.defaultView);
+
+  info("Disable TP for the page (which reloads the page)");
+  let tabReloadPromise = promiseTabLoadEvent(tab);
+  clickButton("#tracking-action-unblock");
+  yield tabReloadPromise;
+  testTrackingPageUnblocked();
+
+  privateWin.close();
+});
--- a/browser/base/content/test/plugins/head.js
+++ b/browser/base/content/test/plugins/head.js
@@ -1,12 +1,10 @@
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
-  "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
 
 // The blocklist shim running in the content process does not initialize at
 // start up, so it's not active until we load content that needs to do a
 // check. This helper bypasses the delay to get the svc up and running
@@ -30,28 +28,24 @@ function promiseInitContentBlocklistSvc(
   * Usage:
   *    let wait = yield waitForMs(2000);
   *    ok(wait, "2 seconds should now have elapsed");
   *
   * @param aMs the number of miliseconds to wait for
   * @returns a Promise that resolves to true after the time has elapsed
   */
 function waitForMs(aMs) {
-  let deferred = Promise.defer();
-  let startTime = Date.now();
-  setTimeout(done, aMs);
-  function done() {
-    deferred.resolve(true);
-  }
-  return deferred.promise;
+  return new Promise((resolve) => {
+    setTimeout(done, aMs);
+    function done() {
+      resolve(true);
+    }
+  });
 }
 
-
-// DOM Promise fails for unknown reasons here, so we're using
-// resource://gre/modules/Promise.jsm.
 function waitForEvent(subject, eventName, checkFn, useCapture, useUntrusted) {
   return new Promise((resolve, reject) => {
     subject.addEventListener(eventName, function listener(event) {
       try {
         if (checkFn && !checkFn(event)) {
           return;
         }
         subject.removeEventListener(eventName, listener, useCapture);
@@ -79,43 +73,43 @@ function waitForEvent(subject, eventName
  *        The url to load, or the current url.
  * @param [optional] event
  *        The load event type to wait for.  Defaults to "load".
  * @return {Promise} resolved when the event is handled.
  * @resolves to the received event
  * @rejects if a valid load event is not received within a meaningful interval
  */
 function promiseTabLoadEvent(tab, url, eventType="load") {
-  let deferred = Promise.defer();
-  info("Wait tab event: " + eventType);
+  return new Promise((resolve, reject) => {
+    info("Wait tab event: " + eventType);
 
-  function handle(event) {
-    if (event.originalTarget != tab.linkedBrowser.contentDocument ||
-        event.target.location.href == "about:blank" ||
-        (url && event.target.location.href != url)) {
-      info("Skipping spurious '" + eventType + "'' event" +
-            " for " + event.target.location.href);
-      return;
+    function handle(event) {
+      if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+          event.target.location.href == "about:blank" ||
+          (url && event.target.location.href != url)) {
+        info("Skipping spurious '" + eventType + "'' event" +
+              " for " + event.target.location.href);
+        return;
+      }
+      clearTimeout(timeout);
+      tab.linkedBrowser.removeEventListener(eventType, handle, true);
+      info("Tab event received: " + eventType);
+      resolve(event);
     }
-    clearTimeout(timeout);
-    tab.linkedBrowser.removeEventListener(eventType, handle, true);
-    info("Tab event received: " + eventType);
-    deferred.resolve(event);
-  }
+
+    let timeout = setTimeout(() => {
+      tab.linkedBrowser.removeEventListener(eventType, handle, true);
+      reject(new Error("Timed out while waiting for a '" + eventType + "'' event"));
+    }, 30000);
 
-  let timeout = setTimeout(() => {
-    tab.linkedBrowser.removeEventListener(eventType, handle, true);
-    deferred.reject(new Error("Timed out while waiting for a '" + eventType + "'' event"));
-  }, 30000);
-
-  tab.linkedBrowser.addEventListener(eventType, handle, true, true);
-  if (url) {
-    tab.linkedBrowser.loadURI(url);
-  }
-  return deferred.promise;
+    tab.linkedBrowser.addEventListener(eventType, handle, true, true);
+    if (url) {
+      tab.linkedBrowser.loadURI(url);
+    }
+  });
 }
 
 function waitForCondition(condition, nextTest, errorMsg, aTries, aWait) {
   let tries = 0;
   let maxTries = aTries || 100; // 100 tries
   let maxWait = aWait || 100; // 100 msec x 100 tries = ten seconds
   let interval = setInterval(function() {
     if (tries >= maxTries) {
@@ -134,21 +128,21 @@ function waitForCondition(condition, nex
     }
     tries++;
   }, maxWait);
   let moveOn = function() { clearInterval(interval); nextTest(); };
 }
 
 // Waits for a conditional function defined by the caller to return true.
 function promiseForCondition(aConditionFn, aMessage, aTries, aWait) {
-  let deferred = Promise.defer();
-  waitForCondition(aConditionFn, deferred.resolve,
-                   (aMessage || "Condition didn't pass."),
-                   aTries, aWait);
-  return deferred.promise;
+  return new Promise((resolve) => {
+    waitForCondition(aConditionFn, resolve,
+                     (aMessage || "Condition didn't pass."),
+                     aTries, aWait);
+  });
 }
 
 // Returns the chrome side nsIPluginTag for this plugin
 function getTestPlugin(aName) {
   let pluginName = aName || "Test Plug-in";
   let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
   let tags = ph.getPluginTags();
 
@@ -293,27 +287,25 @@ function* asyncSetAndUpdateBlocklist(aUR
 // Reset back to the blocklist we had at the start of the test run.
 function resetBlocklist() {
   Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL);
 }
 
 // Insure there's a popup notification present. This test does not indicate
 // open state. aBrowser can be undefined.
 function promisePopupNotification(aName, aBrowser) {
-  let deferred = Promise.defer();
+  return new Promise((resolve) => {
+    waitForCondition(() => PopupNotifications.getNotification(aName, aBrowser),
+                     () => {
+      ok(!!PopupNotifications.getNotification(aName, aBrowser),
+         aName + " notification appeared");
 
-  waitForCondition(() => PopupNotifications.getNotification(aName, aBrowser),
-                   () => {
-    ok(!!PopupNotifications.getNotification(aName, aBrowser),
-       aName + " notification appeared");
-
-    deferred.resolve();
-  }, "timeout waiting for popup notification " + aName);
-
-  return deferred.promise;
+      resolve();
+    }, "timeout waiting for popup notification " + aName);
+  });
 }
 
 /**
  * Allows setting focus on a window, and waiting for that window to achieve
  * focus.
  *
  * @param aWindow
  *        The window to focus and wait for.
@@ -356,19 +348,19 @@ function waitForNotificationBar(notifica
         resolve(notification);
       },
       `Waited too long for the ${notificationID} notification bar`
     );
   });
 }
 
 function promiseForNotificationBar(notificationID, browser) {
-  let deferred = Promise.defer();
-  waitForNotificationBar(notificationID, browser, deferred.resolve);
-  return deferred.promise;
+  return new Promise((resolve) => {
+    waitForNotificationBar(notificationID, browser, resolve);
+  });
 }
 
 /**
  * Reshow a notification and call a callback when it is reshown.
  * @param notification
  *        The notification to reshow
  * @param callback
  *        A function to be called when the notification has been reshown
@@ -381,19 +373,19 @@ function waitForNotificationShown(notifi
   PopupNotifications.panel.addEventListener("popupshown", function onShown(e) {
     PopupNotifications.panel.removeEventListener("popupshown", onShown);
     callback();
   }, false);
   notification.reshow();
 }
 
 function promiseForNotificationShown(notification) {
-  let deferred = Promise.defer();
-  waitForNotificationShown(notification, deferred.resolve);
-  return deferred.promise;
+  return new Promise((resolve) => {
+    waitForNotificationShown(notification, resolve);
+  });
 }
 
 /**
  * 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. This trick only works if
  * there is some sort of plugin in the page.
  * @param browser
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -952,33 +952,40 @@ const CustomizableWidgets = [
       let win = aEvent.view;
       win.MailIntegration.sendLinkForBrowser(win.gBrowser.selectedBrowser)
     }
   }, {
     id: "loop-button",
     type: "custom",
     label: "loop-call-button3.label",
     tooltiptext: "loop-call-button3.tooltiptext",
+    privateBrowsingTooltiptext: "loop-call-button3-pb.tooltiptext",
     defaultArea: CustomizableUI.AREA_NAVBAR,
-    // Not in private browsing, see bug 1108187.
-    showInPrivateBrowsing: false,
     introducedInVersion: 4,
     onBuild: function(aDocument) {
       // If we're not supposed to see the button, return zip.
       if (!Services.prefs.getBoolPref("loop.enabled")) {
         return null;
       }
 
+      let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(aDocument.defaultView);
+
       let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
       node.setAttribute("id", this.id);
       node.classList.add("toolbarbutton-1");
       node.classList.add("chromeclass-toolbar-additional");
       node.classList.add("badged-button");
       node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
-      node.setAttribute("tooltiptext", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
+      if (isWindowPrivate)
+        node.setAttribute("disabled", "true");
+      let tooltiptext = isWindowPrivate ?
+        CustomizableUI.getLocalizedProperty(this, "privateBrowsingTooltiptext",
+          [CustomizableUI.getLocalizedProperty(this, "label")]) :
+        CustomizableUI.getLocalizedProperty(this, "tooltiptext");
+      node.setAttribute("tooltiptext", tooltiptext);
       node.setAttribute("removable", "true");
       node.addEventListener("command", function(event) {
         aDocument.defaultView.LoopUI.togglePanel(event);
       });
 
       return node;
     }
   }, {
--- a/browser/components/customizableui/test/browser_946320_tabs_from_other_computers.js
+++ b/browser/components/customizableui/test/browser_946320_tabs_from_other_computers.js
@@ -114,17 +114,17 @@ function configureFxAccountIdentity() {
   };
 
   let MockInternal = {
     newAccountState(credentials) {
       isnot(credentials, "not expecting credentials");
       let storageManager = new MockFxaStorageManager();
       // and init storage with our user.
       storageManager.initialize(user);
-      return new AccountState(this, storageManager);
+      return new AccountState(storageManager);
     },
     getCertificate(data, keyPair, mustBeValidUntil) {
       this.cert = {
         validUntil: this.now() + 10000,
         cert: "certificate",
       };
       return Promise.resolve(this.cert.cert);
     },
--- a/browser/components/loop/content/js/conversationViews.js
+++ b/browser/components/loop/content/js/conversationViews.js
@@ -565,23 +565,25 @@ loop.conversationViews = (function(mozL1
           )
         )
       );
     }
   });
 
   var OngoingConversationView = React.createClass({displayName: "OngoingConversationView",
     mixins: [
-      loop.store.StoreMixin("conversationStore"),
       sharedMixins.MediaSetupMixin
     ],
 
     propTypes: {
       // local
       audio: React.PropTypes.object,
+      // We pass conversationStore here rather than use the mixin, to allow
+      // easy configurability for the ui-showcase.
+      conversationStore: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       // The poster URLs are for UI-showcase testing and development.
       localPosterUrl: React.PropTypes.string,
       // This is used from the props rather than the state to make it easier for
       // the ui-showcase.
       mediaConnected: React.PropTypes.bool,
       remotePosterUrl: React.PropTypes.string,
       remoteVideoEnabled: React.PropTypes.bool,
@@ -592,17 +594,27 @@ loop.conversationViews = (function(mozL1
     getDefaultProps: function() {
       return {
         video: {enabled: true, visible: true},
         audio: {enabled: true, visible: true}
       };
     },
 
     getInitialState: function() {
-      return this.getStoreState();
+      return this.props.conversationStore.getStoreState();
+    },
+
+    componentWillMount: function() {
+      this.props.conversationStore.on("change", function() {
+        this.setState(this.props.conversationStore.getStoreState());
+      }, this);
+    },
+
+    componentWillUnmount: function() {
+      this.props.conversationStore.off("change", null, this);
     },
 
     componentDidMount: function() {
       // The SDK needs to know about the configuration and the elements to use
       // for display. So the best way seems to pass the information here - ideally
       // the sdk wouldn't need to know this, but we can't change that.
       this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
         publisherConfig: this.getDefaultPublisherConfig({
@@ -628,64 +640,79 @@ loop.conversationViews = (function(mozL1
     publishStream: function(type, enabled) {
       this.props.dispatcher.dispatch(
         new sharedActions.SetMute({
           type: type,
           enabled: enabled
         }));
     },
 
+    /**
+     * Should we render a visual cue to the user (e.g. a spinner) that a local
+     * stream is on its way from the camera?
+     *
+     * @returns {boolean}
+     * @private
+     */
+    _isLocalLoading: function () {
+      return !this.state.localSrcVideoObject && !this.props.localPosterUrl;
+    },
+
+    /**
+     * Should we render a visual cue to the user (e.g. a spinner) that a remote
+     * stream is on its way from the other user?
+     *
+     * @returns {boolean}
+     * @private
+     */
+    _isRemoteLoading: function() {
+      return !!(!this.state.remoteSrcVideoObject &&
+                !this.props.remotePosterUrl &&
+                !this.state.mediaConnected);
+    },
+
     shouldRenderRemoteVideo: function() {
       if (this.props.mediaConnected) {
         // If remote video is not enabled, we're muted, so we'll show an avatar
         // instead.
         return this.props.remoteVideoEnabled;
       }
 
       // We're not yet connected, but we don't want to show the avatar, and in
       // the common case, we'll just transition to the video.
       return true;
     },
 
     render: function() {
-      var localStreamClasses = React.addons.classSet({
-        local: true,
-        "local-stream": true,
-        "local-stream-audio": !this.props.video.enabled
-      });
-
       return (
-        React.createElement("div", {className: "video-layout-wrapper"}, 
-          React.createElement("div", {className: "conversation"}, 
-            React.createElement("div", {className: "media nested"}, 
-              React.createElement("div", {className: "video_wrapper remote_wrapper"}, 
-                React.createElement("div", {className: "video_inner remote focus-stream"}, 
-                  React.createElement(sharedViews.MediaView, {displayAvatar: !this.shouldRenderRemoteVideo(), 
-                    isLoading: false, 
-                    mediaType: "remote", 
-                    posterUrl: this.props.remotePosterUrl, 
-                    srcVideoObject: this.state.remoteSrcVideoObject})
-                )
-              ), 
-              React.createElement("div", {className: localStreamClasses}, 
-                React.createElement(sharedViews.MediaView, {displayAvatar: !this.props.video.enabled, 
-                  isLoading: false, 
-                  mediaType: "local", 
-                  posterUrl: this.props.localPosterUrl, 
-                  srcVideoObject: this.state.localSrcVideoObject})
-              )
-            ), 
-            React.createElement(loop.shared.views.ConversationToolbar, {
-              audio: this.props.audio, 
-              dispatcher: this.props.dispatcher, 
-              edit: { visible: false, enabled: false}, 
-              hangup: this.hangup, 
-              publishStream: this.publishStream, 
-              video: this.props.video})
-          )
+        React.createElement("div", {className: "desktop-call-wrapper"}, 
+          React.createElement(sharedViews.MediaLayoutView, {
+            dispatcher: this.props.dispatcher, 
+            displayScreenShare: false, 
+            isLocalLoading: this._isLocalLoading(), 
+            isRemoteLoading: this._isRemoteLoading(), 
+            isScreenShareLoading: false, 
+            localPosterUrl: this.props.localPosterUrl, 
+            localSrcVideoObject: this.state.localSrcVideoObject, 
+            localVideoMuted: !this.props.video.enabled, 
+            matchMedia: this.state.matchMedia || window.matchMedia.bind(window), 
+            remotePosterUrl: this.props.remotePosterUrl, 
+            remoteSrcVideoObject: this.state.remoteSrcVideoObject, 
+            renderRemoteVideo: this.shouldRenderRemoteVideo(), 
+            screenSharePosterUrl: null, 
+            screenShareVideoObject: this.state.screenShareVideoObject, 
+            showContextRoomName: false, 
+            useDesktopPaths: true}), 
+          React.createElement(loop.shared.views.ConversationToolbar, {
+            audio: this.props.audio, 
+            dispatcher: this.props.dispatcher, 
+            edit: { visible: false, enabled: false}, 
+            hangup: this.hangup, 
+            publishStream: this.publishStream, 
+            video: this.props.video})
         )
       );
     }
   });
 
   /**
    * Master View Controller for outgoing calls. This manages
    * the different views that need displaying.
@@ -773,16 +800,17 @@ loop.conversationViews = (function(mozL1
           return (React.createElement(CallFailedView, {
             contact: this.state.contact, 
             dispatcher: this.props.dispatcher, 
             outgoing: this.state.outgoing}));
         }
         case CALL_STATES.ONGOING: {
           return (React.createElement(OngoingConversationView, {
             audio: {enabled: !this.state.audioMuted}, 
+            conversationStore: this.getStore(), 
             dispatcher: this.props.dispatcher, 
             mediaConnected: this.state.mediaConnected, 
             remoteSrcVideoObject: this.state.remoteSrcVideoObject, 
             remoteVideoEnabled: this.state.remoteVideoEnabled, 
             video: {enabled: !this.state.videoMuted}})
           );
         }
         case CALL_STATES.FINISHED: {
--- a/browser/components/loop/content/js/conversationViews.jsx
+++ b/browser/components/loop/content/js/conversationViews.jsx
@@ -565,23 +565,25 @@ loop.conversationViews = (function(mozL1
           </div>
         </div>
       );
     }
   });
 
   var OngoingConversationView = React.createClass({
     mixins: [
-      loop.store.StoreMixin("conversationStore"),
       sharedMixins.MediaSetupMixin
     ],
 
     propTypes: {
       // local
       audio: React.PropTypes.object,
+      // We pass conversationStore here rather than use the mixin, to allow
+      // easy configurability for the ui-showcase.
+      conversationStore: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       // The poster URLs are for UI-showcase testing and development.
       localPosterUrl: React.PropTypes.string,
       // This is used from the props rather than the state to make it easier for
       // the ui-showcase.
       mediaConnected: React.PropTypes.bool,
       remotePosterUrl: React.PropTypes.string,
       remoteVideoEnabled: React.PropTypes.bool,
@@ -592,17 +594,27 @@ loop.conversationViews = (function(mozL1
     getDefaultProps: function() {
       return {
         video: {enabled: true, visible: true},
         audio: {enabled: true, visible: true}
       };
     },
 
     getInitialState: function() {
-      return this.getStoreState();
+      return this.props.conversationStore.getStoreState();
+    },
+
+    componentWillMount: function() {
+      this.props.conversationStore.on("change", function() {
+        this.setState(this.props.conversationStore.getStoreState());
+      }, this);
+    },
+
+    componentWillUnmount: function() {
+      this.props.conversationStore.off("change", null, this);
     },
 
     componentDidMount: function() {
       // The SDK needs to know about the configuration and the elements to use
       // for display. So the best way seems to pass the information here - ideally
       // the sdk wouldn't need to know this, but we can't change that.
       this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
         publisherConfig: this.getDefaultPublisherConfig({
@@ -628,64 +640,79 @@ loop.conversationViews = (function(mozL1
     publishStream: function(type, enabled) {
       this.props.dispatcher.dispatch(
         new sharedActions.SetMute({
           type: type,
           enabled: enabled
         }));
     },
 
+    /**
+     * Should we render a visual cue to the user (e.g. a spinner) that a local
+     * stream is on its way from the camera?
+     *
+     * @returns {boolean}
+     * @private
+     */
+    _isLocalLoading: function () {
+      return !this.state.localSrcVideoObject && !this.props.localPosterUrl;
+    },
+
+    /**
+     * Should we render a visual cue to the user (e.g. a spinner) that a remote
+     * stream is on its way from the other user?
+     *
+     * @returns {boolean}
+     * @private
+     */
+    _isRemoteLoading: function() {
+      return !!(!this.state.remoteSrcVideoObject &&
+                !this.props.remotePosterUrl &&
+                !this.state.mediaConnected);
+    },
+
     shouldRenderRemoteVideo: function() {
       if (this.props.mediaConnected) {
         // If remote video is not enabled, we're muted, so we'll show an avatar
         // instead.
         return this.props.remoteVideoEnabled;
       }
 
       // We're not yet connected, but we don't want to show the avatar, and in
       // the common case, we'll just transition to the video.
       return true;
     },
 
     render: function() {
-      var localStreamClasses = React.addons.classSet({
-        local: true,
-        "local-stream": true,
-        "local-stream-audio": !this.props.video.enabled
-      });
-
       return (
-        <div className="video-layout-wrapper">
-          <div className="conversation">
-            <div className="media nested">
-              <div className="video_wrapper remote_wrapper">
-                <div className="video_inner remote focus-stream">
-                  <sharedViews.MediaView displayAvatar={!this.shouldRenderRemoteVideo()}
-                    isLoading={false}
-                    mediaType="remote"
-                    posterUrl={this.props.remotePosterUrl}
-                    srcVideoObject={this.state.remoteSrcVideoObject} />
-                </div>
-              </div>
-              <div className={localStreamClasses}>
-                <sharedViews.MediaView displayAvatar={!this.props.video.enabled}
-                  isLoading={false}
-                  mediaType="local"
-                  posterUrl={this.props.localPosterUrl}
-                  srcVideoObject={this.state.localSrcVideoObject} />
-              </div>
-            </div>
-            <loop.shared.views.ConversationToolbar
-              audio={this.props.audio}
-              dispatcher={this.props.dispatcher}
-              edit={{ visible: false, enabled: false }}
-              hangup={this.hangup}
-              publishStream={this.publishStream}
-              video={this.props.video} />
-          </div>
+        <div className="desktop-call-wrapper">
+          <sharedViews.MediaLayoutView
+            dispatcher={this.props.dispatcher}
+            displayScreenShare={false}
+            isLocalLoading={this._isLocalLoading()}
+            isRemoteLoading={this._isRemoteLoading()}
+            isScreenShareLoading={false}
+            localPosterUrl={this.props.localPosterUrl}
+            localSrcVideoObject={this.state.localSrcVideoObject}
+            localVideoMuted={!this.props.video.enabled}
+            matchMedia={this.state.matchMedia || window.matchMedia.bind(window)}
+            remotePosterUrl={this.props.remotePosterUrl}
+            remoteSrcVideoObject={this.state.remoteSrcVideoObject}
+            renderRemoteVideo={this.shouldRenderRemoteVideo()}
+            screenSharePosterUrl={null}
+            screenShareVideoObject={this.state.screenShareVideoObject}
+            showContextRoomName={false}
+            useDesktopPaths={true} />
+          <loop.shared.views.ConversationToolbar
+            audio={this.props.audio}
+            dispatcher={this.props.dispatcher}
+            edit={{ visible: false, enabled: false }}
+            hangup={this.hangup}
+            publishStream={this.publishStream}
+            video={this.props.video} />
         </div>
       );
     }
   });
 
   /**
    * Master View Controller for outgoing calls. This manages
    * the different views that need displaying.
@@ -773,16 +800,17 @@ loop.conversationViews = (function(mozL1
           return (<CallFailedView
             contact={this.state.contact}
             dispatcher={this.props.dispatcher}
             outgoing={this.state.outgoing} />);
         }
         case CALL_STATES.ONGOING: {
           return (<OngoingConversationView
             audio={{enabled: !this.state.audioMuted}}
+            conversationStore={this.getStore()}
             dispatcher={this.props.dispatcher}
             mediaConnected={this.state.mediaConnected}
             remoteSrcVideoObject={this.state.remoteSrcVideoObject}
             remoteVideoEnabled={this.state.remoteVideoEnabled}
             video={{enabled: !this.state.videoMuted}} />
           );
         }
         case CALL_STATES.FINISHED: {
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -660,29 +660,29 @@ loop.roomViews = (function(mozL10n) {
 
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a local
      * stream is on its way from the camera?
      *
      * @returns {boolean}
      * @private
      */
-    _shouldRenderLocalLoading: function () {
+    _isLocalLoading: function () {
       return this.state.roomState === ROOM_STATES.MEDIA_WAIT &&
              !this.state.localSrcVideoObject;
     },
 
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a remote
      * stream is on its way from the other user?
      *
      * @returns {boolean}
      * @private
      */
-    _shouldRenderRemoteLoading: function() {
+    _isRemoteLoading: function() {
       return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
                 !this.state.remoteSrcVideoObject &&
                 !this.state.mediaConnected);
     },
 
     handleAddContextClick: function() {
       this.setState({ showEditContext: true });
     },
@@ -736,73 +736,64 @@ loop.roomViews = (function(mozL10n) {
           );
         }
         case ROOM_STATES.ENDED: {
           // When conversation ended we either display a feedback form or
           // close the window. This is decided in the AppControllerView.
           return null;
         }
         default: {
-
           return (
-            React.createElement("div", {className: "room-conversation-wrapper"}, 
-              React.createElement("div", {className: "video-layout-wrapper"}, 
-                React.createElement("div", {className: "conversation room-conversation"}, 
-                  React.createElement("div", {className: "media nested"}, 
-                    React.createElement(DesktopRoomInvitationView, {
-                      dispatcher: this.props.dispatcher, 
-                      error: this.state.error, 
-                      mozLoop: this.props.mozLoop, 
-                      onAddContextClick: this.handleAddContextClick, 
-                      onEditContextClose: this.handleEditContextClose, 
-                      roomData: roomData, 
-                      savingContext: this.state.savingContext, 
-                      show: shouldRenderInvitationOverlay, 
-                      showEditContext: shouldRenderInvitationOverlay && shouldRenderEditContextView, 
-                      socialShareProviders: this.state.socialShareProviders}), 
-                    React.createElement("div", {className: "video_wrapper remote_wrapper"}, 
-                      React.createElement("div", {className: "video_inner remote focus-stream"}, 
-                        React.createElement(sharedViews.MediaView, {displayAvatar: !this.shouldRenderRemoteVideo(), 
-                          isLoading: this._shouldRenderRemoteLoading(), 
-                          mediaType: "remote", 
-                          posterUrl: this.props.remotePosterUrl, 
-                          srcVideoObject: this.state.remoteSrcVideoObject})
-                      )
-                    ), 
-                    React.createElement("div", {className: localStreamClasses}, 
-                      React.createElement(sharedViews.MediaView, {displayAvatar: this.state.videoMuted, 
-                        isLoading: this._shouldRenderLocalLoading(), 
-                        mediaType: "local", 
-                        posterUrl: this.props.localPosterUrl, 
-                        srcVideoObject: this.state.localSrcVideoObject})
-                    ), 
-                    React.createElement(DesktopRoomEditContextView, {
-                      dispatcher: this.props.dispatcher, 
-                      error: this.state.error, 
-                      mozLoop: this.props.mozLoop, 
-                      onClose: this.handleEditContextClose, 
-                      roomData: roomData, 
-                      savingContext: this.state.savingContext, 
-                      show: !shouldRenderInvitationOverlay && shouldRenderEditContextView})
-                  ), 
-                  React.createElement(sharedViews.ConversationToolbar, {
-                    audio: {enabled: !this.state.audioMuted, visible: true}, 
-                    dispatcher: this.props.dispatcher, 
-                    edit: { visible: this.state.contextEnabled, enabled: !this.state.showEditContext}, 
-                    hangup: this.leaveRoom, 
-                    onEditClick: this.handleEditContextClick, 
-                    publishStream: this.publishStream, 
-                    screenShare: screenShareData, 
-                    video: {enabled: !this.state.videoMuted, visible: true}})
-                )
+            React.createElement("div", {className: "room-conversation-wrapper desktop-room-wrapper"}, 
+              React.createElement(sharedViews.MediaLayoutView, {
+                dispatcher: this.props.dispatcher, 
+                displayScreenShare: false, 
+                isLocalLoading: this._isLocalLoading(), 
+                isRemoteLoading: this._isRemoteLoading(), 
+                isScreenShareLoading: false, 
+                localPosterUrl: this.props.localPosterUrl, 
+                localSrcVideoObject: this.state.localSrcVideoObject, 
+                localVideoMuted: this.state.videoMuted, 
+                matchMedia: this.state.matchMedia || window.matchMedia.bind(window), 
+                remotePosterUrl: this.props.remotePosterUrl, 
+                remoteSrcVideoObject: this.state.remoteSrcVideoObject, 
+                renderRemoteVideo: this.shouldRenderRemoteVideo(), 
+                screenSharePosterUrl: null, 
+                screenShareVideoObject: this.state.screenShareVideoObject, 
+                showContextRoomName: false, 
+                useDesktopPaths: true}, 
+                React.createElement(DesktopRoomInvitationView, {
+                  dispatcher: this.props.dispatcher, 
+                  error: this.state.error, 
+                  mozLoop: this.props.mozLoop, 
+                  onAddContextClick: this.handleAddContextClick, 
+                  onEditContextClose: this.handleEditContextClose, 
+                  roomData: roomData, 
+                  savingContext: this.state.savingContext, 
+                  show: shouldRenderInvitationOverlay, 
+                  showEditContext: shouldRenderInvitationOverlay && shouldRenderEditContextView, 
+                  socialShareProviders: this.state.socialShareProviders}), 
+                React.createElement(DesktopRoomEditContextView, {
+                  dispatcher: this.props.dispatcher, 
+                  error: this.state.error, 
+                  mozLoop: this.props.mozLoop, 
+                  onClose: this.handleEditContextClose, 
+                  roomData: roomData, 
+                  savingContext: this.state.savingContext, 
+                  show: !shouldRenderInvitationOverlay && shouldRenderEditContextView})
               ), 
-              React.createElement(sharedViews.chat.TextChatView, {
+              React.createElement(sharedViews.ConversationToolbar, {
+                audio: {enabled: !this.state.audioMuted, visible: true}, 
                 dispatcher: this.props.dispatcher, 
-                showRoomName: false, 
-                useDesktopPaths: true})
+                edit: { visible: this.state.contextEnabled, enabled: !this.state.showEditContext}, 
+                hangup: this.leaveRoom, 
+                onEditClick: this.handleEditContextClick, 
+                publishStream: this.publishStream, 
+                screenShare: screenShareData, 
+                video: {enabled: !this.state.videoMuted, visible: true}})
             )
           );
         }
       }
     }
   });
 
   return {
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -660,29 +660,29 @@ loop.roomViews = (function(mozL10n) {
 
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a local
      * stream is on its way from the camera?
      *
      * @returns {boolean}
      * @private
      */
-    _shouldRenderLocalLoading: function () {
+    _isLocalLoading: function () {
       return this.state.roomState === ROOM_STATES.MEDIA_WAIT &&
              !this.state.localSrcVideoObject;
     },
 
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a remote
      * stream is on its way from the other user?
      *
      * @returns {boolean}
      * @private
      */
-    _shouldRenderRemoteLoading: function() {
+    _isRemoteLoading: function() {
       return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
                 !this.state.remoteSrcVideoObject &&
                 !this.state.mediaConnected);
     },
 
     handleAddContextClick: function() {
       this.setState({ showEditContext: true });
     },
@@ -736,73 +736,64 @@ loop.roomViews = (function(mozL10n) {
           );
         }
         case ROOM_STATES.ENDED: {
           // When conversation ended we either display a feedback form or
           // close the window. This is decided in the AppControllerView.
           return null;
         }
         default: {
-
           return (
-            <div className="room-conversation-wrapper">
-              <div className="video-layout-wrapper">
-                <div className="conversation room-conversation">
-                  <div className="media nested">
-                    <DesktopRoomInvitationView
-                      dispatcher={this.props.dispatcher}
-                      error={this.state.error}
-                      mozLoop={this.props.mozLoop}
-                      onAddContextClick={this.handleAddContextClick}
-                      onEditContextClose={this.handleEditContextClose}
-                      roomData={roomData}
-                      savingContext={this.state.savingContext}
-                      show={shouldRenderInvitationOverlay}
-                      showEditContext={shouldRenderInvitationOverlay && shouldRenderEditContextView}
-                      socialShareProviders={this.state.socialShareProviders} />
-                    <div className="video_wrapper remote_wrapper">
-                      <div className="video_inner remote focus-stream">
-                        <sharedViews.MediaView displayAvatar={!this.shouldRenderRemoteVideo()}
-                          isLoading={this._shouldRenderRemoteLoading()}
-                          mediaType="remote"
-                          posterUrl={this.props.remotePosterUrl}
-                          srcVideoObject={this.state.remoteSrcVideoObject} />
-                      </div>
-                    </div>
-                    <div className={localStreamClasses}>
-                      <sharedViews.MediaView displayAvatar={this.state.videoMuted}
-                        isLoading={this._shouldRenderLocalLoading()}
-                        mediaType="local"
-                        posterUrl={this.props.localPosterUrl}
-                        srcVideoObject={this.state.localSrcVideoObject} />
-                    </div>
-                    <DesktopRoomEditContextView
-                      dispatcher={this.props.dispatcher}
-                      error={this.state.error}
-                      mozLoop={this.props.mozLoop}
-                      onClose={this.handleEditContextClose}
-                      roomData={roomData}
-                      savingContext={this.state.savingContext}
-                      show={!shouldRenderInvitationOverlay && shouldRenderEditContextView} />
-                  </div>
-                  <sharedViews.ConversationToolbar
-                    audio={{enabled: !this.state.audioMuted, visible: true}}
-                    dispatcher={this.props.dispatcher}
-                    edit={{ visible: this.state.contextEnabled, enabled: !this.state.showEditContext }}
-                    hangup={this.leaveRoom}
-                    onEditClick={this.handleEditContextClick}
-                    publishStream={this.publishStream}
-                    screenShare={screenShareData}
-                    video={{enabled: !this.state.videoMuted, visible: true}} />
-                </div>
-              </div>
-              <sharedViews.chat.TextChatView
+            <div className="room-conversation-wrapper desktop-room-wrapper">
+              <sharedViews.MediaLayoutView
                 dispatcher={this.props.dispatcher}
-                showRoomName={false}
-                useDesktopPaths={true} />
+                displayScreenShare={false}
+                isLocalLoading={this._isLocalLoading()}
+                isRemoteLoading={this._isRemoteLoading()}
+                isScreenShareLoading={false}
+                localPosterUrl={this.props.localPosterUrl}
+                localSrcVideoObject={this.state.localSrcVideoObject}
+                localVideoMuted={this.state.videoMuted}
+                matchMedia={this.state.matchMedia || window.matchMedia.bind(window)}
+                remotePosterUrl={this.props.remotePosterUrl}
+                remoteSrcVideoObject={this.state.remoteSrcVideoObject}
+                renderRemoteVideo={this.shouldRenderRemoteVideo()}
+                screenSharePosterUrl={null}
+                screenShareVideoObject={this.state.screenShareVideoObject}
+                showContextRoomName={false}
+                useDesktopPaths={true}>
+                <DesktopRoomInvitationView
+                  dispatcher={this.props.dispatcher}
+                  error={this.state.error}
+                  mozLoop={this.props.mozLoop}
+                  onAddContextClick={this.handleAddContextClick}
+                  onEditContextClose={this.handleEditContextClose}
+                  roomData={roomData}
+                  savingContext={this.state.savingContext}
+                  show={shouldRenderInvitationOverlay}
+                  showEditContext={shouldRenderInvitationOverlay && shouldRenderEditContextView}
+                  socialShareProviders={this.state.socialShareProviders} />
+                <DesktopRoomEditContextView
+                  dispatcher={this.props.dispatcher}
+                  error={this.state.error}
+                  mozLoop={this.props.mozLoop}
+                  onClose={this.handleEditContextClose}
+                  roomData={roomData}
+                  savingContext={this.state.savingContext}
+                  show={!shouldRenderInvitationOverlay && shouldRenderEditContextView} />
+              </sharedViews.MediaLayoutView>
+              <sharedViews.ConversationToolbar
+                audio={{enabled: !this.state.audioMuted, visible: true}}
+                dispatcher={this.props.dispatcher}
+                edit={{ visible: this.state.contextEnabled, enabled: !this.state.showEditContext }}
+                hangup={this.leaveRoom}
+                onEditClick={this.handleEditContextClick}
+                publishStream={this.publishStream}
+                screenShare={screenShareData}
+                video={{enabled: !this.state.videoMuted, visible: true}} />
             </div>
           );
         }
       }
     }
   });
 
   return {
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -499,38 +499,16 @@
 
 .feedback .info {
   display: block;
   font-size: 10px;
   color: #CCC;
   text-align: center;
 }
 
-.fx-embedded .local-stream {
-  position: absolute;
-  right: 3px;
-  bottom: 5px;
-  /* next two lines are workaround for lack of object-fit; see bug 1020445 */
-  max-width: 140px;
-  width: 30%;
-  height: 28%;
-  max-height: 105px;
-}
-
-.fx-embedded .local-stream.room-preview {
-  top: 0px;
-  left: 0px;
-  right: 0px;
-  bottom: 0px;
-  height: 100%;
-  width: 100%;
-  max-width: none;
-  max-height: none;
-}
-
 .conversation .media.nested .focus-stream {
   display: inline-block;
   position: absolute; /* workaround for lack of object-fit; see bug 1020445 */
   width: 100%;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
@@ -587,25 +565,21 @@
   }
 }
 
 .conversation .local .avatar {
   position: absolute;
   z-index: 1;
 }
 
-.remote .avatar {
+.remote > .avatar {
   /* make visually distinct from local avatar */
   opacity: 0.25;
 }
 
-.fx-embedded .media.nested {
-  min-height: 200px;
-}
-
 .fx-embedded-call-identifier {
   display: inline;
   width: 100%;
   padding: 1.2em;
 }
 
 .fx-embedded-call-identifier-item {
   height: 50px;
@@ -670,17 +644,19 @@
   }
 
 /* Force full height on all parents up to the video elements
  * this way we can ensure the aspect ratio and use height 100%
  * on the video element
  * */
 html, .fx-embedded, #main,
 .video-layout-wrapper,
-.conversation {
+.conversation,
+.desktop-call-wrapper,
+.desktop-room-wrapper {
   height: 100%;
 }
 
 /* We use 641px rather than 640, as min-width and max-width are inclusive */
 @media screen and (min-width: 641px) {
   .standalone .conversation .conversation-toolbar {
     position: absolute;
     bottom: 0;
@@ -930,33 +906,32 @@ body[platform="win"] .share-service-drop
   background-color: #E8F6FE;
 }
 
 .room-context {
   background: rgba(0,0,0,.8);
   border-top: 2px solid #444;
   border-bottom: 2px solid #444;
   padding: .5rem;
-  max-height: 400px;
   position: absolute;
   left: 0;
   bottom: 0;
   width: 100%;
   /* Stretch to the maximum available space whilst not covering the conversation
      toolbar (26px). */
   height: calc(100% - 26px);
   font-size: .9em;
   display: flex;
   flex-flow: column nowrap;
   align-content: flex-start;
   align-items: flex-start;
   overflow-x: hidden;
   overflow-y: auto;
   /* Make the context view float atop the video elements. */
-  z-index: 2;
+  z-index: 3;
 }
 
 .room-invitation-overlay .room-context {
   position: relative;
   left: auto;
   bottom: auto;
   flex: 0 1 auto;
   height: 100%;
@@ -1082,22 +1057,22 @@ html[dir="rtl"] .room-context-btn-close 
 
 .media-layout {
   height: 100%;
 }
 
 .standalone-room-wrapper > .media-layout {
   /* 50px is the header, 64px for toolbar, 3em is the footer. */
   height: calc(100% - 50px - 64px - 3em);
+  margin: 0 10px;
 }
 
 .media-layout > .media-wrapper {
   display: flex;
   flex-flow: column wrap;
-  margin: 0 10px;
   height: 100%;
 }
 
 .media-wrapper > .focus-stream {
   /* We want this to be the width, minus 200px which is for the right-side text
      chat and video displays. */
   width: calc(100% - 200px);
   /* 100% height to fill up media-layout, thus forcing other elements into the
@@ -1134,16 +1109,24 @@ html[dir="rtl"] .room-context-btn-close 
 }
 
 .media-wrapper.showing-local-streams.receiving-screen-share > .text-chat-view {
   /* When we're displaying the local streams, then we need to make the text
      chat view a bit shorter to give room. */
   height: calc(100% - 300px);
 }
 
+.desktop-call-wrapper > .media-layout > .media-wrapper > .text-chat-view,
+.desktop-room-wrapper > .media-layout > .media-wrapper > .text-chat-view {
+  /* Account for height of .conversation-toolbar on desktop */
+  /* When we change the toolbar in bug 1184559 we can remove this. */
+  margin-top: 26px;
+  height: calc(100% - 150px - 26px);
+}
+
 /* Temporarily slaved from .media-wrapper until we use it in more places
    to avoid affecting the conversation window on desktop. */
 .media-wrapper > .text-chat-view > .text-chat-entries {
   /* 40px is the height of .text-chat-box. */
   height: calc(100% - 40px);
 }
 
 .media-wrapper > .text-chat-disabled > .text-chat-entries {
@@ -1199,41 +1182,47 @@ html[dir="rtl"] .room-context-btn-close 
   }
 
   .media-wrapper.receiving-screen-share > .focus-stream {
     height: 50%;
   }
 
   /* Temporarily slaved from .media-wrapper until we use it in more places
      to avoid affecting the conversation window on desktop. */
-  .media-wrapper > .text-chat-view > .text-chat-entries {
+  .text-chat-view > .text-chat-entries {
     /* 40px is the height of .text-chat-box. */
     height: calc(100% - 40px);
     width: 100%;
   }
 
   .media-wrapper > .text-chat-disabled > .text-chat-entries {
     /* When text chat is disabled, the entries box should be 100% height. */
     height: 100%;
   }
 
-  .media-wrapper > .local {
+  .media-wrapper > .focus-stream > .local {
     /* Position over the remote video */
     position: absolute;
     /* Make sure its on top */
-    z-index: 1001;
+    z-index: 2;
     margin: 3px;
     right: 0;
     /* 29px is (30% of 50px high header) + (height toolbar (38px) +
        height footer (25px) - height header (50px)) */
-    bottom: calc(30% + 29px);
+    bottom: 0;
     width: 120px;
     height: 120px;
   }
 
+  .standalone-room-wrapper > .media-layout > .media-wrapper > .local {
+    /* Add 10px for the margin on standalone */
+    right: 10px;
+  }
+
+
   html[dir="rtl"] .media-wrapper > .local {
     right: auto;
     left: 0;
   }
 
   .media-wrapper > .text-chat-view {
     order: 3;
     flex: 1 1 auto;
@@ -1242,16 +1231,25 @@ html[dir="rtl"] .room-context-btn-close 
 
   .media-wrapper > .text-chat-view,
   .media-wrapper.showing-local-streams > .text-chat-view,
   .media-wrapper.showing-local-streams.receiving-screen-share > .text-chat-view {
     /* The remaining 30% that the .focus-stream doesn't use. */
     height: 30%;
   }
 
+  .desktop-call-wrapper > .media-layout > .media-wrapper > .text-chat-view,
+  .desktop-room-wrapper > .media-layout > .media-wrapper > .text-chat-view {
+    /* When we change the toolbar in bug 1184559 we can remove this. */
+    /* Reset back to 0 for .conversation-toolbar override on desktop */
+    margin-top: 0;
+    /* This is temp, to echo the .media-wrapper > .text-chat-view above */
+    height: 30%;
+  }
+
   .media-wrapper.receiving-screen-share > .screen {
     order: 1;
   }
 
   .media-wrapper.receiving-screen-share > .remote {
     /* Screen shares have remote & local video side-by-side on narrow screens */
     order: 2;
     flex: 1 1 auto;
@@ -1283,16 +1281,57 @@ html[dir="rtl"] .room-context-btn-close 
     margin: 0;
   }
 
   .media-wrapper.receiving-screen-share > .text-chat-view {
     order: 4;
   }
 }
 
+/* e.g. very narrow widths similar to conversation window */
+@media screen and (max-width:300px) {
+  .media-layout > .media-wrapper {
+    flex-flow: column nowrap;
+  }
+
+  .media-wrapper > .focus-stream > .local {
+    position: absolute;
+    right: 0;
+    /* 30% is the height of the text chat. As we have a margin,
+       we don't need to worry about any offset for a border */
+    bottom: 0;
+    margin: 3px;
+    object-fit: contain;
+    /* These make the avatar look reasonable and the local
+       video not too big */
+    width: 25%;
+    height: 25%;
+  }
+
+  .media-wrapper:not(.showing-remote-streams) > .focus-stream > .no-video {
+    display: none;
+  }
+
+  .media-wrapper:not(.showing-remote-streams) > .focus-stream > .local {
+    position: relative;
+    margin: 0;
+    right: auto;
+    left: auto;
+    bottom: auto;
+    width: 100%;
+    height: 100%;
+    background-color: black;
+  }
+
+  .media-wrapper > .focus-stream {
+    flex: 1 1 auto;
+    height: auto;
+  }
+}
+
 .standalone > #main > .room-conversation-wrapper > .media-layout > .conversation-toolbar {
   border: none;
 }
 
 /* Standalone rooms */
 
 .standalone .room-conversation-wrapper {
   position: relative;
@@ -1410,47 +1449,22 @@ html[dir="rtl"] .standalone .room-conver
   display: block;
 }
 
 .standalone .room-conversation-wrapper .ended-conversation {
   position: relative;
   height: auto;
 }
 
-/* Text chat in rooms styles */
-
-.fx-embedded .room-conversation-wrapper {
-  display: flex;
-  flex-flow: column nowrap;
-}
-
-.fx-embedded .video-layout-wrapper {
-  flex: 1 1 auto;
-}
+/* Text chat in styles */
 
 .text-chat-view {
   background: white;
 }
 
-.fx-embedded .text-chat-view {
-  flex: 1 0 auto;
-  display: flex;
-  flex-flow: column nowrap;
-}
-
-.fx-embedded .text-chat-entries {
-  flex: 1 1 auto;
-  max-height: 120px;
-  min-height: 60px;
-}
-
-.fx-embedded .text-chat-view > .text-chat-entries-empty {
-  display: none;
-}
-
 .text-chat-box {
   flex: 0 0 auto;
   max-height: 40px;
   min-height: 40px;
   width: 100%;
 }
 
 .text-chat-entries {
@@ -1735,16 +1749,57 @@ html[dir="rtl"] .text-chat-entry.receive
   }
 
   .standalone .media.nested {
     /* This forces the remote video stream to fit within wrapper's height */
     min-height: 0px;
   }
 }
 
+/* e.g. very narrow widths similar to conversation window */
+@media screen and (max-width:300px) {
+  .text-chat-view {
+    flex: 0 0 auto;
+    display: flex;
+    flex-flow: column nowrap;
+    /* 120px max-height of .text-chat-entries plus 40px of .text-chat-box */
+    max-height: 160px;
+    /* 60px min-height of .text-chat-entries plus 40px of .text-chat-box */
+    min-height: 100px;
+    /* The !important is to override the values defined above which have more
+       specificity when we fix bug 1184559, we should be able to remove it,
+       but this should be tests first. */
+    height: auto !important;
+  }
+
+  .text-chat-entries {
+    /* The !important is to override the values defined above which have more
+       specificity when we fix bug 1184559, we should be able to remove it,
+       but this should be tests first. */
+    flex: 1 1 auto !important;
+    max-height: 120px;
+    min-height: 60px;
+  }
+
+  .text-chat-entries-empty.text-chat-disabled {
+    display: none;
+  }
+
+  /* When the text chat entries are not present, then hide the entries view
+     and just show the chat box. */
+  .text-chat-entries-empty {
+    max-height: 40px;
+    min-height: 40px;
+  }
+
+  .text-chat-entries-empty > .text-chat-entries {
+    display: none;
+  }
+}
+
 .self-view-hidden-message {
   /* Not displayed by default; display is turned on elsewhere when the
    * self-view is actually hidden.
    */
   display: none;
 }
 
 /* Avoid the privacy problem where a user can size the window so small that
--- a/browser/components/loop/content/shared/js/activeRoomStore.js
+++ b/browser/components/loop/content/shared/js/activeRoomStore.js
@@ -575,31 +575,16 @@ loop.store.ActiveRoomStore = (function()
     },
 
     /**
      * Handles disconnection of this local client from the sdk servers.
      *
      * @param {sharedActions.ConnectionFailure} actionData
      */
     connectionFailure: function(actionData) {
-      /**
-       * XXX This is a workaround for desktop machines that do not have a
-       * camera installed. As we don't yet have device enumeration, when
-       * we do, this can be removed (bug 1138851), and the sdk should handle it.
-       */
-      if (this._isDesktop &&
-          actionData.reason === FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA &&
-          this.getStoreState().videoMuted === false) {
-        // We failed to publish with media, so due to the bug, we try again without
-        // video.
-        this.setStoreState({videoMuted: true});
-        this._sdkDriver.retryPublishWithoutVideo();
-        return;
-      }
-
       var exitState = this._storeState.roomState === ROOM_STATES.FAILED ?
         this._storeState.failureExitState : this._storeState.roomState;
 
       // Treat all reasons as something failed. In theory, clientDisconnected
       // could be a success case, but there's no way we should be intentionally
       // sending that and still have the window open.
       this.setStoreState({
         failureReason: actionData.reason,
--- a/browser/components/loop/content/shared/js/conversationStore.js
+++ b/browser/components/loop/content/shared/js/conversationStore.js
@@ -141,31 +141,16 @@ loop.store = loop.store || {};
 
     /**
      * Handles the connection failure action, setting the state to
      * terminated.
      *
      * @param {sharedActions.ConnectionFailure} actionData The action data.
      */
     connectionFailure: function(actionData) {
-      /**
-       * XXX This is a workaround for desktop machines that do not have a
-       * camera installed. As we don't yet have device enumeration, when
-       * we do, this can be removed (bug 1138851), and the sdk should handle it.
-       */
-      if (this._isDesktop &&
-          actionData.reason === FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA &&
-          this.getStoreState().videoMuted === false) {
-        // We failed to publish with media, so due to the bug, we try again without
-        // video.
-        this.setStoreState({videoMuted: true});
-        this.sdkDriver.retryPublishWithoutVideo();
-        return;
-      }
-
       this._endSession();
       this.setStoreState({
         callState: CALL_STATES.TERMINATED,
         callStateReason: actionData.reason
       });
     },
 
     /**
--- a/browser/components/loop/content/shared/js/otSdkDriver.js
+++ b/browser/components/loop/content/shared/js/otSdkDriver.js
@@ -56,25 +56,36 @@ loop.OTSdkDriver = (function() {
     // about:config, or use
     //
     // localStorage.setItem("debug.twoWayMediaTelemetry", true);
     this._debugTwoWayMediaTelemetry =
       loop.shared.utils.getBoolPreference("debug.twoWayMediaTelemetry");
 
     /**
      * XXX This is a workaround for desktop machines that do not have a
-     * camera installed. As we don't yet have device enumeration, when
-     * we do, this can be removed (bug 1138851), and the sdk should handle it.
+     * camera installed. The SDK doesn't currently do use the new device
+     * enumeration apis, when it does (bug 1138851), we can drop this part.
      */
-    if (this._isDesktop && !window.MediaStreamTrack.getSources) {
+    if (this._isDesktop) {
       // If there's no getSources function, the sdk defines its own and caches
-      // the result. So here we define the "normal" one which doesn't get cached, so
-      // we can change it later.
+      // the result. So here we define our own one which wraps around the
+      // real device enumeration api.
       window.MediaStreamTrack.getSources = function(callback) {
-        callback([{kind: "audio"}, {kind: "video"}]);
+        navigator.mediaDevices.enumerateDevices().then(function(devices) {
+          var result = [];
+          devices.forEach(function(device) {
+            if (device.kind === "audioinput") {
+              result.push({kind: "audio"});
+            }
+            if (device.kind === "videoinput") {
+              result.push({kind: "video"});
+            }
+          });
+          callback(result);
+        });
       };
     }
   };
 
   OTSdkDriver.prototype = {
     /**
      * Clones the publisher config into a new object, as the sdk modifies the
      * properties object.
@@ -104,54 +115,35 @@ loop.OTSdkDriver = (function() {
      * @param {sharedActions.SetupStreamElements} actionData The data associated
      *   with the action. See action.js.
      */
     setupStreamElements: function(actionData) {
       this.publisherConfig = actionData.publisherConfig;
 
       this.sdk.on("exception", this._onOTException.bind(this));
 
-      // At this state we init the publisher, even though we might be waiting for
-      // the initial connect of the session. This saves time when setting up
-      // the media.
-      this._publishLocalStreams();
-    },
-
-    /**
-     * Internal function to publish a local stream.
-     * XXX This can be simplified when bug 1138851 is actioned.
-     */
-    _publishLocalStreams: function() {
       // We expect the local video to be muted automatically by the SDK. Hence
       // we don't mute it manually here.
       this._mockPublisherEl = document.createElement("div");
 
+      // At this state we init the publisher, even though we might be waiting for
+      // the initial connect of the session. This saves time when setting up
+      // the media.
       this.publisher = this.sdk.initPublisher(this._mockPublisherEl,
         _.extend(this._getDataChannelSettings, this._getCopyPublisherConfig));
 
       this.publisher.on("streamCreated", this._onLocalStreamCreated.bind(this));
       this.publisher.on("streamDestroyed", this._onLocalStreamDestroyed.bind(this));
       this.publisher.on("accessAllowed", this._onPublishComplete.bind(this));
       this.publisher.on("accessDenied", this._onPublishDenied.bind(this));
       this.publisher.on("accessDialogOpened",
         this._onAccessDialogOpened.bind(this));
     },
 
     /**
-     * Forces the sdk into not using video, and starts publishing again.
-     * XXX This is part of the work around that will be removed by bug 1138851.
-     */
-    retryPublishWithoutVideo: function() {
-      window.MediaStreamTrack.getSources = function(callback) {
-        callback([{kind: "audio"}]);
-      };
-      this._publishLocalStreams();
-    },
-
-    /**
      * Handles the setMute action. Informs the published stream to mute
      * or unmute audio as appropriate.
      *
      * @param {sharedActions.SetMute} actionData The data associated with the
      *                                           action. See action.js.
      */
     setMute: function(actionData) {
       if (actionData.type === "audio") {
--- a/browser/components/loop/content/shared/js/textChatView.js
+++ b/browser/components/loop/content/shared/js/textChatView.js
@@ -145,18 +145,17 @@ loop.shared.views.chat = (function(mozL1
       }
     },
 
     render: function() {
       /* Keep track of the last printed timestamp. */
       var lastTimestamp = 0;
 
       var entriesClasses = React.addons.classSet({
-        "text-chat-entries": true,
-        "text-chat-entries-empty": !this.props.messageList.length
+        "text-chat-entries": true
       });
 
       return (
         React.createElement("div", {className: entriesClasses}, 
           React.createElement("div", {className: "text-chat-scroller"}, 
             
               this.props.messageList.map(function(entry, i) {
                 if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
@@ -377,17 +376,18 @@ loop.shared.views.chat = (function(mozL1
           return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
             item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME;
         });
         hasNonSpecialMessages = !!messageList.length;
       }
 
       var textChatViewClasses = React.addons.classSet({
         "text-chat-view": true,
-        "text-chat-disabled": !this.state.textChatEnabled
+        "text-chat-disabled": !this.state.textChatEnabled,
+        "text-chat-entries-empty": !messageList.length
       });
 
       return (
         React.createElement("div", {className: textChatViewClasses}, 
           React.createElement(TextChatEntriesView, {
             dispatcher: this.props.dispatcher, 
             messageList: messageList, 
             useDesktopPaths: this.props.useDesktopPaths}), 
--- a/browser/components/loop/content/shared/js/textChatView.jsx
+++ b/browser/components/loop/content/shared/js/textChatView.jsx
@@ -145,18 +145,17 @@ loop.shared.views.chat = (function(mozL1
       }
     },
 
     render: function() {
       /* Keep track of the last printed timestamp. */
       var lastTimestamp = 0;
 
       var entriesClasses = React.addons.classSet({
-        "text-chat-entries": true,
-        "text-chat-entries-empty": !this.props.messageList.length
+        "text-chat-entries": true
       });
 
       return (
         <div className={entriesClasses}>
           <div className="text-chat-scroller">
             {
               this.props.messageList.map(function(entry, i) {
                 if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
@@ -377,17 +376,18 @@ loop.shared.views.chat = (function(mozL1
           return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
             item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME;
         });
         hasNonSpecialMessages = !!messageList.length;
       }
 
       var textChatViewClasses = React.addons.classSet({
         "text-chat-view": true,
-        "text-chat-disabled": !this.state.textChatEnabled
+        "text-chat-disabled": !this.state.textChatEnabled,
+        "text-chat-entries-empty": !messageList.length
       });
 
       return (
         <div className={textChatViewClasses}>
           <TextChatEntriesView
             dispatcher={this.props.dispatcher}
             messageList={messageList}
             useDesktopPaths={this.props.useDesktopPaths} />
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -941,83 +941,141 @@ loop.shared.views = (function(_, mozL10n
                {className: this.props.mediaType + "-video", 
                muted: true}))
       );
     }
   });
 
   var MediaLayoutView = React.createClass({displayName: "MediaLayoutView",
     propTypes: {
+      children: React.PropTypes.node,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       displayScreenShare: React.PropTypes.bool.isRequired,
       isLocalLoading: React.PropTypes.bool.isRequired,
       isRemoteLoading: React.PropTypes.bool.isRequired,
       isScreenShareLoading: React.PropTypes.bool.isRequired,
       // The poster URLs are for UI-showcase testing and development.
       localPosterUrl: React.PropTypes.string,
       localSrcVideoObject: React.PropTypes.object,
       localVideoMuted: React.PropTypes.bool.isRequired,
+      // Passing in matchMedia, allows it to be overriden for ui-showcase's
+      // benefit. We expect either the override or window.matchMedia.
+      matchMedia: React.PropTypes.func.isRequired,
       remotePosterUrl: React.PropTypes.string,
       remoteSrcVideoObject: React.PropTypes.object,
       renderRemoteVideo: React.PropTypes.bool.isRequired,
       screenSharePosterUrl: React.PropTypes.string,
       screenShareVideoObject: React.PropTypes.object,
       showContextRoomName: React.PropTypes.bool.isRequired,
       useDesktopPaths: React.PropTypes.bool.isRequired
     },
 
+    isLocalMediaAbsolutelyPositioned: function(matchMedia) {
+      if (!matchMedia) {
+        matchMedia = this.props.matchMedia;
+      }
+      return matchMedia &&
+        // The screen width is less than 640px and we are not screen sharing.
+        ((matchMedia("screen and (max-width:640px)").matches &&
+         !this.props.displayScreenShare) ||
+         // or the screen width is less than 300px.
+         (matchMedia("screen and (max-width:300px)").matches));
+    },
+
+    getInitialState: function() {
+      return {
+        localMediaAboslutelyPositioned: this.isLocalMediaAbsolutelyPositioned()
+      };
+    },
+
+    componentWillReceiveProps: function(nextProps) {
+      // This is all for the ui-showcase's benefit.
+      if (this.props.matchMedia != nextProps.matchMedia) {
+        this.updateLocalMediaState(null, nextProps.matchMedia);
+      }
+    },
+
+    componentDidMount: function() {
+      window.addEventListener("resize", this.updateLocalMediaState);
+    },
+
+    componentWillUnmount: function() {
+      window.removeEventListener("resize", this.updateLocalMediaState);
+    },
+
+    updateLocalMediaState: function(event, matchMedia) {
+      var newState = this.isLocalMediaAbsolutelyPositioned(matchMedia);
+      if (this.state.localMediaAboslutelyPositioned != newState) {
+        this.setState({
+          localMediaAboslutelyPositioned: newState
+        });
+      }
+    },
+
+    renderLocalVideo: function() {
+      return (
+        React.createElement("div", {className: "local"}, 
+          React.createElement(MediaView, {displayAvatar: this.props.localVideoMuted, 
+            isLoading: this.props.isLocalLoading, 
+            mediaType: "local", 
+            posterUrl: this.props.localPosterUrl, 
+            srcVideoObject: this.props.localSrcVideoObject})
+        )
+      );
+    },
+
     render: function() {
       var remoteStreamClasses = React.addons.classSet({
         "remote": true,
         "focus-stream": !this.props.displayScreenShare
       });
 
       var screenShareStreamClasses = React.addons.classSet({
         "screen": true,
         "focus-stream": this.props.displayScreenShare
       });
 
       var mediaWrapperClasses = React.addons.classSet({
         "media-wrapper": true,
         "receiving-screen-share": this.props.displayScreenShare,
         "showing-local-streams": this.props.localSrcVideoObject ||
-          this.props.localPosterUrl
+          this.props.localPosterUrl,
+        "showing-remote-streams": this.props.remoteSrcVideoObject ||
+          this.props.remotePosterUrl || this.props.isRemoteLoading
       });
 
       return (
         React.createElement("div", {className: "media-layout"}, 
           React.createElement("div", {className: mediaWrapperClasses}, 
             React.createElement("span", {className: "self-view-hidden-message"}, 
               mozL10n.get("self_view_hidden_message")
             ), 
             React.createElement("div", {className: remoteStreamClasses}, 
               React.createElement(MediaView, {displayAvatar: !this.props.renderRemoteVideo, 
                 isLoading: this.props.isRemoteLoading, 
                 mediaType: "remote", 
                 posterUrl: this.props.remotePosterUrl, 
-                srcVideoObject: this.props.remoteSrcVideoObject})
+                srcVideoObject: this.props.remoteSrcVideoObject}), 
+               this.state.localMediaAboslutelyPositioned ?
+                this.renderLocalVideo() : null, 
+               this.props.children
             ), 
             React.createElement("div", {className: screenShareStreamClasses}, 
               React.createElement(MediaView, {displayAvatar: false, 
                 isLoading: this.props.isScreenShareLoading, 
                 mediaType: "screen-share", 
                 posterUrl: this.props.screenSharePosterUrl, 
                 srcVideoObject: this.props.screenShareVideoObject})
             ), 
             React.createElement(loop.shared.views.chat.TextChatView, {
               dispatcher: this.props.dispatcher, 
               showRoomName: this.props.showContextRoomName, 
               useDesktopPaths: false}), 
-            React.createElement("div", {className: "local"}, 
-              React.createElement(MediaView, {displayAvatar: this.props.localVideoMuted, 
-                isLoading: this.props.isLocalLoading, 
-                mediaType: "local", 
-                posterUrl: this.props.localPosterUrl, 
-                srcVideoObject: this.props.localSrcVideoObject})
-            )
+             this.state.localMediaAboslutelyPositioned ?
+              null : this.renderLocalVideo()
           )
         )
       );
     }
   });
 
   return {
     AvatarView: AvatarView,
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -941,83 +941,141 @@ loop.shared.views = (function(_, mozL10n
                className={this.props.mediaType + "-video"}
                muted />
       );
     }
   });
 
   var MediaLayoutView = React.createClass({
     propTypes: {
+      children: React.PropTypes.node,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       displayScreenShare: React.PropTypes.bool.isRequired,
       isLocalLoading: React.PropTypes.bool.isRequired,
       isRemoteLoading: React.PropTypes.bool.isRequired,
       isScreenShareLoading: React.PropTypes.bool.isRequired,
       // The poster URLs are for UI-showcase testing and development.
       localPosterUrl: React.PropTypes.string,
       localSrcVideoObject: React.PropTypes.object,
       localVideoMuted: React.PropTypes.bool.isRequired,
+      // Passing in matchMedia, allows it to be overriden for ui-showcase's
+      // benefit. We expect either the override or window.matchMedia.
+      matchMedia: React.PropTypes.func.isRequired,
       remotePosterUrl: React.PropTypes.string,
       remoteSrcVideoObject: React.PropTypes.object,
       renderRemoteVideo: React.PropTypes.bool.isRequired,
       screenSharePosterUrl: React.PropTypes.string,
       screenShareVideoObject: React.PropTypes.object,
       showContextRoomName: React.PropTypes.bool.isRequired,
       useDesktopPaths: React.PropTypes.bool.isRequired
     },
 
+    isLocalMediaAbsolutelyPositioned: function(matchMedia) {
+      if (!matchMedia) {
+        matchMedia = this.props.matchMedia;
+      }
+      return matchMedia &&
+        // The screen width is less than 640px and we are not screen sharing.
+        ((matchMedia("screen and (max-width:640px)").matches &&
+         !this.props.displayScreenShare) ||
+         // or the screen width is less than 300px.
+         (matchMedia("screen and (max-width:300px)").matches));
+    },
+
+    getInitialState: function() {
+      return {
+        localMediaAboslutelyPositioned: this.isLocalMediaAbsolutelyPositioned()
+      };
+    },
+
+    componentWillReceiveProps: function(nextProps) {
+      // This is all for the ui-showcase's benefit.
+      if (this.props.matchMedia != nextProps.matchMedia) {
+        this.updateLocalMediaState(null, nextProps.matchMedia);
+      }
+    },
+
+    componentDidMount: function() {
+      window.addEventListener("resize", this.updateLocalMediaState);
+    },
+
+    componentWillUnmount: function() {
+      window.removeEventListener("resize", this.updateLocalMediaState);
+    },
+
+    updateLocalMediaState: function(event, matchMedia) {
+      var newState = this.isLocalMediaAbsolutelyPositioned(matchMedia);
+      if (this.state.localMediaAboslutelyPositioned != newState) {
+        this.setState({
+          localMediaAboslutelyPositioned: newState
+        });
+      }
+    },
+
+    renderLocalVideo: function() {
+      return (
+        <div className="local">
+          <MediaView displayAvatar={this.props.localVideoMuted}
+            isLoading={this.props.isLocalLoading}
+            mediaType="local"
+            posterUrl={this.props.localPosterUrl}
+            srcVideoObject={this.props.localSrcVideoObject} />
+        </div>
+      );
+    },
+
     render: function() {
       var remoteStreamClasses = React.addons.classSet({
         "remote": true,
         "focus-stream": !this.props.displayScreenShare
       });
 
       var screenShareStreamClasses = React.addons.classSet({
         "screen": true,
         "focus-stream": this.props.displayScreenShare
       });
 
       var mediaWrapperClasses = React.addons.classSet({
         "media-wrapper": true,
         "receiving-screen-share": this.props.displayScreenShare,
         "showing-local-streams": this.props.localSrcVideoObject ||
-          this.props.localPosterUrl
+          this.props.localPosterUrl,
+        "showing-remote-streams": this.props.remoteSrcVideoObject ||
+          this.props.remotePosterUrl || this.props.isRemoteLoading
       });
 
       return (
         <div className="media-layout">
           <div className={mediaWrapperClasses}>
             <span className="self-view-hidden-message">
               {mozL10n.get("self_view_hidden_message")}
             </span>
             <div className={remoteStreamClasses}>
               <MediaView displayAvatar={!this.props.renderRemoteVideo}
                 isLoading={this.props.isRemoteLoading}
                 mediaType="remote"
                 posterUrl={this.props.remotePosterUrl}
                 srcVideoObject={this.props.remoteSrcVideoObject} />
+              { this.state.localMediaAboslutelyPositioned ?
+                this.renderLocalVideo() : null }
+              { this.props.children }
             </div>
             <div className={screenShareStreamClasses}>
               <MediaView displayAvatar={false}
                 isLoading={this.props.isScreenShareLoading}
                 mediaType="screen-share"
                 posterUrl={this.props.screenSharePosterUrl}
                 srcVideoObject={this.props.screenShareVideoObject} />
             </div>
             <loop.shared.views.chat.TextChatView
               dispatcher={this.props.dispatcher}
               showRoomName={this.props.showContextRoomName}
               useDesktopPaths={false} />
-            <div className="local">
-              <MediaView displayAvatar={this.props.localVideoMuted}
-                isLoading={this.props.isLocalLoading}
-                mediaType="local"
-                posterUrl={this.props.localPosterUrl}
-                srcVideoObject={this.props.localSrcVideoObject} />
-            </div>
+            { this.state.localMediaAboslutelyPositioned ?
+              null : this.renderLocalVideo() }
           </div>
         </div>
       );
     }
   });
 
   return {
     AvatarView: AvatarView,
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -251,21 +251,22 @@ loop.standaloneRoomViews = (function(moz
       );
     }
   });
 
   var StandaloneRoomView = React.createClass({displayName: "StandaloneRoomView",
     mixins: [
       Backbone.Events,
       sharedMixins.MediaSetupMixin,
-      sharedMixins.RoomsAudioMixin,
-      loop.store.StoreMixin("activeRoomStore")
+      sharedMixins.RoomsAudioMixin
     ],
 
     propTypes: {
+      // We pass conversationStore here rather than use the mixin, to allow
+      // easy configurability for the ui-showcase.
       activeRoomStore: React.PropTypes.oneOfType([
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore),
         React.PropTypes.instanceOf(loop.store.FxOSActiveRoomStore)
       ]).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       isFirefox: React.PropTypes.bool.isRequired,
       // The poster URLs are for UI-showcase testing and development
       localPosterUrl: React.PropTypes.string,
@@ -277,16 +278,26 @@ loop.standaloneRoomViews = (function(moz
     getInitialState: function() {
       var storeState = this.props.activeRoomStore.getStoreState();
       return _.extend({}, storeState, {
         // Used by the UI showcase.
         roomState: this.props.roomState || storeState.roomState
       });
     },
 
+    componentWillMount: function() {
+      this.props.activeRoomStore.on("change", function() {
+        this.setState(this.props.activeRoomStore.getStoreState());
+      }, this);
+    },
+
+    componentWillUnmount: function() {
+      this.props.activeRoomStore.off("change", null, this);
+    },
+
     componentDidMount: function() {
       // Adding a class to the document body element from here to ease styling it.
       document.body.classList.add("is-standalone-room");
     },
 
     /**
      * Watches for when we transition to MEDIA_WAIT room state, so we can request
      * user media access.
@@ -424,17 +435,18 @@ loop.standaloneRoomViews = (function(moz
      * Should we render a visual cue to the user (e.g. a spinner) that a remote
      * screen-share is on its way from the other user?
      *
      * @returns {boolean}
      * @private
      */
     _isScreenShareLoading: function() {
       return this.state.receivingScreenShare &&
-             !this.state.screenShareVideoObject;
+             !this.state.screenShareVideoObject &&
+             !this.props.screenSharePosterUrl;
     },
 
     render: function() {
       var displayScreenShare = !!(this.state.receivingScreenShare ||
         this.props.screenSharePosterUrl);
 
       return (
         React.createElement("div", {className: "room-conversation-wrapper standalone-room-wrapper"}, 
@@ -451,16 +463,17 @@ loop.standaloneRoomViews = (function(moz
             dispatcher: this.props.dispatcher, 
             displayScreenShare: displayScreenShare, 
             isLocalLoading: this._isLocalLoading(), 
             isRemoteLoading: this._isRemoteLoading(), 
             isScreenShareLoading: this._isScreenShareLoading(), 
             localPosterUrl: this.props.localPosterUrl, 
             localSrcVideoObject: this.state.localSrcVideoObject, 
             localVideoMuted: this.state.videoMuted, 
+            matchMedia: this.state.matchMedia || window.matchMedia.bind(window), 
             remotePosterUrl: this.props.remotePosterUrl, 
             remoteSrcVideoObject: this.state.remoteSrcVideoObject, 
             renderRemoteVideo: this.shouldRenderRemoteVideo(), 
             screenSharePosterUrl: this.props.screenSharePosterUrl, 
             screenShareVideoObject: this.state.screenShareVideoObject, 
             showContextRoomName: true, 
             useDesktopPaths: false}), 
           React.createElement(sharedViews.ConversationToolbar, {
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -251,21 +251,22 @@ loop.standaloneRoomViews = (function(moz
       );
     }
   });
 
   var StandaloneRoomView = React.createClass({
     mixins: [
       Backbone.Events,
       sharedMixins.MediaSetupMixin,
-      sharedMixins.RoomsAudioMixin,
-      loop.store.StoreMixin("activeRoomStore")
+      sharedMixins.RoomsAudioMixin
     ],
 
     propTypes: {
+      // We pass conversationStore here rather than use the mixin, to allow
+      // easy configurability for the ui-showcase.
       activeRoomStore: React.PropTypes.oneOfType([
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore),
         React.PropTypes.instanceOf(loop.store.FxOSActiveRoomStore)
       ]).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       isFirefox: React.PropTypes.bool.isRequired,
       // The poster URLs are for UI-showcase testing and development
       localPosterUrl: React.PropTypes.string,
@@ -277,16 +278,26 @@ loop.standaloneRoomViews = (function(moz
     getInitialState: function() {
       var storeState = this.props.activeRoomStore.getStoreState();
       return _.extend({}, storeState, {
         // Used by the UI showcase.
         roomState: this.props.roomState || storeState.roomState
       });
     },
 
+    componentWillMount: function() {
+      this.props.activeRoomStore.on("change", function() {
+        this.setState(this.props.activeRoomStore.getStoreState());
+      }, this);
+    },
+
+    componentWillUnmount: function() {
+      this.props.activeRoomStore.off("change", null, this);
+    },
+
     componentDidMount: function() {
       // Adding a class to the document body element from here to ease styling it.
       document.body.classList.add("is-standalone-room");
     },
 
     /**
      * Watches for when we transition to MEDIA_WAIT room state, so we can request
      * user media access.
@@ -424,17 +435,18 @@ loop.standaloneRoomViews = (function(moz
      * Should we render a visual cue to the user (e.g. a spinner) that a remote
      * screen-share is on its way from the other user?
      *
      * @returns {boolean}
      * @private
      */
     _isScreenShareLoading: function() {
       return this.state.receivingScreenShare &&
-             !this.state.screenShareVideoObject;
+             !this.state.screenShareVideoObject &&
+             !this.props.screenSharePosterUrl;
     },
 
     render: function() {
       var displayScreenShare = !!(this.state.receivingScreenShare ||
         this.props.screenSharePosterUrl);
 
       return (
         <div className="room-conversation-wrapper standalone-room-wrapper">
@@ -451,16 +463,17 @@ loop.standaloneRoomViews = (function(moz
             dispatcher={this.props.dispatcher}
             displayScreenShare={displayScreenShare}
             isLocalLoading={this._isLocalLoading()}
             isRemoteLoading={this._isRemoteLoading()}
             isScreenShareLoading={this._isScreenShareLoading()}
             localPosterUrl={this.props.localPosterUrl}
             localSrcVideoObject={this.state.localSrcVideoObject}
             localVideoMuted={this.state.videoMuted}
+            matchMedia={this.state.matchMedia || window.matchMedia.bind(window)}
             remotePosterUrl={this.props.remotePosterUrl}
             remoteSrcVideoObject={this.state.remoteSrcVideoObject}
             renderRemoteVideo={this.shouldRenderRemoteVideo()}
             screenSharePosterUrl={this.props.screenSharePosterUrl}
             screenShareVideoObject={this.state.screenShareVideoObject}
             showContextRoomName={true}
             useDesktopPaths={false} />
           <sharedViews.ConversationToolbar
--- a/browser/components/loop/test/desktop-local/conversationViews_test.js
+++ b/browser/components/loop/test/desktop-local/conversationViews_test.js
@@ -469,63 +469,46 @@ describe("loop.conversationViews", funct
         sinon.assert.calledWith(document.mozL10n.get,
           "generic_contact_unavailable_title");
     });
   });
 
   describe("OngoingConversationView", function() {
     function mountTestComponent(extraProps) {
       var props = _.extend({
-        dispatcher: dispatcher
+        conversationStore: conversationStore,
+        dispatcher: dispatcher,
+        matchMedia: window.matchMedia
       }, extraProps);
       return TestUtils.renderIntoDocument(
         React.createElement(loop.conversationViews.OngoingConversationView, props));
     }
 
     it("should dispatch a setupStreamElements action when the view is created",
       function() {
         view = mountTestComponent();
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithMatch(dispatcher.dispatch,
           sinon.match.hasOwn("name", "setupStreamElements"));
       });
 
-    it("should display an avatar for remote video when the stream is not enabled", function() {
-      view = mountTestComponent({
-        mediaConnected: true,
-        remoteVideoEnabled: false
-      });
-
-      TestUtils.findRenderedComponentWithType(view, sharedViews.AvatarView);
-    });
-
     it("should display the remote video when the stream is enabled", function() {
       conversationStore.setStoreState({
         remoteSrcVideoObject: { fake: 1 }
       });
 
       view = mountTestComponent({
         mediaConnected: true,
         remoteVideoEnabled: true
       });
 
       expect(view.getDOMNode().querySelector(".remote video")).not.eql(null);
     });
 
-    it("should display an avatar for local video when the stream is not enabled", function() {
-      view = mountTestComponent({
-        video: {
-          enabled: false
-        }
-      });
-
-      TestUtils.findRenderedComponentWithType(view, sharedViews.AvatarView);
-    });
-
     it("should display the local video when the stream is enabled", function() {
       conversationStore.setStoreState({
         localSrcVideoObject: { fake: 1 }
       });
 
       view = mountTestComponent({
         video: {
           enabled: true
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -90,17 +90,17 @@
     describe("Uncaught Error Check", function() {
       it("should load the tests without errors", function() {
         chai.expect(uncaughtError && uncaughtError.message).to.be.undefined;
       });
     });
 
     describe("Unexpected Warnings Check", function() {
       it("should long only the warnings we expect", function() {
-        chai.expect(caughtWarnings.length).to.eql(27);
+        chai.expect(caughtWarnings.length).to.eql(28);
       });
     });
 
     mocha.run(function () {
       var completeNode = document.createElement("p");
       completeNode.setAttribute("id", "complete");
       completeNode.appendChild(document.createTextNode("Complete"));
       document.getElementById("mocha").appendChild(completeNode);
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -593,31 +593,16 @@ describe("loop.roomViews", function () {
 
         view = mountTestComponent();
 
         expect(view.getDOMNode().querySelector(".local video")).not.eql(null);
       });
 
     });
 
-    describe("Mute", function() {
-      it("should render local media as audio-only if video is muted",
-        function() {
-          activeRoomStore.setStoreState({
-            roomState: ROOM_STATES.SESSION_CONNECTED,
-            videoMuted: true
-          });
-
-          view = mountTestComponent();
-
-          expect(view.getDOMNode().querySelector(".local-stream-audio"))
-            .not.eql(null);
-        });
-    });
-
     describe("Edit Context", function() {
       it("should show the form when the edit button is clicked", function() {
         view = mountTestComponent();
         var node = view.getDOMNode();
 
         expect(node.querySelector(".room-context")).to.eql(null);
 
         var editButton = node.querySelector(".btn-mute-edit");
--- a/browser/components/loop/test/functional/test_1_browser_call.py
+++ b/browser/components/loop/test/functional/test_1_browser_call.py
@@ -94,17 +94,17 @@ class Test1BrowserCall(MarionetteTestCas
         self.wait_for_element_enabled(button, 120)
 
         button.click()
 
     def local_check_room_self_video(self):
         self.switch_to_chatbox()
 
         # expect a video container on desktop side
-        media_container = self.wait_for_element_displayed(By.CLASS_NAME, "media")
+        media_container = self.wait_for_element_displayed(By.CLASS_NAME, "media-layout")
         self.assertEqual(media_container.tag_name, "div", "expect a video container")
 
         self.check_video(".local-video")
 
     def local_get_and_verify_room_url(self):
         self.switch_to_chatbox()
         button = self.wait_for_element_displayed(By.CLASS_NAME, "btn-copy")
 
--- a/browser/components/loop/test/mochitest/.eslintrc
+++ b/browser/components/loop/test/mochitest/.eslintrc
@@ -16,16 +16,17 @@
     "HAWK_TOKEN_LENGTH": true,
     "checkLoggedOutState": false,
     "checkFxAOAuthTokenData": false,
     "loadLoopPanel": false,
     "getLoopString": false,
     "gMozLoopAPI": true,
     "mockDb": true,
     "mockPushHandler": true,
+    "OpenBrowserWindow": true,
     "promiseDeletedOAuthParams": false,
     "promiseOAuthGetRegistration": false,
     "promiseOAuthParamsSetup": false,
     "promiseObserverNotified": false,
     "promiseWaitForCondition": false,
     "resetFxA": true,
     // Loop specific items
     "MozLoopServiceInternal": true,
--- a/browser/components/loop/test/mochitest/browser_toolbarbutton.js
+++ b/browser/components/loop/test/mochitest/browser_toolbarbutton.js
@@ -162,8 +162,24 @@ add_task(function* test_panelToggle_on_c
 
 add_task(function* test_screen_share() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
   MozLoopService.setScreenShareState("1", true);
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "action", "Check button is in action state");
   MozLoopService.setScreenShareState("1", false);
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
 });
+
+add_task(function* test_private_browsing_window() {
+  let win = OpenBrowserWindow({ private: true });
+  yield new Promise(resolve => {
+    win.addEventListener("load", function listener() {
+      win.removeEventListener("load", listener);
+      resolve();
+    });
+  });
+
+  let button = win.LoopUI.toolbarButton.node;
+  Assert.ok(button, "Loop button should be present");
+  Assert.ok(button.getAttribute("disabled"), "Disabled attribute should be set");
+
+  win.close();
+});
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -911,36 +911,16 @@ describe("loop.store.ActiveRoomStore", f
         sessionToken: "1627384950"
       });
 
       connectionFailureAction = new sharedActions.ConnectionFailure({
         reason: "FAIL"
       });
     });
 
-    it("should retry publishing if on desktop, and in the videoMuted state", function() {
-      store._isDesktop = true;
-
-      store.connectionFailure(new sharedActions.ConnectionFailure({
-        reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
-      }));
-
-      sinon.assert.calledOnce(fakeSdkDriver.retryPublishWithoutVideo);
-    });
-
-    it("should set videoMuted to try when retrying publishing", function() {
-      store._isDesktop = true;
-
-      store.connectionFailure(new sharedActions.ConnectionFailure({
-        reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
-      }));
-
-      expect(store.getStoreState().videoMuted).eql(true);
-    });
-
     it("should store the failure reason", function() {
       store.connectionFailure(connectionFailureAction);
 
       expect(store.getStoreState().failureReason).eql("FAIL");
     });
 
     it("should reset the multiplexGum", function() {
       store.connectionFailure(connectionFailureAction);
--- a/browser/components/loop/test/shared/conversationStore_test.js
+++ b/browser/components/loop/test/shared/conversationStore_test.js
@@ -142,36 +142,16 @@ describe("loop.store.ConversationStore",
   });
 
   describe("#connectionFailure", function() {
     beforeEach(function() {
       store._websocket = fakeWebsocket;
       store.setStoreState({windowId: "42"});
     });
 
-    it("should retry publishing if on desktop, and in the videoMuted state", function() {
-      store._isDesktop = true;
-
-      store.connectionFailure(new sharedActions.ConnectionFailure({
-        reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
-      }));
-
-      sinon.assert.calledOnce(sdkDriver.retryPublishWithoutVideo);
-    });
-
-    it("should set videoMuted to try when retrying publishing", function() {
-      store._isDesktop = true;
-
-      store.connectionFailure(new sharedActions.ConnectionFailure({
-        reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
-      }));
-
-      expect(store.getStoreState().videoMuted).eql(true);
-    });
-
     it("should disconnect the session", function() {
       store.connectionFailure(
         new sharedActions.ConnectionFailure({reason: "fake"}));
 
       sinon.assert.calledOnce(sdkDriver.disconnectSession);
     });
 
     it("should ensure the websocket is closed", function() {
--- a/browser/components/loop/test/shared/otSdkDriver_test.js
+++ b/browser/components/loop/test/shared/otSdkDriver_test.js
@@ -128,53 +128,16 @@ describe("loop.OTSdkDriver", function ()
 
       sinon.assert.calledOnce(sdk.initPublisher);
       sinon.assert.calledWith(sdk.initPublisher,
         sinon.match.instanceOf(HTMLDivElement),
         expectedConfig);
     });
   });
 
-  describe("#retryPublishWithoutVideo", function() {
-    beforeEach(function() {
-      sdk.initPublisher.returns(publisher);
-
-      driver.setupStreamElements(new sharedActions.SetupStreamElements({
-        publisherConfig: publisherConfig
-      }));
-    });
-
-    it("should make MediaStreamTrack.getSources return without a video source", function(done) {
-      driver.retryPublishWithoutVideo();
-
-      window.MediaStreamTrack.getSources(function(sources) {
-        expect(sources.some(function(src) {
-          return src.kind === "video";
-        })).eql(false);
-
-        done();
-      });
-    });
-
-    it("should call initPublisher", function() {
-      driver.retryPublishWithoutVideo();
-
-      var expectedConfig = _.extend({
-        channels: {
-          text: {}
-        }
-      }, publisherConfig);
-
-      sinon.assert.calledTwice(sdk.initPublisher);
-      sinon.assert.calledWith(sdk.initPublisher,
-        sinon.match.instanceOf(HTMLDivElement),
-        expectedConfig);
-    });
-  });
-
   describe("#setMute", function() {
     beforeEach(function() {
       sdk.initPublisher.returns(publisher);
 
       driver.setupStreamElements(new sharedActions.SetupStreamElements({
         publisherConfig: publisherConfig
       }));
     });
--- a/browser/components/loop/test/shared/textChatView_test.js
+++ b/browser/components/loop/test/shared/textChatView_test.js
@@ -51,37 +51,16 @@ describe("loop.shared.views.TextChatView
         React.createElement(loop.shared.views.chat.TextChatEntriesView,
           _.extend(basicProps, extraProps)));
     }
 
     beforeEach(function() {
       store.setStoreState({ textChatEnabled: true });
     });
 
-    it("should add an empty class when the list is empty", function() {
-      view = mountTestComponent({
-        messageList: []
-      });
-
-      expect(view.getDOMNode().classList.contains("text-chat-entries-empty")).eql(true);
-    });
-
-    it("should not add an empty class when the list is has items", function() {
-      view = mountTestComponent({
-        messageList: [{
-          type: CHAT_MESSAGE_TYPES.RECEIVED,
-          contentType: CHAT_CONTENT_TYPES.TEXT,
-          message: "Hello!",
-          receivedTimestamp: "2015-06-25T17:53:55.357Z"
-        }]
-      });
-
-      expect(view.getDOMNode().classList.contains("text-chat-entries-empty")).eql(false);
-    });
-
     it("should render message entries when message were sent/ received", function() {
       view = mountTestComponent({
         messageList: [{
           type: CHAT_MESSAGE_TYPES.RECEIVED,
           contentType: CHAT_CONTENT_TYPES.TEXT,
           message: "Hello!",
           receivedTimestamp: "2015-06-25T17:53:55.357Z"
         }, {
@@ -292,16 +271,51 @@ describe("loop.shared.views.TextChatView
       fakeServer = sinon.fakeServer.create();
       store.setStoreState({ textChatEnabled: true });
     });
 
     afterEach(function() {
       fakeServer.restore();
     });
 
+    it("should add a disabled class when text chat is disabled", function() {
+      view = mountTestComponent();
+
+      store.setStoreState({ textChatEnabled: false });
+
+      expect(view.getDOMNode().classList.contains("text-chat-disabled")).eql(true);
+    });
+
+    it("should not a disabled class when text chat is enabled", function() {
+      view = mountTestComponent();
+
+      store.setStoreState({ textChatEnabled: true });
+
+      expect(view.getDOMNode().classList.contains("text-chat-disabled")).eql(false);
+    });
+
+    it("should add an empty class when the entries list is empty", function() {
+      view = mountTestComponent();
+
+      expect(view.getDOMNode().classList.contains("text-chat-entries-empty")).eql(true);
+    });
+
+    it("should not add an empty class when the entries list is has items", function() {
+      view = mountTestComponent();
+
+      store.sendTextChatMessage({
+        contentType: CHAT_CONTENT_TYPES.TEXT,
+        message: "Hello!",
+        sentTimestamp: "1970-01-01T00:02:00.000Z",
+        receivedTimestamp: "1970-01-01T00:02:00.000Z"
+      });
+
+      expect(view.getDOMNode().classList.contains("text-chat-entries-empty")).eql(false);
+    });
+
     it("should show timestamps from msgs sent more than 1 min apart", function() {
       view = mountTestComponent();
 
       store.sendTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
         message: "Hello!",
         sentTimestamp: "1970-01-01T00:02:00.000Z",
         receivedTimestamp: "1970-01-01T00:02:00.000Z"
@@ -321,22 +335,16 @@ describe("loop.shared.views.TextChatView
       });
 
       var node = view.getDOMNode();
 
       expect(node.querySelectorAll(".text-chat-entry-timestamp").length)
           .to.eql(2);
     });
 
-    it("should display the view if no messages and text chat is enabled", function() {
-      view = mountTestComponent();
-
-      expect(view.getDOMNode()).not.eql(null);
-    });
-
     it("should render message entries when message were sent/ received", function() {
       view = mountTestComponent();
 
       store.receivedTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
         message: "Hello!",
         sentTimestamp: "1970-01-01T00:03:00.000Z",
         receivedTimestamp: "1970-01-01T00:03:00.000Z"
--- a/browser/components/loop/test/shared/views_test.js
+++ b/browser/components/loop/test/shared/views_test.js
@@ -1052,16 +1052,17 @@ describe("loop.shared.views", function()
     function mountTestComponent(extraProps) {
       var defaultProps = {
         dispatcher: dispatcher,
         displayScreenShare: false,
         isLocalLoading: false,
         isRemoteLoading: false,
         isScreenShareLoading: false,
         localVideoMuted: false,
+        matchMedia: window.matchMedia,
         renderRemoteVideo: false,
         showContextRoomName: false,
         useDesktopPaths: false
       };
 
       return TestUtils.renderIntoDocument(
         React.createElement(sharedViews.MediaLayoutView,
           _.extend(defaultProps, extraProps)));
@@ -1139,10 +1140,40 @@ describe("loop.shared.views", function()
       view = mountTestComponent({
         localSrcVideoObject: {},
         localPosterUrl: "fake/url"
       });
 
       expect(view.getDOMNode().querySelector(".media-wrapper")
         .classList.contains("showing-local-streams")).eql(true);
     });
+
+    it("should not mark the wrapper as showing remote streams when not displaying a stream", function() {
+      view = mountTestComponent({
+        remoteSrcVideoObject: null,
+        remotePosterUrl: null
+      });
+
+      expect(view.getDOMNode().querySelector(".media-wrapper")
+        .classList.contains("showing-remote-streams")).eql(false);
+    });
+
+    it("should mark the wrapper as showing remote streams when displaying a stream", function() {
+      view = mountTestComponent({
+        remoteSrcVideoObject: {},
+        remotePosterUrl: null
+      });
+
+      expect(view.getDOMNode().querySelector(".media-wrapper")
+        .classList.contains("showing-remote-streams")).eql(true);
+    });
+
+    it("should mark the wrapper as showing remote streams when displaying a poster url", function() {
+      view = mountTestComponent({
+        remoteSrcVideoObject: {},
+        remotePosterUrl: "fake/url"
+      });
+
+      expect(view.getDOMNode().querySelector(".media-wrapper")
+        .classList.contains("showing-remote-streams")).eql(true);
+    });
   });
 });
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -70,23 +70,35 @@
     }
     window.removeEventListener(eventName, func);
   };
 
   loop.shared.mixins.setRootObject(rootObject);
 
   var dispatcher = new loop.Dispatcher();
 
-  var mockSDK = _.extend({
+  var MockSDK = function() {
+    dispatcher.register(this, [
+      "setupStreamElements"
+    ]);
+  };
+
+  MockSDK.prototype = {
+    setupStreamElements: function() {
+      // Dummy function to stop warnings.
+    },
+
     sendTextChatMessage: function(message) {
       dispatcher.dispatch(new loop.shared.actions.ReceivedTextChatMessage({
         message: message.message
       }));
     }
-  }, Backbone.Events);
+  };
+
+  var mockSDK = new MockSDK();
 
   /**
    * Every view that uses an activeRoomStore needs its own; if they shared
    * an active store, they'd interfere with each other.
    *
    * @param options
    * @returns {loop.store.ActiveRoomStore}
    */
@@ -111,17 +123,16 @@
       remoteVideoEnabled: options.remoteVideoEnabled,
       roomName: "A Very Long Conversation Name",
       roomState: options.roomState,
       used: !!options.roomUsed,
       videoMuted: !!options.videoMuted
     });
 
     store.forcedUpdate = function forcedUpdate(contentWindow) {
-
       // Since this is called by setTimeout, we don't want to lose any
       // exceptions if there's a problem and we need to debug, so...
       try {
         // the dimensions here are taken from the poster images that we're
         // using, since they give the <video> elements their initial intrinsic
         // size.  This ensures that the right aspect ratios are calculated.
         // These are forced to 640x480, because it makes it visually easy to
         // validate that the showcase looks like the real app on a chine
@@ -131,16 +142,27 @@
             camera: {height: 480, orientation: 0, width: 640}
           },
           mediaConnected: options.mediaConnected,
           receivingScreenShare: !!options.receivingScreenShare,
           remoteVideoDimensions: {
             camera: {height: 480, orientation: 0, width: 640}
           },
           remoteVideoEnabled: options.remoteVideoEnabled,
+          // Override the matchMedia, this is so that the correct version is
+          // used for the frame.
+          //
+          // Currently, we use an icky hack, and the showcase conspires with
+          // react-frame-component to set iframe.contentWindow.matchMedia onto
+          // the store. Once React context matures a bit (somewhere between
+          // 0.14 and 1.0, apparently):
+          //
+          // https://facebook.github.io/react/blog/2015/02/24/streamlining-react-elements.html#solution-make-context-parent-based-instead-of-owner-based
+          //
+          // we should be able to use those to clean this up.
           matchMedia: contentWindow.matchMedia.bind(contentWindow),
           roomState: options.roomState,
           videoMuted: !!options.videoMuted
         };
 
         if (options.receivingScreenShare) {
           // Note that the image we're using had to be scaled a bit, and
           // it still ended up a bit narrower than the live thing that
@@ -180,38 +202,49 @@
     mediaConnected: false,
     roomState: ROOM_STATES.READY
   });
 
   var updatingActiveRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.HAS_PARTICIPANTS
   });
 
+  var updatingMobileActiveRoomStore = makeActiveRoomStore({
+    roomState: ROOM_STATES.HAS_PARTICIPANTS
+  });
+
   var localFaceMuteRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     videoMuted: true
   });
 
   var remoteFaceMuteRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     remoteVideoEnabled: false,
     mediaConnected: true
   });
 
   var updatingSharingRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     receivingScreenShare: true
   });
 
+  var updatingSharingRoomMobileStore = makeActiveRoomStore({
+    roomState: ROOM_STATES.HAS_PARTICIPANTS,
+    receivingScreenShare: true
+  });
+
   var loadingRemoteLoadingScreenStore = makeActiveRoomStore({
     mediaConnected: false,
+    receivingScreenShare: true,
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     remoteSrcVideoObject: false
   });
   var loadingScreenSharingRoomStore = makeActiveRoomStore({
+    receivingScreenShare: true,
     roomState: ROOM_STATES.HAS_PARTICIPANTS
   });
 
   /* Set up the stores for pending screen sharing */
   loadingScreenSharingRoomStore.receivingScreenShare({
     receiving: true,
     srcVideoObject: false
   });
@@ -229,17 +262,20 @@
   });
 
   var endedRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.ENDED,
     roomUsed: true
   });
 
   var invitationRoomStore = new loop.store.RoomStore(dispatcher, {
-    mozLoop: navigator.mozLoop
+    mozLoop: navigator.mozLoop,
+    activeRoomStore: makeActiveRoomStore({
+      roomState: ROOM_STATES.INIT
+    })
   });
 
   var roomStore = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: makeActiveRoomStore({
       roomState: ROOM_STATES.HAS_PARTICIPANTS
     })
   });
@@ -248,16 +284,30 @@
     mozLoop: navigator.mozLoop,
     activeRoomStore: makeActiveRoomStore({
       roomState: ROOM_STATES.HAS_PARTICIPANTS,
       mediaConnected: false,
       remoteSrcVideoObject: false
     })
   });
 
+  var desktopRoomStoreMedium = new loop.store.RoomStore(dispatcher, {
+    mozLoop: navigator.mozLoop,
+    activeRoomStore: makeActiveRoomStore({
+      roomState: ROOM_STATES.HAS_PARTICIPANTS
+    })
+  });
+
+  var desktopRoomStoreLarge = new loop.store.RoomStore(dispatcher, {
+    mozLoop: navigator.mozLoop,
+    activeRoomStore: makeActiveRoomStore({
+      roomState: ROOM_STATES.HAS_PARTICIPANTS
+    })
+  });
+
   var desktopLocalFaceMuteActiveRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     videoMuted: true
   });
   var desktopLocalFaceMuteRoomStore = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: desktopLocalFaceMuteActiveRoomStore
   });
@@ -267,25 +317,69 @@
     remoteVideoEnabled: false,
     mediaConnected: true
   });
   var desktopRemoteFaceMuteRoomStore = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: desktopRemoteFaceMuteActiveRoomStore
   });
 
-  var conversationStore = new loop.store.ConversationStore(dispatcher, {
-    client: {},
-    mozLoop: navigator.mozLoop,
-    sdkDriver: mockSDK
-  });
   var textChatStore = new loop.store.TextChatStore(dispatcher, {
     sdkDriver: mockSDK
   });
 
+  /**
+   * Every view that uses an conversationStore needs its own; if they shared
+   * a conversation store, they'd interfere with each other.
+   *
+   * @param options
+   * @returns {loop.store.ConversationStore}
+   */
+  function makeConversationStore() {
+    var roomDispatcher = new loop.Dispatcher();
+
+    var store = new loop.store.ConversationStore(dispatcher, {
+      client: {},
+      mozLoop: navigator.mozLoop,
+      sdkDriver: mockSDK
+    });
+
+    store.forcedUpdate = function forcedUpdate(contentWindow) {
+      // Since this is called by setTimeout, we don't want to lose any
+      // exceptions if there's a problem and we need to debug, so...
+      try {
+        var newStoreState = {
+          // Override the matchMedia, this is so that the correct version is
+          // used for the frame.
+          //
+          // Currently, we use an icky hack, and the showcase conspires with
+          // react-frame-component to set iframe.contentWindow.matchMedia onto
+          // the store. Once React context matures a bit (somewhere between
+          // 0.14 and 1.0, apparently):
+          //
+          // https://facebook.github.io/react/blog/2015/02/24/streamlining-react-elements.html#solution-make-context-parent-based-instead-of-owner-based
+          //
+          // we should be able to use those to clean this up.
+          matchMedia: contentWindow.matchMedia.bind(contentWindow)
+        };
+
+        store.setStoreState(newStoreState);
+      } catch (ex) {
+        console.error("exception in forcedUpdate:", ex);
+      }
+    };
+
+    return store;
+  }
+
+  var conversationStores = [];
+  for (var index = 0; index < 5; index++) {
+    conversationStores[index] = makeConversationStore();
+  }
+
   // Update the text chat store with the room info.
   textChatStore.updateRoomInfo(new sharedActions.UpdateRoomInfo({
     roomName: "A Very Long Conversation Name",
     roomOwner: "fake",
     roomUrl: "http://showcase",
     urls: [{
       description: "A wonderful page!",
       location: "http://wonderful.invalid"
@@ -336,17 +430,17 @@
   dispatcher.dispatch(new sharedActions.SendTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
     message: "Cool",
     sentTimestamp: "2015-06-23T22:27:45.590Z"
   }));
 
   loop.store.StoreMixin.register({
     activeRoomStore: activeRoomStore,
-    conversationStore: conversationStore,
+    conversationStore: conversationStores[0],
     textChatStore: textChatStore
   });
 
   // Local mocks
 
   var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
 
   var mockContact = {
@@ -355,24 +449,16 @@
       value: "smith@invalid.com"
     }]
   };
 
   var mockClient = {
     requestCallUrlInfo: noop
   };
 
-  var mockConversationModel = new loop.shared.models.ConversationModel({
-    callerId: "Mrs Jones",
-    urlCreationDate: (new Date() / 1000).toString()
-  }, {
-    sdk: mockSDK
-  });
-  mockConversationModel.startSession = noop;
-
   var mockWebSocket = new loop.CallConnectionWebSocket({
     url: "fake",
     callId: "fakeId",
     websocketToken: "fakeToken"
   });
 
   var notifications = new loop.shared.models.NotificationCollection();
   var errNotifications = new loop.shared.models.NotificationCollection();
@@ -758,93 +844,128 @@
 
           React.createElement(Section, {name: "CallFailedView"}, 
             React.createElement(Example, {dashed: true, 
                      style: {width: "300px", height: "272px"}, 
                      summary: "Call Failed - Incoming"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(CallFailedView, {dispatcher: dispatcher, 
                                 outgoing: false, 
-                                store: conversationStore})
+                                store: conversationStores[0]})
               )
             ), 
             React.createElement(Example, {dashed: true, 
                      style: {width: "300px", height: "272px"}, 
                      summary: "Call Failed - Outgoing"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(CallFailedView, {dispatcher: dispatcher, 
                                 outgoing: true, 
-                                store: conversationStore})
+                                store: conversationStores[1]})
               )
             ), 
             React.createElement(Example, {dashed: true, 
                      style: {width: "300px", height: "272px"}, 
                      summary: "Call Failed — with call URL error"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(CallFailedView, {dispatcher: dispatcher, emailLinkError: true, 
                                 outgoing: true, 
-                                store: conversationStore})
+                                store: conversationStores[0]})
               )
             )
           ), 
 
           React.createElement(Section, {name: "OngoingConversationView"}, 
-            React.createElement(FramedExample, {height: 254, 
-                           summary: "Desktop ongoing conversation window", 
-                           width: 298}, 
+            React.createElement(FramedExample, {
+              dashed: true, 
+              height: 394, 
+              onContentsRendered: conversationStores[0].forcedUpdate, 
+              summary: "Desktop ongoing conversation window", 
+              width: 298}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(OngoingConversationView, {
                   audio: {enabled: true}, 
+                  conversationStore: conversationStores[0], 
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
                   mediaConnected: true, 
                   remotePosterUrl: "sample-img/video-screen-remote.png", 
                   remoteVideoEnabled: true, 
                   video: {enabled: true}})
               )
             ), 
 
-            React.createElement(FramedExample, {height: 600, 
-                           summary: "Desktop ongoing conversation window large", 
-                           width: 800}, 
-                React.createElement("div", {className: "fx-embedded"}, 
-                  React.createElement(OngoingConversationView, {
-                    audio: {enabled: true}, 
-                    dispatcher: dispatcher, 
-                    localPosterUrl: "sample-img/video-screen-local.png", 
-                    mediaConnected: true, 
-                    remotePosterUrl: "sample-img/video-screen-remote.png", 
-                    remoteVideoEnabled: true, 
-                    video: {enabled: true}})
-                )
+            React.createElement(FramedExample, {
+              dashed: true, 
+              height: 400, 
+              onContentsRendered: conversationStores[1].forcedUpdate, 
+              summary: "Desktop ongoing conversation window (medium)", 
+              width: 600}, 
+              React.createElement("div", {className: "fx-embedded"}, 
+                React.createElement(OngoingConversationView, {
+                  audio: {enabled: true}, 
+                  conversationStore: conversationStores[1], 
+                  dispatcher: dispatcher, 
+                  localPosterUrl: "sample-img/video-screen-local.png", 
+                  mediaConnected: true, 
+                  remotePosterUrl: "sample-img/video-screen-remote.png", 
+                  remoteVideoEnabled: true, 
+                  video: {enabled: true}})
+              )
             ), 
 
-            React.createElement(FramedExample, {height: 254, 
+            React.createElement(FramedExample, {
+              height: 600, 
+              onContentsRendered: conversationStores[2].forcedUpdate, 
+              summary: "Desktop ongoing conversation window (large)", 
+              width: 800}, 
+              React.createElement("div", {className: "fx-embedded"}, 
+                React.createElement(OngoingConversationView, {
+                  audio: {enabled: true}, 
+                  conversationStore: conversationStores[2], 
+                  dispatcher: dispatcher, 
+                  localPosterUrl: "sample-img/video-screen-local.png", 
+                  mediaConnected: true, 
+                  remotePosterUrl: "sample-img/video-screen-remote.png", 
+                  remoteVideoEnabled: true, 
+                  video: {enabled: true}})
+              )
+            ), 
+
+            React.createElement(FramedExample, {
+              dashed: true, 
+              height: 394, 
+              onContentsRendered: conversationStores[3].forcedUpdate, 
               summary: "Desktop ongoing conversation window - local face mute", 
               width: 298}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(OngoingConversationView, {
                   audio: {enabled: true}, 
+                  conversationStore: conversationStores[3], 
                   dispatcher: dispatcher, 
+                  localPosterUrl: "sample-img/video-screen-local.png", 
                   mediaConnected: true, 
                   remotePosterUrl: "sample-img/video-screen-remote.png", 
                   remoteVideoEnabled: true, 
                   video: {enabled: false}})
               )
             ), 
 
-            React.createElement(FramedExample, {height: 254, 
+            React.createElement(FramedExample, {
+              dashed: true, height: 394, 
+              onContentsRendered: conversationStores[4].forcedUpdate, 
               summary: "Desktop ongoing conversation window - remote face mute", 
               width: 298}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(OngoingConversationView, {
                   audio: {enabled: true}, 
+                  conversationStore: conversationStores[4], 
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
                   mediaConnected: true, 
+                  remotePosterUrl: "sample-img/video-screen-remote.png", 
                   remoteVideoEnabled: false, 
                   video: {enabled: true}})
               )
             )
 
           ), 
 
           React.createElement(Section, {name: "FeedbackView"}, 
@@ -889,86 +1010,133 @@
               React.createElement("div", {className: "standalone"}, 
                 React.createElement(UnsupportedDeviceView, {platform: "ios"})
               )
             )
           ), 
 
           React.createElement(Section, {name: "DesktopRoomConversationView"}, 
             React.createElement(FramedExample, {
-              height: 254, 
+              height: 398, 
+              onContentsRendered: invitationRoomStore.activeRoomStore.forcedUpdate, 
               summary: "Desktop room conversation (invitation, text-chat inclusion/scrollbars don't happen in real client)", 
               width: 298}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
                   mozLoop: navigator.mozLoop, 
                   onCallTerminated: function(){}, 
                   roomState: ROOM_STATES.INIT, 
                   roomStore: invitationRoomStore})
               )
             ), 
 
             React.createElement(FramedExample, {
               dashed: true, 
               height: 394, 
+              onContentsRendered: desktopRoomStoreLoading.activeRoomStore.forcedUpdate, 
               summary: "Desktop room conversation (loading)", 
               width: 298}, 
               /* Hide scrollbars here. Rotating loading div overflows and causes
                scrollbars to appear */
               React.createElement("div", {className: "fx-embedded overflow-hidden"}, 
                 React.createElement(DesktopRoomConversationView, {
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
                   mozLoop: navigator.mozLoop, 
                   onCallTerminated: function(){}, 
                   remotePosterUrl: "sample-img/video-screen-remote.png", 
                   roomState: ROOM_STATES.HAS_PARTICIPANTS, 
                   roomStore: desktopRoomStoreLoading})
               )
             ), 
 
-            React.createElement(FramedExample, {height: 254, 
-                           summary: "Desktop room conversation"}, 
+            React.createElement(FramedExample, {
+              dashed: true, 
+              height: 394, 
+              onContentsRendered: roomStore.activeRoomStore.forcedUpdate, 
+              summary: "Desktop room conversation", 
+              width: 298}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
                   mozLoop: navigator.mozLoop, 
                   onCallTerminated: function(){}, 
                   remotePosterUrl: "sample-img/video-screen-remote.png", 
                   roomState: ROOM_STATES.HAS_PARTICIPANTS, 
                   roomStore: roomStore})
               )
             ), 
 
-            React.createElement(FramedExample, {dashed: true, 
-                           height: 394, 
-                           summary: "Desktop room conversation local face-mute", 
-                           width: 298}, 
+            React.createElement(FramedExample, {
+              dashed: true, 
+              height: 482, 
+              onContentsRendered: desktopRoomStoreMedium.activeRoomStore.forcedUpdate, 
+              summary: "Desktop room conversation (medium)", 
+              width: 602}, 
+              React.createElement("div", {className: "fx-embedded"}, 
+                React.createElement(DesktopRoomConversationView, {
+                  dispatcher: dispatcher, 
+                  localPosterUrl: "sample-img/video-screen-local.png", 
+                  mozLoop: navigator.mozLoop, 
+                  onCallTerminated: function(){}, 
+                  remotePosterUrl: "sample-img/video-screen-remote.png", 
+                  roomState: ROOM_STATES.HAS_PARTICIPANTS, 
+                  roomStore: desktopRoomStoreMedium})
+              )
+            ), 
+
+            React.createElement(FramedExample, {
+              dashed: true, 
+              height: 485, 
+              onContentsRendered: desktopRoomStoreLarge.activeRoomStore.forcedUpdate, 
+              summary: "Desktop room conversation (large)", 
+              width: 646}, 
+              React.createElement("div", {className: "fx-embedded"}, 
+                React.createElement(DesktopRoomConversationView, {
+                  dispatcher: dispatcher, 
+                  localPosterUrl: "sample-img/video-screen-local.png", 
+                  mozLoop: navigator.mozLoop, 
+                  onCallTerminated: function(){}, 
+                  remotePosterUrl: "sample-img/video-screen-remote.png", 
+                  roomState: ROOM_STATES.HAS_PARTICIPANTS, 
+                  roomStore: desktopRoomStoreLarge})
+              )
+            ), 
+
+            React.createElement(FramedExample, {
+              dashed: true, 
+              height: 394, 
+              onContentsRendered: desktopLocalFaceMuteRoomStore.activeRoomStore.forcedUpdate, 
+              summary: "Desktop room conversation local face-mute", 
+              width: 298}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   dispatcher: dispatcher, 
                   mozLoop: navigator.mozLoop, 
                   onCallTerminated: function(){}, 
                   remotePosterUrl: "sample-img/video-screen-remote.png", 
                   roomStore: desktopLocalFaceMuteRoomStore})
               )
             ), 
 
-            React.createElement(FramedExample, {dashed: true, height: 394, 
+            React.createElement(FramedExample, {dashed: true, 
+                           height: 394, 
+                           onContentsRendered: desktopRemoteFaceMuteRoomStore.activeRoomStore.forcedUpdate, 
                            summary: "Desktop room conversation remote face-mute", 
                            width: 298}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
                   mozLoop: navigator.mozLoop, 
                   onCallTerminated: function(){}, 
+                  remotePosterUrl: "sample-img/video-screen-remote.png", 
                   roomStore: desktopRemoteFaceMuteRoomStore})
               )
             )
           ), 
 
           React.createElement(Section, {name: "StandaloneRoomView"}, 
             React.createElement(FramedExample, {cssClass: "standalone", 
                            dashed: true, 
@@ -1076,18 +1244,17 @@
                scrollbars to appear */
                React.createElement("div", {className: "standalone overflow-hidden"}, 
                   React.createElement(StandaloneRoomView, {
                     activeRoomStore: loadingRemoteLoadingScreenStore, 
                     dispatcher: dispatcher, 
                     isFirefox: true, 
                     localPosterUrl: "sample-img/video-screen-local.png", 
                     remotePosterUrl: "sample-img/video-screen-remote.png", 
-                    roomState: ROOM_STATES.HAS_PARTICIPANTS, 
-                    screenSharePosterUrl: "sample-img/video-screen-baz.png"})
+                    roomState: ROOM_STATES.HAS_PARTICIPANTS})
                 )
             ), 
 
             React.createElement(FramedExample, {
               cssClass: "standalone", 
               dashed: true, 
               height: 660, 
               onContentsRendered: loadingScreenSharingRoomStore.forcedUpdate, 
@@ -1097,18 +1264,17 @@
                scrollbars to appear */
                React.createElement("div", {className: "standalone overflow-hidden"}, 
                   React.createElement(StandaloneRoomView, {
                     activeRoomStore: loadingScreenSharingRoomStore, 
                     dispatcher: dispatcher, 
                     isFirefox: true, 
                     localPosterUrl: "sample-img/video-screen-local.png", 
                     remotePosterUrl: "sample-img/video-screen-remote.png", 
-                    roomState: ROOM_STATES.HAS_PARTICIPANTS, 
-                    screenSharePosterUrl: "sample-img/video-screen-baz.png"})
+                    roomState: ROOM_STATES.HAS_PARTICIPANTS})
                 )
             ), 
 
             React.createElement(FramedExample, {
               cssClass: "standalone", 
               dashed: true, 
               height: 660, 
               onContentsRendered: updatingSharingRoomStore.forcedUpdate, 
@@ -1166,40 +1332,40 @@
             )
           ), 
 
           React.createElement(Section, {name: "StandaloneRoomView (Mobile)"}, 
             React.createElement(FramedExample, {
               cssClass: "standalone", 
               dashed: true, 
               height: 480, 
-              onContentsRendered: updatingActiveRoomStore.forcedUpdate, 
+              onContentsRendered: updatingMobileActiveRoomStore.forcedUpdate, 
               summary: "Standalone room conversation (has-participants, 600x480)", 
               width: 600}, 
                 React.createElement("div", {className: "standalone"}, 
                   React.createElement(StandaloneRoomView, {
-                    activeRoomStore: updatingActiveRoomStore, 
+                    activeRoomStore: updatingMobileActiveRoomStore, 
                     dispatcher: dispatcher, 
                     isFirefox: true, 
                     localPosterUrl: "sample-img/video-screen-local.png", 
                     remotePosterUrl: "sample-img/video-screen-remote.png", 
                     roomState: ROOM_STATES.HAS_PARTICIPANTS})
                 )
             ), 
 
             React.createElement(FramedExample, {
               cssClass: "standalone", 
               dashed: true, 
               height: 480, 
-              onContentsRendered: updatingSharingRoomStore.forcedUpdate, 
+              onContentsRendered: updatingSharingRoomMobileStore.forcedUpdate, 
               summary: "Standalone room convo (has-participants, receivingScreenShare, 600x480)", 
               width: 600}, 
                 React.createElement("div", {className: "standalone", cssClass: "standalone"}, 
                   React.createElement(StandaloneRoomView, {
-                    activeRoomStore: updatingSharingRoomStore, 
+                    activeRoomStore: updatingSharingRoomMobileStore, 
                     dispatcher: dispatcher, 
                     isFirefox: true, 
                     localPosterUrl: "sample-img/video-screen-local.png", 
                     remotePosterUrl: "sample-img/video-screen-remote.png", 
                     roomState: ROOM_STATES.HAS_PARTICIPANTS, 
                     screenSharePosterUrl: "sample-img/video-screen-terminal.png"})
                 )
             )
@@ -1277,17 +1443,17 @@
         setTimeout(waitForQueuedFrames, 500);
         return;
       }
       // Put the title back, in case views changed it.
       document.title = "Loop UI Components Showcase";
 
       // This simulates the mocha layout for errors which means we can run
       // this alongside our other unit tests but use the same harness.
-      var expectedWarningsCount = 23;
+      var expectedWarningsCount = 18;
       var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
       if (uncaughtError || warningsMismatch) {
         $("#results").append("<div class='failures'><em>" +
           ((uncaughtError && warningsMismatch) ? 2 : 1) + "</em></div>");
         if (warningsMismatch) {
           $("#results").append("<li class='test fail'>" +
             "<h2>Unexpected number of warnings detected in UI-Showcase</h2>" +
             "<pre class='error'>Got: " + caughtWarnings.length + "\n" +
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -70,23 +70,35 @@
     }
     window.removeEventListener(eventName, func);
   };
 
   loop.shared.mixins.setRootObject(rootObject);
 
   var dispatcher = new loop.Dispatcher();
 
-  var mockSDK = _.extend({
+  var MockSDK = function() {
+    dispatcher.register(this, [
+      "setupStreamElements"
+    ]);
+  };
+
+  MockSDK.prototype = {
+    setupStreamElements: function() {
+      // Dummy function to stop warnings.
+    },
+
     sendTextChatMessage: function(message) {
       dispatcher.dispatch(new loop.shared.actions.ReceivedTextChatMessage({
         message: message.message
       }));
     }
-  }, Backbone.Events);
+  };
+
+  var mockSDK = new MockSDK();
 
   /**
    * Every view that uses an activeRoomStore needs its own; if they shared
    * an active store, they'd interfere with each other.
    *
    * @param options
    * @returns {loop.store.ActiveRoomStore}
    */
@@ -111,17 +123,16 @@
       remoteVideoEnabled: options.remoteVideoEnabled,
       roomName: "A Very Long Conversation Name",
       roomState: options.roomState,
       used: !!options.roomUsed,
       videoMuted: !!options.videoMuted
     });
 
     store.forcedUpdate = function forcedUpdate(contentWindow) {
-
       // Since this is called by setTimeout, we don't want to lose any
       // exceptions if there's a problem and we need to debug, so...
       try {
         // the dimensions here are taken from the poster images that we're
         // using, since they give the <video> elements their initial intrinsic
         // size.  This ensures that the right aspect ratios are calculated.
         // These are forced to 640x480, because it makes it visually easy to
         // validate that the showcase looks like the real app on a chine
@@ -131,16 +142,27 @@
             camera: {height: 480, orientation: 0, width: 640}
           },
           mediaConnected: options.mediaConnected,
           receivingScreenShare: !!options.receivingScreenShare,
           remoteVideoDimensions: {
             camera: {height: 480, orientation: 0, width: 640}
           },
           remoteVideoEnabled: options.remoteVideoEnabled,
+          // Override the matchMedia, this is so that the correct version is
+          // used for the frame.
+          //
+          // Currently, we use an icky hack, and the showcase conspires with
+          // react-frame-component to set iframe.contentWindow.matchMedia onto
+          // the store. Once React context matures a bit (somewhere between
+          // 0.14 and 1.0, apparently):
+          //
+          // https://facebook.github.io/react/blog/2015/02/24/streamlining-react-elements.html#solution-make-context-parent-based-instead-of-owner-based
+          //
+          // we should be able to use those to clean this up.
           matchMedia: contentWindow.matchMedia.bind(contentWindow),
           roomState: options.roomState,
           videoMuted: !!options.videoMuted
         };
 
         if (options.receivingScreenShare) {
           // Note that the image we're using had to be scaled a bit, and
           // it still ended up a bit narrower than the live thing that
@@ -180,38 +202,49 @@
     mediaConnected: false,
     roomState: ROOM_STATES.READY
   });
 
   var updatingActiveRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.HAS_PARTICIPANTS
   });
 
+  var updatingMobileActiveRoomStore = makeActiveRoomStore({
+    roomState: ROOM_STATES.HAS_PARTICIPANTS
+  });
+
   var localFaceMuteRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     videoMuted: true
   });
 
   var remoteFaceMuteRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     remoteVideoEnabled: false,
     mediaConnected: true
   });
 
   var updatingSharingRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     receivingScreenShare: true
   });
 
+  var updatingSharingRoomMobileStore = makeActiveRoomStore({
+    roomState: ROOM_STATES.HAS_PARTICIPANTS,
+    receivingScreenShare: true
+  });
+
   var loadingRemoteLoadingScreenStore = makeActiveRoomStore({
     mediaConnected: false,
+    receivingScreenShare: true,
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     remoteSrcVideoObject: false
   });
   var loadingScreenSharingRoomStore = makeActiveRoomStore({
+    receivingScreenShare: true,
     roomState: ROOM_STATES.HAS_PARTICIPANTS
   });
 
   /* Set up the stores for pending screen sharing */
   loadingScreenSharingRoomStore.receivingScreenShare({
     receiving: true,
     srcVideoObject: false
   });
@@ -229,17 +262,20 @@
   });
 
   var endedRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.ENDED,
     roomUsed: true
   });
 
   var invitationRoomStore = new loop.store.RoomStore(dispatcher, {
-    mozLoop: navigator.mozLoop
+    mozLoop: navigator.mozLoop,
+    activeRoomStore: makeActiveRoomStore({
+      roomState: ROOM_STATES.INIT
+    })
   });
 
   var roomStore = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: makeActiveRoomStore({
       roomState: ROOM_STATES.HAS_PARTICIPANTS
     })
   });
@@ -248,16 +284,30 @@
     mozLoop: navigator.mozLoop,
     activeRoomStore: makeActiveRoomStore({
       roomState: ROOM_STATES.HAS_PARTICIPANTS,
       mediaConnected: false,
       remoteSrcVideoObject: false
     })
   });
 
+  var desktopRoomStoreMedium = new loop.store.RoomStore(dispatcher, {
+    mozLoop: navigator.mozLoop,
+    activeRoomStore: makeActiveRoomStore({
+      roomState: ROOM_STATES.HAS_PARTICIPANTS
+    })
+  });
+
+  var desktopRoomStoreLarge = new loop.store.RoomStore(dispatcher, {
+    mozLoop: navigator.mozLoop,
+    activeRoomStore: makeActiveRoomStore({
+      roomState: ROOM_STATES.HAS_PARTICIPANTS
+    })
+  });
+
   var desktopLocalFaceMuteActiveRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     videoMuted: true
   });
   var desktopLocalFaceMuteRoomStore = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: desktopLocalFaceMuteActiveRoomStore
   });
@@ -267,25 +317,69 @@
     remoteVideoEnabled: false,
     mediaConnected: true
   });
   var desktopRemoteFaceMuteRoomStore = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: desktopRemoteFaceMuteActiveRoomStore
   });
 
-  var conversationStore = new loop.store.ConversationStore(dispatcher, {
-    client: {},
-    mozLoop: navigator.mozLoop,
-    sdkDriver: mockSDK
-  });
   var textChatStore = new loop.store.TextChatStore(dispatcher, {
     sdkDriver: mockSDK
   });
 
+  /**
+   * Every view that uses an conversationStore needs its own; if they shared
+   * a conversation store, they'd interfere with each other.
+   *
+   * @param options
+   * @returns {loop.store.ConversationStore}
+   */
+  function makeConversationStore() {
+    var roomDispatcher = new loop.Dispatcher();
+
+    var store = new loop.store.ConversationStore(dispatcher, {
+      client: {},
+      mozLoop: navigator.mozLoop,
+      sdkDriver: mockSDK
+    });
+
+    store.forcedUpdate = function forcedUpdate(contentWindow) {
+      // Since this is called by setTimeout, we don't want to lose any
+      // exceptions if there's a problem and we need to debug, so...
+      try {
+        var newStoreState = {
+          // Override the matchMedia, this is so that the correct version is
+          // used for the frame.
+          //
+          // Currently, we use an icky hack, and the showcase conspires with
+          // react-frame-component to set iframe.contentWindow.matchMedia onto
+          // the store. Once React context matures a bit (somewhere between
+          // 0.14 and 1.0, apparently):
+          //
+          // https://facebook.github.io/react/blog/2015/02/24/streamlining-react-elements.html#solution-make-context-parent-based-instead-of-owner-based
+          //
+          // we should be able to use those to clean this up.
+          matchMedia: contentWindow.matchMedia.bind(contentWindow)
+        };
+
+        store.setStoreState(newStoreState);
+      } catch (ex) {
+        console.error("exception in forcedUpdate:", ex);
+      }
+    };
+
+    return store;
+  }
+
+  var conversationStores = [];
+  for (var index = 0; index < 5; index++) {
+    conversationStores[index] = makeConversationStore();
+  }
+
   // Update the text chat store with the room info.
   textChatStore.updateRoomInfo(new sharedActions.UpdateRoomInfo({
     roomName: "A Very Long Conversation Name",
     roomOwner: "fake",
     roomUrl: "http://showcase",
     urls: [{
       description: "A wonderful page!",
       location: "http://wonderful.invalid"
@@ -336,17 +430,17 @@
   dispatcher.dispatch(new sharedActions.SendTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
     message: "Cool",
     sentTimestamp: "2015-06-23T22:27:45.590Z"
   }));
 
   loop.store.StoreMixin.register({
     activeRoomStore: activeRoomStore,
-    conversationStore: conversationStore,
+    conversationStore: conversationStores[0],
     textChatStore: textChatStore
   });
 
   // Local mocks
 
   var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
 
   var mockContact = {
@@ -355,24 +449,16 @@
       value: "smith@invalid.com"
     }]
   };
 
   var mockClient = {
     requestCallUrlInfo: noop
   };
 
-  var mockConversationModel = new loop.shared.models.ConversationModel({
-    callerId: "Mrs Jones",
-    urlCreationDate: (new Date() / 1000).toString()
-  }, {
-    sdk: mockSDK
-  });
-  mockConversationModel.startSession = noop;
-
   var mockWebSocket = new loop.CallConnectionWebSocket({
     url: "fake",
     callId: "fakeId",
     websocketToken: "fakeToken"
   });
 
   var notifications = new loop.shared.models.NotificationCollection();
   var errNotifications = new loop.shared.models.NotificationCollection();
@@ -758,93 +844,128 @@
 
           <Section name="CallFailedView">
             <Example dashed={true}
                      style={{width: "300px", height: "272px"}}
                      summary="Call Failed - Incoming">
               <div className="fx-embedded">
                 <CallFailedView dispatcher={dispatcher}
                                 outgoing={false}
-                                store={conversationStore} />
+                                store={conversationStores[0]} />
               </div>
             </Example>
             <Example dashed={true}
                      style={{width: "300px", height: "272px"}}
                      summary="Call Failed - Outgoing">
               <div className="fx-embedded">
                 <CallFailedView dispatcher={dispatcher}
                                 outgoing={true}
-                                store={conversationStore} />
+                                store={conversationStores[1]} />
               </div>
             </Example>
             <Example dashed={true}
                      style={{width: "300px", height: "272px"}}
                      summary="Call Failed — with call URL error">
               <div className="fx-embedded">
                 <CallFailedView dispatcher={dispatcher} emailLinkError={true}
                                 outgoing={true}
-                                store={conversationStore} />
+                                store={conversationStores[0]} />
               </div>
             </Example>
           </Section>
 
           <Section name="OngoingConversationView">
-            <FramedExample height={254}
-                           summary="Desktop ongoing conversation window"
-                           width={298}>
+            <FramedExample
+              dashed={true}
+              height={394}
+              onContentsRendered={conversationStores[0].forcedUpdate}
+              summary="Desktop ongoing conversation window"
+              width={298}>
               <div className="fx-embedded">
                 <OngoingConversationView
                   audio={{enabled: true}}
+                  conversationStore={conversationStores[0]}
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
                   mediaConnected={true}
                   remotePosterUrl="sample-img/video-screen-remote.png"
                   remoteVideoEnabled={true}
                   video={{enabled: true}} />
               </div>
             </FramedExample>
 
-            <FramedExample height={600}
-                           summary="Desktop ongoing conversation window large"
-                           width={800}>
-                <div className="fx-embedded">
-                  <OngoingConversationView
-                    audio={{enabled: true}}
-                    dispatcher={dispatcher}
-                    localPosterUrl="sample-img/video-screen-local.png"
-                    mediaConnected={true}
-                    remotePosterUrl="sample-img/video-screen-remote.png"
-                    remoteVideoEnabled={true}
-                    video={{enabled: true}} />
-                </div>
+            <FramedExample
+              dashed={true}
+              height={400}
+              onContentsRendered={conversationStores[1].forcedUpdate}
+              summary="Desktop ongoing conversation window (medium)"
+              width={600}>
+              <div className="fx-embedded">
+                <OngoingConversationView
+                  audio={{enabled: true}}
+                  conversationStore={conversationStores[1]}
+                  dispatcher={dispatcher}
+                  localPosterUrl="sample-img/video-screen-local.png"
+                  mediaConnected={true}
+                  remotePosterUrl="sample-img/video-screen-remote.png"
+                  remoteVideoEnabled={true}
+                  video={{enabled: true}} />
+              </div>
             </FramedExample>
 
-            <FramedExample height={254}
+            <FramedExample
+              height={600}
+              onContentsRendered={conversationStores[2].forcedUpdate}
+              summary="Desktop ongoing conversation window (large)"
+              width={800}>
+              <div className="fx-embedded">
+                <OngoingConversationView
+                  audio={{enabled: true}}
+                  conversationStore={conversationStores[2]}
+                  dispatcher={dispatcher}
+                  localPosterUrl="sample-img/video-screen-local.png"
+                  mediaConnected={true}
+                  remotePosterUrl="sample-img/video-screen-remote.png"
+                  remoteVideoEnabled={true}
+                  video={{enabled: true}} />
+              </div>
+            </FramedExample>
+
+            <FramedExample
+              dashed={true}
+              height={394}
+              onContentsRendered={conversationStores[3].forcedUpdate}
               summary="Desktop ongoing conversation window - local face mute"
               width={298} >
               <div className="fx-embedded">
                 <OngoingConversationView
                   audio={{enabled: true}}
+                  conversationStore={conversationStores[3]}
                   dispatcher={dispatcher}
+                  localPosterUrl="sample-img/video-screen-local.png"
                   mediaConnected={true}
                   remotePosterUrl="sample-img/video-screen-remote.png"
                   remoteVideoEnabled={true}
                   video={{enabled: false}} />
               </div>
             </FramedExample>
 
-            <FramedExample height={254}
+            <FramedExample
+              dashed={true} height={394}
+              onContentsRendered={conversationStores[4].forcedUpdate}
               summary="Desktop ongoing conversation window - remote face mute"
               width={298} >
               <div className="fx-embedded">
                 <OngoingConversationView
                   audio={{enabled: true}}
+                  conversationStore={conversationStores[4]}
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
                   mediaConnected={true}
+                  remotePosterUrl="sample-img/video-screen-remote.png"
                   remoteVideoEnabled={false}
                   video={{enabled: true}} />
               </div>
             </FramedExample>
 
           </Section>
 
           <Section name="FeedbackView">
@@ -889,86 +1010,133 @@
               <div className="standalone">
                 <UnsupportedDeviceView platform="ios"/>
               </div>
             </Example>
           </Section>
 
           <Section name="DesktopRoomConversationView">
             <FramedExample
-              height={254}
+              height={398}
+              onContentsRendered={invitationRoomStore.activeRoomStore.forcedUpdate}
               summary="Desktop room conversation (invitation, text-chat inclusion/scrollbars don't happen in real client)"
               width={298}>
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
                   mozLoop={navigator.mozLoop}
                   onCallTerminated={function(){}}
                   roomState={ROOM_STATES.INIT}
                   roomStore={invitationRoomStore} />
               </div>
             </FramedExample>
 
             <FramedExample
               dashed={true}
               height={394}
+              onContentsRendered={desktopRoomStoreLoading.activeRoomStore.forcedUpdate}
               summary="Desktop room conversation (loading)"
               width={298}>
               {/* Hide scrollbars here. Rotating loading div overflows and causes
                scrollbars to appear */}
               <div className="fx-embedded overflow-hidden">
                 <DesktopRoomConversationView
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
                   mozLoop={navigator.mozLoop}
                   onCallTerminated={function(){}}
                   remotePosterUrl="sample-img/video-screen-remote.png"
                   roomState={ROOM_STATES.HAS_PARTICIPANTS}
                   roomStore={desktopRoomStoreLoading} />
               </div>
             </FramedExample>
 
-            <FramedExample height={254}
-                           summary="Desktop room conversation">
+            <FramedExample
+              dashed={true}
+              height={394}
+              onContentsRendered={roomStore.activeRoomStore.forcedUpdate}
+              summary="Desktop room conversation"
+              width={298}>
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
                   mozLoop={navigator.mozLoop}
                   onCallTerminated={function(){}}
                   remotePosterUrl="sample-img/video-screen-remote.png"
                   roomState={ROOM_STATES.HAS_PARTICIPANTS}
                   roomStore={roomStore} />
               </div>
             </FramedExample>
 
-            <FramedExample dashed={true}
-                           height={394}
-                           summary="Desktop room conversation local face-mute"
-                           width={298}>
+            <FramedExample
+              dashed={true}
+              height={482}
+              onContentsRendered={desktopRoomStoreMedium.activeRoomStore.forcedUpdate}
+              summary="Desktop room conversation (medium)"
+              width={602}>
+              <div className="fx-embedded">
+                <DesktopRoomConversationView
+                  dispatcher={dispatcher}
+                  localPosterUrl="sample-img/video-screen-local.png"
+                  mozLoop={navigator.mozLoop}
+                  onCallTerminated={function(){}}
+                  remotePosterUrl="sample-img/video-screen-remote.png"
+                  roomState={ROOM_STATES.HAS_PARTICIPANTS}
+                  roomStore={desktopRoomStoreMedium} />
+              </div>
+            </FramedExample>
+
+            <FramedExample
+              dashed={true}
+              height={485}
+              onContentsRendered={desktopRoomStoreLarge.activeRoomStore.forcedUpdate}
+              summary="Desktop room conversation (large)"
+              width={646}>
+              <div className="fx-embedded">
+                <DesktopRoomConversationView
+                  dispatcher={dispatcher}
+                  localPosterUrl="sample-img/video-screen-local.png"
+                  mozLoop={navigator.mozLoop}
+                  onCallTerminated={function(){}}
+                  remotePosterUrl="sample-img/video-screen-remote.png"
+                  roomState={ROOM_STATES.HAS_PARTICIPANTS}
+                  roomStore={desktopRoomStoreLarge} />
+              </div>
+            </FramedExample>
+
+            <FramedExample
+              dashed={true}
+              height={394}
+              onContentsRendered={desktopLocalFaceMuteRoomStore.activeRoomStore.forcedUpdate}
+              summary="Desktop room conversation local face-mute"
+              width={298}>
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   dispatcher={dispatcher}
                   mozLoop={navigator.mozLoop}
                   onCallTerminated={function(){}}
                   remotePosterUrl="sample-img/video-screen-remote.png"
                   roomStore={desktopLocalFaceMuteRoomStore} />
               </div>
             </FramedExample>
 
-            <FramedExample dashed={true} height={394}
+            <FramedExample dashed={true}
+                           height={394}
+                           onContentsRendered={desktopRemoteFaceMuteRoomStore.activeRoomStore.forcedUpdate}
                            summary="Desktop room conversation remote face-mute"
                            width={298} >
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
                   mozLoop={navigator.mozLoop}
                   onCallTerminated={function(){}}
+                  remotePosterUrl="sample-img/video-screen-remote.png"
                   roomStore={desktopRemoteFaceMuteRoomStore} />
               </div>
             </FramedExample>
           </Section>
 
           <Section name="StandaloneRoomView">
             <FramedExample cssClass="standalone"
                            dashed={true}
@@ -1076,18 +1244,17 @@
                scrollbars to appear */}
                <div className="standalone overflow-hidden">
                   <StandaloneRoomView
                     activeRoomStore={loadingRemoteLoadingScreenStore}
                     dispatcher={dispatcher}
                     isFirefox={true}
                     localPosterUrl="sample-img/video-screen-local.png"
                     remotePosterUrl="sample-img/video-screen-remote.png"
-                    roomState={ROOM_STATES.HAS_PARTICIPANTS}
-                    screenSharePosterUrl="sample-img/video-screen-baz.png" />
+                    roomState={ROOM_STATES.HAS_PARTICIPANTS} />
                 </div>
             </FramedExample>
 
             <FramedExample
               cssClass="standalone"
               dashed={true}
               height={660}
               onContentsRendered={loadingScreenSharingRoomStore.forcedUpdate}
@@ -1097,18 +1264,17 @@
                scrollbars to appear */}
                <div className="standalone overflow-hidden">
                   <StandaloneRoomView
                     activeRoomStore={loadingScreenSharingRoomStore}
                     dispatcher={dispatcher}
                     isFirefox={true}
                     localPosterUrl="sample-img/video-screen-local.png"
                     remotePosterUrl="sample-img/video-screen-remote.png"
-                    roomState={ROOM_STATES.HAS_PARTICIPANTS}
-                    screenSharePosterUrl="sample-img/video-screen-baz.png" />
+                    roomState={ROOM_STATES.HAS_PARTICIPANTS} />
                 </div>
             </FramedExample>
 
             <FramedExample
               cssClass="standalone"
               dashed={true}
               height={660}
               onContentsRendered={updatingSharingRoomStore.forcedUpdate}
@@ -1166,40 +1332,40 @@
             </FramedExample>
           </Section>
 
           <Section name="StandaloneRoomView (Mobile)">
             <FramedExample
               cssClass="standalone"
               dashed={true}
               height={480}
-              onContentsRendered={updatingActiveRoomStore.forcedUpdate}
+              onContentsRendered={updatingMobileActiveRoomStore.forcedUpdate}
               summary="Standalone room conversation (has-participants, 600x480)"
               width={600}>
                 <div className="standalone">
                   <StandaloneRoomView
-                    activeRoomStore={updatingActiveRoomStore}
+                    activeRoomStore={updatingMobileActiveRoomStore}
                     dispatcher={dispatcher}
                     isFirefox={true}
                     localPosterUrl="sample-img/video-screen-local.png"
                     remotePosterUrl="sample-img/video-screen-remote.png"
                     roomState={ROOM_STATES.HAS_PARTICIPANTS} />
                 </div>
             </FramedExample>
 
             <FramedExample
               cssClass="standalone"
               dashed={true}
               height={480}
-              onContentsRendered={updatingSharingRoomStore.forcedUpdate}
+              onContentsRendered={updatingSharingRoomMobileStore.forcedUpdate}
               summary="Standalone room convo (has-participants, receivingScreenShare, 600x480)"
               width={600} >
                 <div className="standalone" cssClass="standalone">
                   <StandaloneRoomView
-                    activeRoomStore={updatingSharingRoomStore}
+                    activeRoomStore={updatingSharingRoomMobileStore}
                     dispatcher={dispatcher}
                     isFirefox={true}
                     localPosterUrl="sample-img/video-screen-local.png"
                     remotePosterUrl="sample-img/video-screen-remote.png"
                     roomState={ROOM_STATES.HAS_PARTICIPANTS}
                     screenSharePosterUrl="sample-img/video-screen-terminal.png" />
                 </div>
             </FramedExample>
@@ -1277,17 +1443,17 @@
         setTimeout(waitForQueuedFrames, 500);
         return;
       }
       // Put the title back, in case views changed it.
       document.title = "Loop UI Components Showcase";
 
       // This simulates the mocha layout for errors which means we can run
       // this alongside our other unit tests but use the same harness.
-      var expectedWarningsCount = 23;
+      var expectedWarningsCount = 18;
       var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
       if (uncaughtError || warningsMismatch) {
         $("#results").append("<div class='failures'><em>" +
           ((uncaughtError && warningsMismatch) ? 2 : 1) + "</em></div>");
         if (warningsMismatch) {
           $("#results").append("<li class='test fail'>" +
             "<h2>Unexpected number of warnings detected in UI-Showcase</h2>" +
             "<pre class='error'>Got: " + caughtWarnings.length + "\n" +
--- a/browser/devtools/styleinspector/computed-view.js
+++ b/browser/devtools/styleinspector/computed-view.js
@@ -1,15 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* globals overlays, StyleInspectorMenu */
+/* globals overlays, StyleInspectorMenu, loader, clipboardHelper,
+  _Iterator, StopIteration */
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 
 const ToolDefinitions = require("main").Tools;
 const {CssLogic} = require("devtools/styleinspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
@@ -44,90 +45,86 @@ const HTML_NS = "http://www.w3.org/1999/
  *          onBatch {function} Will be called after each batch of iterations,
  *            before yielding to the main loop.
  *          onDone {function} Will be called when iteration is complete.
  *          onCancel {function} Will be called if the process is canceled.
  *          threshold {int} How long to process before yielding, in ms.
  *
  * @constructor
  */
-function UpdateProcess(aWin, aGenerator, aOptions)
-{
+function UpdateProcess(aWin, aGenerator, aOptions) {
   this.win = aWin;
   this.iter = _Iterator(aGenerator);
   this.onItem = aOptions.onItem || function() {};
   this.onBatch = aOptions.onBatch || function() {};
   this.onDone = aOptions.onDone || function() {};
   this.onCancel = aOptions.onCancel || function() {};
   this.threshold = aOptions.threshold || 45;
 
   this.canceled = false;
 }
 
 UpdateProcess.prototype = {
   /**
    * Schedule a new batch on the main loop.
    */
-  schedule: function UP_schedule()
-  {
+  schedule: function() {
     if (this.canceled) {
       return;
     }
     this._timeout = this.win.setTimeout(this._timeoutHandler.bind(this), 0);
   },
 
   /**
    * Cancel the running process.  onItem will not be called again,
    * and onCancel will be called.
    */
-  cancel: function UP_cancel()
-  {
+  cancel: function() {
     if (this._timeout) {
       this.win.clearTimeout(this._timeout);
       this._timeout = 0;
     }
     this.canceled = true;
     this.onCancel();
   },
 
-  _timeoutHandler: function UP_timeoutHandler() {
+  _timeoutHandler: function() {
     this._timeout = null;
     try {
       this._runBatch();
       this.schedule();
     } catch(e) {
       if (e instanceof StopIteration) {
         this.onBatch();
         this.onDone();
         return;
       }
       console.error(e);
       throw e;
     }
   },
 
-  _runBatch: function Y_runBatch()
-  {
+  _runBatch: function() {
     let time = Date.now();
-    while(!this.canceled) {
+    while (!this.canceled) {
       // Continue until iter.next() throws...
       let next = this.iter.next();
       this.onItem(next[1]);
       if ((Date.now() - time) > this.threshold) {
         this.onBatch();
         return;
       }
     }
   }
 };
 
 /**
- * CssComputedView is a panel that manages the display of a table sorted by style.
- * There should be one instance of CssComputedView per style display (of which there
- * will generally only be one).
+ * CssComputedView is a panel that manages the display of a table
+ * sorted by style. There should be one instance of CssComputedView
+ * per style display (of which there will generally only be one).
  *
  * @param {Inspector} inspector toolbox panel
  * @param {Document} document The document that will contain the computed view.
  * @param {PageStyleFront} pageStyle
  *        Front for the page style actor that will be providing
  *        the style information.
  *
  * @constructor
@@ -137,18 +134,18 @@ function CssComputedView(inspector, docu
   this.styleDocument = document;
   this.styleWindow = this.styleDocument.defaultView;
   this.pageStyle = pageStyle;
 
   this.propertyViews = [];
 
   this._outputParser = new OutputParser();
 
-  let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
-    getService(Ci.nsIXULChromeRegistry);
+  let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]
+    .getService(Ci.nsIXULChromeRegistry);
   this.getRTLAttr = chromeReg.isLocaleRTL("global") ? "rtl" : "ltr";
 
   // Create bound methods.
   this.focusWindow = this.focusWindow.bind(this);
   this._onKeypress = this._onKeypress.bind(this);
   this._onContextMenu = this._onContextMenu.bind(this);
   this._onClick = this._onClick.bind(this);
   this._onCopy = this._onCopy.bind(this);
@@ -206,18 +203,17 @@ function CssComputedView(inspector, docu
   this.highlighters.addToView();
 }
 
 /**
  * Memoized lookup of a l10n string from a string bundle.
  * @param {string} aName The key to lookup.
  * @returns A localized version of the given key.
  */
-CssComputedView.l10n = function CssComputedView_l10n(aName)
-{
+CssComputedView.l10n = function(aName) {
   try {
     return CssComputedView._strings.GetStringFromName(aName);
   } catch (ex) {
     Services.console.logStringMessage("Error reading '" + aName + "'");
     throw new Error("l10n error with " + aName);
   }
 };
 
@@ -246,31 +242,31 @@ CssComputedView.prototype = {
 
   // Number of visible properties
   numVisibleProperties: 0,
 
   setPageStyle: function(pageStyle) {
     this.pageStyle = pageStyle;
   },
 
-  get includeBrowserStyles()
-  {
+  get includeBrowserStyles() {
     return this.includeBrowserStylesCheckbox.checked;
   },
 
   _handlePrefChange: function(event, data) {
     if (this._computed && (data.pref == "devtools.defaultColorUnit" ||
         data.pref == PREF_ORIG_SOURCES)) {
       this.refreshPanel();
     }
   },
 
   /**
-   * Update the view with a new selected element.
-   * The CssComputedView panel will show the style information for the given element.
+   * Update the view with a new selected element. The CssComputedView panel
+   * will show the style information for the given element.
+   *
    * @param {NodeFront} aElement The highlighted node to get styles for.
    * @returns a promise that will be resolved when highlighting is complete.
    */
   selectElement: function(aElement) {
     if (!aElement) {
       this.viewedElement = null;
       this.noResults.hidden = false;
 
@@ -378,30 +374,30 @@ CssComputedView.prototype = {
       value.url = node.href;
     } else {
       return null;
     }
 
     return {type, value};
   },
 
-  _createPropertyViews: function()
-  {
+  _createPropertyViews: function() {
     if (this._createViewsPromise) {
       return this._createViewsPromise;
     }
 
     let deferred = promise.defer();
     this._createViewsPromise = deferred.promise;
 
     this.refreshSourceFilter();
     this.numVisibleProperties = 0;
     let fragment = this.styleDocument.createDocumentFragment();
 
-    this._createViewsProcess = new UpdateProcess(this.styleWindow, CssComputedView.propertyNames, {
+    this._createViewsProcess = new UpdateProcess(
+      this.styleWindow, CssComputedView.propertyNames, {
       onItem: (aPropertyName) => {
         // Per-item callback.
         let propView = new PropertyView(this, aPropertyName);
         fragment.appendChild(propView.buildMain());
         fragment.appendChild(propView.buildSelectorContainer());
 
         if (propView.visible) {
           this.numVisibleProperties++;
@@ -421,39 +417,38 @@ CssComputedView.prototype = {
 
     this._createViewsProcess.schedule();
     return deferred.promise;
   },
 
   /**
    * Refresh the panel content.
    */
-  refreshPanel: function CssComputedView_refreshPanel()
-  {
+  refreshPanel: function() {
     if (!this.viewedElement) {
       return promise.resolve();
     }
 
     // Capture the current viewed element to return from the promise handler
     // early if it changed
     let viewedElement = this.viewedElement;
 
     return promise.all([
       this._createPropertyViews(),
       this.pageStyle.getComputed(this.viewedElement, {
         filter: this._sourceFilter,
         onlyMatched: !this.includeBrowserStyles,
         markMatched: true
       })
-    ]).then(([createViews, computed]) => {
+    ]).then(([, computed]) => {
       if (viewedElement !== this.viewedElement) {
-        return;
+        return promise.resolve();
       }
 
-      this._matchedProperties = new Set;
+      this._matchedProperties = new Set();
       for (let name in computed) {
         if (computed[name].matched) {
           this._matchedProperties.add(name);
         }
       }
       this._computed = computed;
 
       if (this._refreshProcess) {
@@ -464,17 +459,18 @@ CssComputedView.prototype = {
 
       // Reset visible property count
       this.numVisibleProperties = 0;
 
       // Reset zebra striping.
       this._darkStripe = true;
 
       let deferred = promise.defer();
-      this._refreshProcess = new UpdateProcess(this.styleWindow, this.propertyViews, {
+      this._refreshProcess = new UpdateProcess(
+        this.styleWindow, this.propertyViews, {
         onItem: (aPropView) => {
           aPropView.refresh();
         },
         onDone: () => {
           this._refreshProcess = null;
           this.noResults.hidden = this.numVisibleProperties > 0;
 
           if (this.searchField.value.length > 0 && !this.numVisibleProperties) {
@@ -506,18 +502,17 @@ CssComputedView.prototype = {
     }
   },
 
   /**
    * Called when the user enters a search term in the filter style search box.
    *
    * @param {Event} aEvent the DOM Event object.
    */
-  _onFilterStyles: function(aEvent)
-  {
+  _onFilterStyles: function(aEvent) {
     let win = this.styleWindow;
 
     if (this._filterChangedTimeout) {
       win.clearTimeout(this._filterChangedTimeout);
     }
 
     let filterTimeout = (this.searchField.value.length > 0)
       ? FILTER_CHANGED_TIMEOUT : 0;
@@ -575,49 +570,45 @@ CssComputedView.prototype = {
     return false;
   },
 
   /**
    * The change event handler for the includeBrowserStyles checkbox.
    *
    * @param {Event} aEvent the DOM Event object.
    */
-  _onIncludeBrowserStyles: function(aEvent)
-  {
+  _onIncludeBrowserStyles: function(aEvent) {
     this.refreshSourceFilter();
     this.refreshPanel();
   },
 
   /**
    * When includeBrowserStylesCheckbox.checked is false we only display
    * properties that have matched selectors and have been included by the
    * document or one of thedocument's stylesheets. If .checked is false we
    * display all properties including those that come from UA stylesheets.
    */
-  refreshSourceFilter: function CssComputedView_setSourceFilter()
-  {
+  refreshSourceFilter: function() {
     this._matchedProperties = null;
     this._sourceFilter = this.includeBrowserStyles ?
                                  CssLogic.FILTER.UA :
                                  CssLogic.FILTER.USER;
   },
 
-  _onSourcePrefChanged: function CssComputedView__onSourcePrefChanged()
-  {
+  _onSourcePrefChanged: function() {
     for (let propView of this.propertyViews) {
       propView.updateSourceLinks();
     }
     this.inspector.emit("computed-view-sourcelinks-updated");
   },
 
   /**
    * The CSS as displayed by the UI.
    */
-  createStyleViews: function CssComputedView_createStyleViews()
-  {
+  createStyleViews: function() {
     if (CssComputedView.propertyNames) {
       return;
     }
 
     CssComputedView.propertyNames = [];
 
     // Here we build and cache a list of css properties supported by the browser
     // We could use any element but let's use the main document's root element
@@ -636,41 +627,39 @@ CssComputedView.prototype = {
     }
 
     CssComputedView.propertyNames.sort();
     CssComputedView.propertyNames.push.apply(CssComputedView.propertyNames,
       mozProps.sort());
 
     this._createPropertyViews().then(null, e => {
       if (!this._isDestroyed) {
-        console.warn("The creation of property views was cancelled because the " +
-          "computed-view was destroyed before it was done creating views");
+        console.warn("The creation of property views was cancelled because " +
+          "the computed-view was destroyed before it was done creating views");
       } else {
         console.error(e);
       }
     });
   },
 
   /**
    * Get a set of properties that have matched selectors.
    *
    * @return {Set} If a property name is in the set, it has matching selectors.
    */
-  get matchedProperties()
-  {
-    return this._matchedProperties || new Set;
+  get matchedProperties() {
+    return this._matchedProperties || new Set();
   },
 
   /**
    * Focus the window on mousedown.
    *
-   * @param aEvent The event object
+   * @param event The event object
    */
-  focusWindow: function(aEvent)
-  {
+  focusWindow: function(event) {
     let win = this.styleDocument.defaultView;
     win.focus();
   },
 
   /**
    * Context menu handler.
    */
   _onContextMenu: function(event) {
@@ -702,18 +691,18 @@ CssComputedView.prototype = {
   /**
    * Copy the current selection to the clipboard
    */
   copySelection: function() {
     try {
       let win = this.styleDocument.defaultView;
       let text = win.getSelection().toString().trim();
 
-      // Tidy up block headings by moving CSS property names and their values onto
-      // the same line and inserting a colon between them.
+      // Tidy up block headings by moving CSS property names and their
+      // values onto the same line and inserting a colon between them.
       let textArray = text.split(/[\r\n]+/);
       let result = "";
 
       // Parse text array to output string.
       if (textArray.length > 1) {
         for (let prop of textArray) {
           if (CssComputedView.propertyNames.indexOf(prop) !== -1) {
             // Property name
@@ -732,18 +721,17 @@ CssComputedView.prototype = {
     } catch(e) {
       console.error(e);
     }
   },
 
   /**
    * Destructor for CssComputedView.
    */
-  destroy: function CssComputedView_destroy()
-  {
+  destroy: function() {
     this.viewedElement = null;
     this._outputParser = null;
 
     gDevTools.off("pref-changed", this._handlePrefChange);
 
     this._prefObserver.off(PREF_ORIG_SOURCES, this._onSourcePrefChanged);
     this._prefObserver.destroy();
 
@@ -814,18 +802,17 @@ PropertyInfo.prototype = {
 /**
  * A container to give easy access to property data from the template engine.
  *
  * @constructor
  * @param {CssComputedView} aTree the CssComputedView instance we are working with.
  * @param {string} aName the CSS property name for which this PropertyView
  * instance will render the rules.
  */
-function PropertyView(aTree, aName)
-{
+function PropertyView(aTree, aName) {
   this.tree = aTree;
   this.name = aName;
   this.getRTLAttr = aTree.getRTLAttr;
 
   this.link = "https://developer.mozilla.org/CSS/" + aName;
 
   this._propertyInfo = new PropertyInfo(aTree, aName);
 }
@@ -859,42 +846,38 @@ PropertyView.prototype = {
   prevViewedElement: null,
 
   /**
    * Get the computed style for the current property.
    *
    * @return {string} the computed style for the current property of the
    * currently highlighted element.
    */
-  get value()
-  {
+  get value() {
     return this.propertyInfo.value;
   },
 
   /**
    * An easy way to access the CssPropertyInfo behind this PropertyView.
    */
-  get propertyInfo()
-  {
+  get propertyInfo() {
     return this._propertyInfo;
   },
 
   /**
    * Does the property have any matched selectors?
    */
-  get hasMatchedSelectors()
-  {
+  get hasMatchedSelectors() {
     return this.tree.matchedProperties.has(this.name);
   },
 
   /**
    * Should this property be visible?
    */
-  get visible()
-  {
+  get visible() {
     if (!this.tree.viewedElement) {
       return false;
     }
 
     if (!this.tree.includeBrowserStyles && !this.hasMatchedSelectors) {
       return false;
     }
 
@@ -908,45 +891,42 @@ PropertyView.prototype = {
 
     return true;
   },
 
   /**
    * Returns the className that should be assigned to the propertyView.
    * @return string
    */
-  get propertyHeaderClassName()
-  {
+  get propertyHeaderClassName() {
     if (this.visible) {
       let isDark = this.tree._darkStripe = !this.tree._darkStripe;
       return isDark ? "property-view row-striped" : "property-view";
     }
     return "property-view-hidden";
   },
 
   /**
    * Returns the className that should be assigned to the propertyView content
    * container.
    * @return string
    */
-  get propertyContentClassName()
-  {
+  get propertyContentClassName() {
     if (this.visible) {
       let isDark = this.tree._darkStripe;
       return isDark ? "property-content row-striped" : "property-content";
     }
     return "property-content-hidden";
   },
 
   /**
    * Build the markup for on computed style
    * @return Element
    */
-  buildMain: function PropertyView_buildMain()
-  {
+  buildMain: function() {
     let doc = this.tree.styleDocument;
 
     // Build the container element
     this.onMatchedToggle = this.onMatchedToggle.bind(this);
     this.element = doc.createElementNS(HTML_NS, "div");
     this.element.setAttribute("class", this.propertyHeaderClassName);
     this.element.addEventListener("dblclick", this.onMatchedToggle, false);
 
@@ -993,33 +973,31 @@ PropertyView.prototype = {
     this.valueNode.setAttribute("dir", "ltr");
     // Make it hand over the focus to the container
     this.valueNode.addEventListener("click", this.onFocus, false);
     this.element.appendChild(this.valueNode);
 
     return this.element;
   },
 
-  buildSelectorContainer: function PropertyView_buildSelectorContainer()
-  {
+  buildSelectorContainer: function() {
     let doc = this.tree.styleDocument;
     let element = doc.createElementNS(HTML_NS, "div");
     element.setAttribute("class", this.propertyContentClassName);
     this.matchedSelectorsContainer = doc.createElementNS(HTML_NS, "div");
     this.matchedSelectorsContainer.setAttribute("class", "matchedselectors");
     element.appendChild(this.matchedSelectorsContainer);
 
     return element;
   },
 
   /**
    * Refresh the panel's CSS property value.
    */
-  refresh: function PropertyView_refresh()
-  {
+  refresh: function() {
     this.element.className = this.propertyHeaderClassName;
     this.element.nextElementSibling.className = this.propertyContentClassName;
 
     if (this.prevViewedElement != this.tree.viewedElement) {
       this._matchedSelectorViews = null;
       this.prevViewedElement = this.tree.viewedElement;
     }
 
@@ -1046,50 +1024,50 @@ PropertyView.prototype = {
     this.valueNode.appendChild(frag);
 
     this.refreshMatchedSelectors();
   },
 
   /**
    * Refresh the panel matched rules.
    */
-  refreshMatchedSelectors: function PropertyView_refreshMatchedSelectors()
-  {
+  refreshMatchedSelectors: function() {
     let hasMatchedSelectors = this.hasMatchedSelectors;
     this.matchedSelectorsContainer.parentNode.hidden = !hasMatchedSelectors;
 
     if (hasMatchedSelectors) {
       this.matchedExpander.classList.add("expandable");
     } else {
       this.matchedExpander.classList.remove("expandable");
     }
 
     if (this.matchedExpanded && hasMatchedSelectors) {
-      return this.tree.pageStyle.getMatchedSelectors(this.tree.viewedElement, this.name).then(matched => {
-        if (!this.matchedExpanded) {
-          return;
-        }
+      return this.tree.pageStyle
+        .getMatchedSelectors(this.tree.viewedElement, this.name)
+        .then(matched => {
+          if (!this.matchedExpanded) {
+            return promise.resolve(undefined);
+          }
 
-        this._matchedSelectorResponse = matched;
+          this._matchedSelectorResponse = matched;
 
-        return this._buildMatchedSelectors().then(() => {
-          this.matchedExpander.setAttribute("open", "");
-          this.tree.inspector.emit("computed-view-property-expanded");
-        });
-      }).then(null, console.error);
-    } else {
-      this.matchedSelectorsContainer.innerHTML = "";
-      this.matchedExpander.removeAttribute("open");
-      this.tree.inspector.emit("computed-view-property-collapsed");
-      return promise.resolve(undefined);
+          return this._buildMatchedSelectors().then(() => {
+            this.matchedExpander.setAttribute("open", "");
+            this.tree.inspector.emit("computed-view-property-expanded");
+          });
+        }).then(null, console.error);
     }
+
+    this.matchedSelectorsContainer.innerHTML = "";
+    this.matchedExpander.removeAttribute("open");
+    this.tree.inspector.emit("computed-view-property-collapsed");
+    return promise.resolve(undefined);
   },
 
-  get matchedSelectors()
-  {
+  get matchedSelectors() {
     return this._matchedSelectorResponse;
   },
 
   _buildMatchedSelectors: function() {
     let promises = [];
     let frag = this.element.ownerDocument.createDocumentFragment();
 
     for (let selector of this.matchedSelectorViews) {
@@ -1125,76 +1103,73 @@ PropertyView.prototype = {
     this.matchedSelectorsContainer.appendChild(frag);
     return promise.all(promises);
   },
 
   /**
    * Provide access to the matched SelectorViews that we are currently
    * displaying.
    */
-  get matchedSelectorViews()
-  {
+  get matchedSelectorViews() {
     if (!this._matchedSelectorViews) {
       this._matchedSelectorViews = [];
       this._matchedSelectorResponse.forEach(
-        function matchedSelectorViews_convert(aSelectorInfo) {
-          this._matchedSelectorViews.push(new SelectorView(this.tree, aSelectorInfo));
+        function(aSelectorInfo) {
+          let selectorView = new SelectorView(this.tree, aSelectorInfo);
+          this._matchedSelectorViews.push(selectorView);
         }, this);
     }
     return this._matchedSelectorViews;
   },
 
   /**
    * Update all the selector source links to reflect whether we're linking to
    * original sources (e.g. Sass files).
    */
-  updateSourceLinks: function PropertyView_updateSourceLinks()
-  {
+  updateSourceLinks: function() {
     if (!this._matchedSelectorViews) {
       return;
     }
     for (let view of this._matchedSelectorViews) {
       view.updateSourceLink();
     }
   },
 
   /**
    * The action when a user expands matched selectors.
    *
    * @param {Event} aEvent Used to determine the class name of the targets click
    * event.
    */
-  onMatchedToggle: function PropertyView_onMatchedToggle(aEvent)
-  {
+  onMatchedToggle: function(aEvent) {
     if (aEvent.shiftKey) {
       return;
     }
     this.matchedExpanded = !this.matchedExpanded;
     this.refreshMatchedSelectors();
     aEvent.preventDefault();
   },
 
   /**
    * The action when a user clicks on the MDN help link for a property.
    */
-  mdnLinkClick: function PropertyView_mdnLinkClick(aEvent)
-  {
+  mdnLinkClick: function(aEvent) {
     let inspector = this.tree.inspector;
 
     if (inspector.target.tab) {
       let browserWin = inspector.target.tab.ownerDocument.defaultView;
       browserWin.openUILinkIn(this.link, "tab");
     }
     aEvent.preventDefault();
   },
 
   /**
    * Destroy this property view, removing event listeners
    */
-  destroy: function PropertyView_destroy() {
+  destroy: function() {
     this.element.removeEventListener("dblclick", this.onMatchedToggle, false);
     this.element.removeEventListener("keydown", this.onKeyDown, false);
     this.element = null;
 
     this.matchedExpander.removeEventListener("click", this.onMatchedToggle, false);
     this.matchedExpander = null;
 
     this.nameNode.removeEventListener("click", this.onFocus, false);
@@ -1205,18 +1180,17 @@ PropertyView.prototype = {
   }
 };
 
 /**
  * A container to give us easy access to display data from a CssRule
  * @param CssComputedView aTree, the owning CssComputedView
  * @param aSelectorInfo
  */
-function SelectorView(aTree, aSelectorInfo)
-{
+function SelectorView(aTree, aSelectorInfo) {
   this.tree = aTree;
   this.selectorInfo = aSelectorInfo;
   this._cacheStatusNames();
 
   this.openStyleEditor = this.openStyleEditor.bind(this);
   this.maybeOpenStyleEditor = this.maybeOpenStyleEditor.bind(this);
 
   this.ready = this.updateSourceLink();
@@ -1240,71 +1214,63 @@ SelectorView.prototype = {
    * Cache localized status names.
    *
    * These statuses are localized inside the styleinspector.properties string
    * bundle.
    * @see css-logic.js - the CssLogic.STATUS array.
    *
    * @return {void}
    */
-  _cacheStatusNames: function SelectorView_cacheStatusNames()
-  {
+  _cacheStatusNames: function() {
     if (SelectorView.STATUS_NAMES.length) {
       return;
     }
 
     for (let status in CssLogic.STATUS) {
       let i = CssLogic.STATUS[status];
       if (i > CssLogic.STATUS.UNMATCHED) {
         let value = CssComputedView.l10n("rule.status." + status);
         // Replace normal spaces with non-breaking spaces
-        SelectorView.STATUS_NAMES[i] = value.replace(/ /g, '\u00A0');
+        SelectorView.STATUS_NAMES[i] = value.replace(/ /g, "\u00A0");
       }
     }
   },
 
   /**
    * A localized version of cssRule.status
    */
-  get statusText()
-  {
+  get statusText() {
     return SelectorView.STATUS_NAMES[this.selectorInfo.status];
   },
 
   /**
    * Get class name for selector depending on status
    */
-  get statusClass()
-  {
+  get statusClass() {
     return SelectorView.CLASS_NAMES[this.selectorInfo.status - 1];
   },
 
-  get href()
-  {
+  get href() {
     if (this._href) {
       return this._href;
     }
     let sheet = this.selectorInfo.rule.parentStyleSheet;
     this._href = sheet ? sheet.href : "#";
     return this._href;
   },
 
-  get sourceText()
-  {
+  get sourceText() {
     return this.selectorInfo.sourceText;
   },
 
-
-  get value()
-  {
+  get value() {
     return this.selectorInfo.value;
   },
 
-  get outputFragment()
-  {
+  get outputFragment() {
     // Sadly, because this fragment is added to the template by DOM Templater
     // we lose any events that are attached. This means that URLs will open in a
     // new window. At some point we should fix this by stopping using the
     // templater.
     let outputParser = this.tree._outputParser;
     let frag = outputParser.parseCssProperty(
       this.selectorInfo.name,
       this.selectorInfo.value, {
@@ -1315,35 +1281,33 @@ SelectorView.prototype = {
     });
     return frag;
   },
 
   /**
    * Update the text of the source link to reflect whether we're showing
    * original sources or not.
    */
-  updateSourceLink: function()
-  {
+  updateSourceLink: function() {
     return this.updateSource().then((oldSource) => {
       if (oldSource != this.source && this.tree.element) {
         let selector = '[sourcelocation="' + oldSource + '"]';
         let link = this.tree.element.querySelector(selector);
         if (link) {
           link.textContent = this.source;
           link.setAttribute("sourcelocation", this.source);
         }
       }
     });
   },
 
   /**
    * Update the 'source' store based on our original sources preference.
    */
-  updateSource: function()
-  {
+  updateSource: function() {
     let rule = this.selectorInfo.rule;
     this.sheet = rule.parentStyleSheet;
 
     if (!rule || !this.sheet) {
       let oldSource = this.source;
       this.source = CssLogic.l10n("rule.sourceElement");
       return promise.resolve(oldSource);
     }
@@ -1368,53 +1332,44 @@ SelectorView.prototype = {
     let oldSource = this.source;
     this.source = CssLogic.shortSource(this.sheet) + ":" + rule.line;
     return promise.resolve(oldSource);
   },
 
   /**
    * Open the style editor if the RETURN key was pressed.
    */
-  maybeOpenStyleEditor: function(aEvent)
-  {
+  maybeOpenStyleEditor: function(aEvent) {
     let keyEvent = Ci.nsIDOMKeyEvent;
     if (aEvent.keyCode == keyEvent.DOM_VK_RETURN) {
       this.openStyleEditor();
     }
   },
 
   /**
    * When a css link is clicked this method is called in order to either:
    *   1. Open the link in view source (for chrome stylesheets).
    *   2. Open the link in the style editor.
    *
    *   We can only view stylesheets contained in document.styleSheets inside the
    *   style editor.
    *
    * @param aEvent The click event
    */
-  openStyleEditor: function(aEvent)
-  {
+  openStyleEditor: function(aEvent) {
     let inspector = this.tree.inspector;
     let rule = this.selectorInfo.rule;
 
     // The style editor can only display stylesheets coming from content because
     // chrome stylesheets are not listed in the editor's stylesheet selector.
     //
     // If the stylesheet is a content stylesheet we send it to the style
     // editor else we display it in the view source window.
-    let sheet = rule.parentStyleSheet;
-    if (!sheet || sheet.isSystem) {
-      let contentDoc = null;
-      if (this.tree.viewedElement.isLocal_toBeDeprecated()) {
-        let rawNode = this.tree.viewedElement.rawNode();
-        if (rawNode) {
-          contentDoc = rawNode.ownerDocument;
-        }
-      }
+    let parentStyleSheet = rule.parentStyleSheet;
+    if (!parentStyleSheet || parentStyleSheet.isSystem) {
       let toolbox = gDevTools.getToolbox(inspector.target);
       toolbox.viewSource(rule.href, rule.line);
       return;
     }
 
     let location = promise.resolve(rule.location);
     if (Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
       location = rule.getOriginalLocation();
@@ -1442,17 +1397,17 @@ SelectorView.prototype = {
  *        A set of attributes to set on the node.
  */
 function createChild(aParent, aTag, aAttributes={}) {
   let elt = aParent.ownerDocument.createElementNS(HTML_NS, aTag);
   for (let attr in aAttributes) {
     if (aAttributes.hasOwnProperty(attr)) {
       if (attr === "textContent") {
         elt.textContent = aAttributes[attr];
-      } else if(attr === "child") {
+      } else if (attr === "child") {
         elt.appendChild(aAttributes[attr]);
       } else {
         elt.setAttribute(attr, aAttributes[attr]);
       }
     }
   }
   aParent.appendChild(elt);
   return elt;
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1,16 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* globals overlays, Services, EventEmitter, StyleInspectorMenu,
-   clipboardHelper, _strings, domUtils, AutocompletePopup */
+   clipboardHelper, _strings, domUtils, AutocompletePopup, loader,
+   osString */
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 const {CssLogic} = require("devtools/styleinspector/css-logic");
 const {InplaceEditor, editableField, editableItem} =
       require("devtools/shared/inplace-editor");
@@ -204,17 +205,17 @@ ElementStyle.prototype = {
    */
   populate: function() {
     let populated = this.pageStyle.getApplied(this.element, {
       inherited: true,
       matchedSelectors: true,
       filter: this.showUserAgentStyles ? "ua" : undefined,
     }).then(entries => {
       if (this.destroyed) {
-        return;
+        return promise.resolve(undefined);
       }
 
       // Make sure the dummy element has been created before continuing...
       return this.dummyElementPromise.then(() => {
         if (this.populated != populated) {
           // Don't care anymore.
           return;
         }
@@ -231,24 +232,22 @@ ElementStyle.prototype = {
 
         // Mark overridden computed styles.
         this.markOverriddenAll();
 
         this._sortRulesForPseudoElement();
 
         // We're done with the previous list of rules.
         delete this._refreshRules;
-
-        return null;
       });
     }).then(null, e => {
       // populate is often called after a setTimeout,
       // the connection may already be closed.
       if (this.destroyed) {
-        return;
+        return promise.resolve(undefined);
       }
       return promiseWarn(e);
     });
     this.populated = populated;
     return this.populated;
   },
 
   /**
@@ -631,17 +630,17 @@ Rule.prototype = {
     // Store disabled properties in the disabled store.
     let disabled = this.elementStyle.store.disabled;
     if (disabledProps.length > 0) {
       disabled.set(this.style, disabledProps);
     } else {
       disabled.delete(this.style);
     }
 
-    let promise = aModifications.apply().then(() => {
+    let modificationsPromise = aModifications.apply().then(() => {
       let cssProps = {};
       for (let cssProp of parseDeclarations(this.style.cssText)) {
         cssProps[cssProp.name] = cssProp;
       }
 
       for (let textProp of this.textProps) {
         if (!textProp.enabled) {
           continue;
@@ -663,18 +662,18 @@ Rule.prototype = {
 
       if (promise === this._applyingModifications) {
         this._applyingModifications = null;
       }
 
       this.elementStyle._changed();
     }).then(null, promiseWarn);
 
-    this._applyingModifications = promise;
-    return promise;
+    this._applyingModifications = modificationsPromise;
+    return modificationsPromise;
   },
 
   /**
    * Renames a property.
    *
    * @param {TextProperty} aProperty
    *        The property to rename.
    * @param {string} aName
@@ -1106,17 +1105,18 @@ TextProperty.prototype = {
     this.rule.removeProperty(this);
   },
 
   /**
    * Return a string representation of the rule property.
    */
   stringifyProperty: function() {
     // Get the displayed property value
-    let declaration = this.name + ": " + this.editor.committed.value + ";";
+    let declaration = this.name + ": " + this.editor.valueSpan.textContent +
+      ";";
 
     // Comment out property declarations that are not enabled
     if (!this.enabled) {
       declaration = "/* " + declaration + " */";
     }
 
     return declaration;
   }
@@ -1736,17 +1736,17 @@ CssRuleView.prototype = {
   },
 
   /**
    * Update the rules for the currently highlighted element.
    */
   refreshPanel: function() {
     // Ignore refreshes during editing or when no element is selected.
     if (this.isEditing || !this._elementStyle) {
-      return;
+      return promise.resolve(undefined);
     }
 
     // Repopulate the element style once the current modifications are done.
     let promises = [];
     for (let rule of this._elementStyle.rules) {
       if (rule._applyingModifications) {
         promises.push(rule._applyingModifications);
       }
@@ -1888,19 +1888,20 @@ CssRuleView.prototype = {
       this._showPseudoElements =
         Services.prefs.getBoolPref("devtools.inspector.show_pseudo_elements");
     }
     return this._showPseudoElements;
   },
 
   /**
    * Creates an expandable container in the rule view
-   * @param  {String}  aLabel The label for the container header
-   * @param  {Boolean} isPseudo Whether or not the container will hold
-   *                            pseudo element rules
+   * @param  {String} aLabel
+   *         The label for the container header
+   * @param  {Boolean} isPseudo
+   *         Whether or not the container will hold pseudo element rules
    * @return {DOMNode} The container element
    */
   createExpandableContainer: function(aLabel, isPseudo = false) {
     let header = this.styleDocument.createElementNS(HTML_NS, "div");
     header.className = this._getRuleViewHeaderClassName(true);
     header.classList.add("show-expandable-container");
     header.textContent = aLabel;
 
@@ -1910,54 +1911,69 @@ CssRuleView.prototype = {
 
     header.insertBefore(twisty, header.firstChild);
     this.element.appendChild(header);
 
     let container = this.styleDocument.createElementNS(HTML_NS, "div");
     container.classList.add("ruleview-expandable-container");
     this.element.appendChild(container);
 
-    let toggleContainerVisibility = (isPseudo, showPseudo) => {
-      let isOpen = twisty.getAttribute("open");
-
-      if (isPseudo) {
-        this._showPseudoElements = !!showPseudo;
-
-        Services.prefs.setBoolPref("devtools.inspector.show_pseudo_elements",
-          this.showPseudoElements);
-
-        header.classList.toggle("show-expandable-container",
-          this.showPseudoElements);
-
-        isOpen = !this.showPseudoElements;
-      } else {
-        header.classList.toggle("show-expandable-container");
-      }
-
-      if (isOpen) {
-        twisty.removeAttribute("open");
-      } else {
-        twisty.setAttribute("open", "true");
-      }
-    };
-
     header.addEventListener("dblclick", () => {
-      toggleContainerVisibility(isPseudo, !this.showPseudoElements);
+      this._toggleContainerVisibility(twisty, header, isPseudo,
+        !this.showPseudoElements);
     }, false);
+
     twisty.addEventListener("click", () => {
-      toggleContainerVisibility(isPseudo, !this.showPseudoElements);
+      this._toggleContainerVisibility(twisty, header, isPseudo,
+        !this.showPseudoElements);
     }, false);
 
     if (isPseudo) {
-      toggleContainerVisibility(isPseudo, this.showPseudoElements);
+      this._toggleContainerVisibility(twisty, header, isPseudo,
+        this.showPseudoElements);
     }
 
     return container;
   },
 
+  /**
+   * Toggle the visibility of an expandable container
+   * @param  {DOMNode}  twisty
+   *         clickable toggle DOM Node
+   * @param  {DOMNode}  header
+   *         expandable container header DOM Node
+   * @param  {Boolean}  isPseudo
+   *         whether or not the container will hold pseudo element rules
+   * @param  {Boolean}  showPseudo
+   *         whether or not pseudo element rules should be displayed
+   */
+  _toggleContainerVisibility: function(twisty, header, isPseudo, showPseudo) {
+    let isOpen = twisty.getAttribute("open");
+
+    if (isPseudo) {
+      this._showPseudoElements = !!showPseudo;
+
+      Services.prefs.setBoolPref("devtools.inspector.show_pseudo_elements",
+        this.showPseudoElements);
+
+      header.classList.toggle("show-expandable-container",
+        this.showPseudoElements);
+
+      isOpen = !this.showPseudoElements;
+    } else {
+      header.classList.toggle("show-expandable-container");
+    }
+
+    if (isOpen) {
+      twisty.removeAttribute("open");
+    } else {
+      twisty.setAttribute("open", "true");
+    }
+  },
+
   _getRuleViewHeaderClassName: function(isPseudo) {
     let baseClassName = "theme-gutter ruleview-header";
     return isPseudo ? baseClassName + " ruleview-expandable-header" :
       baseClassName;
   },
 
   /**
    * Creates editor UI for each of the rules in _elementStyle.
--- a/browser/devtools/styleinspector/style-inspector-menu.js
+++ b/browser/devtools/styleinspector/style-inspector-menu.js
@@ -226,16 +226,17 @@ StyleInspectorMenu.prototype = {
   /**
    * Display the necessary copy context menu items depending on the clicked
    * node and selection in the rule view.
    */
   _updateCopyMenuItems: function() {
     this.menuitemCopy.hidden = !this._hasTextSelected();
     this.menuitemCopyColor.hidden = !this._isColorPopup();
     this.menuitemCopyImageDataUrl.hidden = !this._isImageUrl();
+    this.menuitemCopyUrl.hidden = !this._isImageUrl();
 
     this.menuitemCopyRule.hidden = true;
     this.menuitemCopyLocation.hidden = true;
     this.menuitemCopyPropertyDeclaration.hidden = true;
     this.menuitemCopyPropertyName.hidden = true;
     this.menuitemCopyPropertyValue.hidden = true;
     this.menuitemCopySelector.hidden = true;
 
@@ -373,16 +374,20 @@ StyleInspectorMenu.prototype = {
   _onCopyColor: function() {
     clipboardHelper.copyString(this._colorToCopy);
   },
 
   /*
    * Retrieve the url for the selected image and copy it to the clipboard
    */
   _onCopyUrl: function() {
+    if (!this._clickedNodeInfo) {
+      return;
+    }
+
     clipboardHelper.copyString(this._clickedNodeInfo.value.url);
   },
 
   /**
    * Retrieve the image data for the selected image url and copy it to the clipboard
    */
   _onCopyImageDataUrl: Task.async(function*() {
     if (!this._clickedNodeInfo) {
--- a/browser/devtools/styleinspector/test/browser_ruleview_copy_styles.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_copy_styles.js
@@ -48,37 +48,66 @@ add_task(function*() {
         copyPropertyDeclaration: false,
         copyPropertyName: true,
         copyPropertyValue: false,
         copySelector: true,
         copyRule: false
       }
     },
     {
+      desc: "Test Copy Property Value with Priority",
+      node: ruleEditor.rule.textProps[3].editor.valueSpan,
+      menuItem: contextmenu.menuitemCopyPropertyValue,
+      expectedPattern: "#00F !important",
+      hidden: {
+        copyLocation: true,
+        copyPropertyDeclaration: false,
+        copyPropertyName: true,
+        copyPropertyValue: false,
+        copySelector: true,
+        copyRule: false
+      }
+    },
+    {
       desc: "Test Copy Property Declaration",
       node: ruleEditor.rule.textProps[2].editor.nameSpan,
       menuItem: contextmenu.menuitemCopyPropertyDeclaration,
       expectedPattern: "font-size: 12px;",
       hidden: {
         copyLocation: true,
         copyPropertyDeclaration: false,
         copyPropertyName: false,
         copyPropertyValue: true,
         copySelector: true,
         copyRule: false
       }
     },
     {
+      desc: "Test Copy Property Declaration with Priority",
+      node: ruleEditor.rule.textProps[3].editor.nameSpan,
+      menuItem: contextmenu.menuitemCopyPropertyDeclaration,
+      expectedPattern: "border-color: #00F !important;",
+      hidden: {
+        copyLocation: true,
+        copyPropertyDeclaration: false,
+        copyPropertyName: false,
+        copyPropertyValue: true,
+        copySelector: true,
+        copyRule: false
+      }
+    },
+    {
       desc: "Test Copy Rule",
       node: ruleEditor.rule.textProps[2].editor.nameSpan,
       menuItem: contextmenu.menuitemCopyRule,
       expectedPattern: "#testid {[\\r\\n]+" +
                        "\tcolor: #F00;[\\r\\n]+" +
                        "\tbackground-color: #00F;[\\r\\n]+" +
                        "\tfont-size: 12px;[\\r\\n]+" +
+                       "\tborder-color: #00F !important;[\\r\\n]+" +
                        "}",
       hidden: {
         copyLocation: true,
         copyPropertyDeclaration: false,
         copyPropertyName: false,
         copyPropertyValue: true,
         copySelector: true,
         copyRule: false
@@ -119,16 +148,17 @@ add_task(function*() {
       },
       desc: "Test Copy Rule with Disabled Property",
       node: ruleEditor.rule.textProps[2].editor.nameSpan,
       menuItem: contextmenu.menuitemCopyRule,
       expectedPattern: "#testid {[\\r\\n]+" +
                        "\t\/\\* color: #F00; \\*\/[\\r\\n]+" +
                        "\tbackground-color: #00F;[\\r\\n]+" +
                        "\tfont-size: 12px;[\\r\\n]+" +
+                       "\tborder-color: #00F !important;[\\r\\n]+" +
                        "}",
       hidden: {
         copyLocation: true,
         copyPropertyDeclaration: false,
         copyPropertyName: false,
         copyPropertyValue: true,
         copySelector: true,
         copyRule: false
--- a/browser/devtools/styleinspector/test/browser_styleinspector_context-menu-copy-urls.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_context-menu-copy-urls.js
@@ -75,16 +75,17 @@ function* testCopyUrlToClipboard({view, 
   let rect = imageLink.getClientRects()[0];
   let x = rect.left + 2;
   let y = rect.top + 2;
 
   EventUtils.synthesizeMouseAtPoint(x, y, {button: 2, type: "contextmenu"}, getViewWindow(view));
   yield popup;
 
   info("Context menu is displayed");
+  ok(!view._contextmenu.menuitemCopyUrl.hidden, "\"Copy URL\" menu entry is displayed");
   ok(!view._contextmenu.menuitemCopyImageDataUrl.hidden, "\"Copy Image Data-URL\" menu entry is displayed");
 
   if (type == "data-uri") {
     info("Click Copy Data URI and wait for clipboard");
     yield waitForClipboard(() => view._contextmenu.menuitemCopyImageDataUrl.click(), expected);
   } else {
     info("Click Copy URL and wait for clipboard");
     yield waitForClipboard(() => view._contextmenu.menuitemCopyUrl.click(), expected);
--- a/browser/devtools/styleinspector/test/doc_copystyles.css
+++ b/browser/devtools/styleinspector/test/doc_copystyles.css
@@ -1,9 +1,10 @@
 /* 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/. */
 
 html, body, #testid {
   color: #F00;
   background-color: #00F;
   font-size: 12px;
+  border-color: #00F !important;
 }
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -553,16 +553,17 @@
 @RESPATH@/components/PhoneNumberService.js
 @RESPATH@/components/PhoneNumberService.manifest
 @RESPATH@/components/NotificationStorage.js
 @RESPATH@/components/NotificationStorage.manifest
 @RESPATH@/components/AlarmsManager.js
 @RESPATH@/components/AlarmsManager.manifest
 @RESPATH@/components/Push.js
 @RESPATH@/components/Push.manifest
+@RESPATH@/components/PushClient.js
 @RESPATH@/components/PushNotificationService.js
 
 @RESPATH@/components/SlowScriptDebug.manifest
 @RESPATH@/components/SlowScriptDebug.js
 
 #ifndef RELEASE_BUILD
 @RESPATH@/components/InterAppComm.manifest
 @RESPATH@/components/InterAppCommService.js
@@ -632,16 +633,17 @@
 @RESPATH@/modules/*
 
 ; Safe Browsing
 #ifdef MOZ_URL_CLASSIFIER
 @RESPATH@/components/nsURLClassifier.manifest
 @RESPATH@/components/nsUrlClassifierHashCompleter.js
 @RESPATH@/components/nsUrlClassifierListManager.js
 @RESPATH@/components/nsUrlClassifierLib.js
+@RESPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js
 @RESPATH@/components/url-classifier.xpt
 #endif
 
 ; ANGLE GLES-on-D3D rendering library
 #ifdef MOZ_ANGLE_RENDERER
 @BINPATH@/libEGL.dll
 @BINPATH@/libGLESv2.dll
 
--- a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
+++ b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
@@ -92,16 +92,19 @@ quit-button.tooltiptext.linux2 = Quit %1
 # LOCALIZATION NOTE(quit-button.tooltiptext.mac): %1$S is the brand name (e.g. Firefox),
 # %2$S is the keyboard shortcut
 quit-button.tooltiptext.mac = Quit %1$S (%2$S)
 
 # LOCALIZATION NOTE(loop-call-button3.label): This is a brand name, request
 # approval before you change it.
 loop-call-button3.label = Hello
 loop-call-button3.tooltiptext = Start a conversation
+# LOCALIZATION NOTE(loop-call-button3-pb.tooltiptext): Shown when the button is
+# placed inside a Private Browsing window. %S is the value of loop-call-button3.label.
+loop-call-button3-pb.tooltiptext = %S is not available in Private Browsing
 
 social-share-button.label = Share This Page
 social-share-button.tooltiptext = Share this page
 
 panic-button.label = Forget
 panic-button.tooltiptext = Forget about some browsing history
 
 web-apps-button.label = Apps
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -201,16 +201,18 @@ hangup_button_caption2=Exit
 mute_local_audio_button_title=Mute your audio
 unmute_local_audio_button_title=Unmute your audio
 mute_local_video_button_title=Mute your video
 unmute_local_video_button_title=Unmute your video
 active_screenshare_button_title=Stop sharing
 inactive_screenshare_button_title=Share your screen
 share_tabs_button_title2=Share your Tabs
 share_windows_button_title=Share other Windows
+self_view_hidden_message=Self-view hidden but still being sent; resize window to show
+
 
 ## LOCALIZATION NOTE (call_with_contact_title): The title displayed
 ## when calling a contact. Don't translate the part between {{..}} because
 ## this will be replaced by the contact's name.
 call_with_contact_title=Conversation with {{contactName}}
 
 # Outgoing conversation
 
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -110,17 +110,17 @@
         #titlebar-buttonbox,
         .titlebar-button {
           -moz-appearance: none !important;
         }
 
         .titlebar-button {
           border: none;
           margin: 0 !important;
-          padding: 12px 17px;
+          padding: 10px 17px;
         }
 
         #main-window[sizemode=maximized] .titlebar-button {
           padding-top: 8px;
           padding-bottom: 8px;
         }
 
         .titlebar-button > .toolbarbutton-icon {
@@ -142,16 +142,30 @@
 
         #titlebar-close {
           list-style-image: url(chrome://browser/skin/caption-buttons.svg#close);
         }
         #titlebar-close:hover {
           list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
         }
 
+        #titlebar-min:-moz-lwtheme {
+          list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-themes);
+        }
+        #titlebar-max:-moz-lwtheme {
+          list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-themes);
+        }
+        #main-window[sizemode="maximized"] #titlebar-max:-moz-lwtheme {
+          list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-themes);
+        }
+        #titlebar-close:-moz-lwtheme {
+          list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-themes);
+        }
+
+
         /* the 12px image renders a 10px icon, and the 10px upscaled gets rounded to 12.5, which
          * rounds up to 13px, which makes the icon one pixel too big on 1.25dppx. Fix: */
         @media (min-resolution: 1.20dppx) and (max-resolution: 1.45dppx) {
           .titlebar-button > .toolbarbutton-icon {
             width: 11.5px;
             height: 11.5px;
           }
         }
@@ -217,30 +231,42 @@
         @media not all and (-moz-windows-default-theme) {
           .titlebar-button {
             background-color: -moz-field;
           }
           .titlebar-button:hover {
             background-color: Highlight;
           }
 
+          #titlebar-min {
+            list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-highcontrast);
+          }
           #titlebar-min:hover {
-            list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-highlight);
+            list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-highcontrast-hover);
           }
 
+          #titlebar-max {
+            list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-highcontrast);
+          }
           #titlebar-max:hover {
-            list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-highlight);
+            list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-highcontrast-hover);
           }
 
+          #main-window[sizemode="maximized"] #titlebar-max {
+            list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-highcontrast);
+          }
           #main-window[sizemode="maximized"] #titlebar-max:hover {
-            list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-highlight);
+            list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-highcontrast-hover);
           }
 
+          #titlebar-close {
+            list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-highcontrast);
+          }
           #titlebar-close:hover {
-            list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-highlight);
+            list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-highcontrast-hover);
           }
         }
       }
     }
   }
 
   @media (-moz-os-version: windows-vista),
          (-moz-os-version: windows-win7),
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -654,17 +654,17 @@ toolbar[brighttext] .toolbarbutton-1 > .
   -moz-box-pack: center;
 }
 
 #nav-bar #PanelUI-menu-button {
   -moz-padding-start: 5px;
   -moz-padding-end: 5px;
 }
 
-#nav-bar .toolbarbutton-1[type=panel]:not(#back-button):not(#forward-button):not(#feed-button):not(#PanelUI-menu-button),
+#nav-bar .toolbarbutton-1[type=panel],
 #nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#PanelUI-menu-button) {
   padding-left: 5px;
   padding-right: 5px;
 }
 
 #nav-bar .toolbarbutton-1 > menupopup {
   margin-top: -3px;
 }
@@ -766,18 +766,18 @@ toolbarbutton[constrain-size="true"][cui
   width: 16px;
 }
 
 #nav-bar toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
   /* XXXgijs box models strike again: this is 16px + 2 * 7px padding + 2 * 1px border (from the rules above) */
   width: 32px;
 }
 
-#nav-bar .toolbarbutton-1[type=panel]:not(#back-button):not(#forward-button):not(#feed-button):not(#PanelUI-menu-button) > .toolbarbutton-icon,
-#nav-bar .toolbarbutton-1[type=panel]:not(#back-button):not(#forward-button):not(#feed-button):not(#PanelUI-menu-button) > .toolbarbutton-badge-container,
+#nav-bar .toolbarbutton-1[type=panel] > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1[type=panel] > .toolbarbutton-badge-container,
 #nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#PanelUI-menu-button) > .toolbarbutton-icon,
 #nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#PanelUI-menu-button) > .toolbarbutton-badge-container,
 #nav-bar .toolbarbutton-1[type=menu] > .toolbarbutton-text /* hack for add-ons that forcefully display the label */ {
   -moz-padding-end: 17px;
 }
 
 #nav-bar .toolbarbutton-1 > .toolbarbutton-menu-dropmarker {
   -moz-margin-start: -15px;
@@ -1198,16 +1198,21 @@ toolbarbutton[constrain-size="true"][cui
   background-clip: padding-box;
   border: 1px solid ThreeDShadow;
 }
 
 #urlbar {
   -moz-padding-end: 2px;
 }
 
+/* overlap the urlbar's border */
+#PopupAutoCompleteRichResult {
+  margin-top: -1px;
+}
+
 @media (-moz-os-version: windows-xp),
        (-moz-os-version: windows-vista),
        (-moz-os-version: windows-win7) {
   #urlbar,
   .searchbar-textbox {
     border-radius: 2px;
   }
 }
@@ -1241,16 +1246,21 @@ toolbarbutton[constrain-size="true"][cui
     .searchbar-textbox:not(:-moz-lwtheme):hover {
       border-color: hsl(0,0%,80%);
     }
 
     #urlbar:not(:-moz-lwtheme)[focused],
     .searchbar-textbox:not(:-moz-lwtheme)[focused] {
       box-shadow: 0 0 0 1px Highlight inset;
     }
+
+    /* overlap the urlbar's border and inset box-shadow */
+    #PopupAutoCompleteRichResult:not(:-moz-lwtheme) {
+      margin-top: -2px;
+    }
   }
 
   @media not all and (-moz-os-version: windows-xp) {
     #urlbar:not(:-moz-lwtheme)[focused],
     .searchbar-textbox:not(:-moz-lwtheme)[focused] {
       border-color: Highlight;
     }
   }
--- a/browser/themes/windows/caption-buttons.svg
+++ b/browser/themes/windows/caption-buttons.svg
@@ -4,52 +4,104 @@
 <svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
   <style>
     g {
       stroke: ButtonText;
       stroke-width: 0.9px;
       fill: none;
     }
 
-    g:not(#close) {
+    g:not([id|="close"]) {
       shape-rendering: crispEdges;
     }
 
     g:not(:target) {
       display: none;
     }
 
     use:target > g {
       display: initial;
     }
 
-    [id$="-highlight"] > g {
+    g.highlight {
+      stroke-width: 1.9px;
+    }
+
+    g.themes {
+      stroke: #fff;
+      stroke-width: 1.9px;
+    }
+
+    .outer-stroke {
+      stroke: #000;
+      stroke-width: 3.6;
+      opacity: .75;
+    }
+
+    .restore-background-window {
+      stroke-width: .9;
+    }
+
+    [id$="-highcontrast-hover"] > g {
       stroke: HighlightText;
     }
 
     [id$="-white"] > g {
       stroke: #fff;
     }
+
   </style>
   <g id="close">
-    <line x1="1" y1="1" x2="11" y2="11"/>
-    <line x1="11" y1="1" x2="1" y2="11"/>
+    <path d="M1,1 l 10,10 M1,11 l 10,-10"/>
   </g>
   <g id="maximize">
     <rect x="1.5" y="1.5" width="9" height="9"/>
   </g>
   <g id="minimize">
     <line x1="1" y1="5.5" x2="11" y2="5.5"/>
   </g>
   <g id="restore">
     <rect x="1.5" y="3.5" width="7" height="7"/>
     <polyline points="3.5,3.5 3.5,1.5 10.5,1.5 10.5,8.5 8.5,8.5"/>
   </g>
-  <use id="close-highlight" xlink:href="#close"/>
-  <use id="maximize-highlight" xlink:href="#maximize"/>
-  <use id="minimize-highlight" xlink:href="#minimize"/>
-  <use id="restore-highlight" xlink:href="#restore"/>
 
   <use id="close-white" xlink:href="#close"/>
   <use id="maximize-white" xlink:href="#maximize"/>
   <use id="minimize-white" xlink:href="#minimize"/>
   <use id="restore-white" xlink:href="#restore"/>
+
+  <g id="close-highcontrast" class="highlight">
+    <path d="M1,1 l 10,10 M1,11 l 10,-10"/>
+  </g>
+  <g id="maximize-highcontrast" class="highlight">
+    <rect x="2" y="2" width="8" height="8"/>
+  </g>
+  <g id="minimize-highcontrast" class="highlight">
+    <line x1="1" y1="6" x2="11" y2="6"/>
+  </g>
+  <g id="restore-highcontrast" class="highlight">
+    <rect x="2" y="4" width="6" height="6"/>
+    <polyline points="3.5,1.5 10.5,1.5 10.5,8.5" class="restore-background-window"/>
+  </g>
+
+  <use id="close-highcontrast-hover" xlink:href="#close-highcontrast"/>
+  <use id="maximize-highcontrast-hover" xlink:href="#maximize-highcontrast"/>
+  <use id="minimize-highcontrast-hover" xlink:href="#minimize-highcontrast"/>
+  <use id="restore-highcontrast-hover" xlink:href="#restore-highcontrast"/>
+
+  <g id="close-themes" class="themes">
+    <path d="M1,1 l 10,10 M1,11 l 10,-10" class="outer-stroke" />
+    <path d="M1.75,1.75 l 8.5,8.5 M1.75,10.25 l 8.5,-8.5"/>
+  </g>
+  <g id="maximize-themes" class="themes">
+    <rect x="2" y="2" width="8" height="8" class="outer-stroke"/>
+    <rect x="2" y="2" width="8" height="8"/>
+  </g>
+  <g id="minimize-themes" class="themes">
+    <line x1="0" y1="6" x2="12" y2="6" class="outer-stroke"/>
+    <line x1="1" y1="6" x2="11" y2="6"/>
+  </g>
+  <g id="restore-themes" class="themes">
+    <path d="M2,4 l 6,0 l 0,6 l -6,0z M2.5,1.5 l 8,0 l 0,8" class="outer-stroke"/>
+    <rect x="2" y="4" width="6" height="6"/>
+    <polyline points="3.5,1.5 10.5,1.5 10.5,8.5" class="restore-background-window"/>
+  </g>
 </svg>
--- a/build/clang-plugin/clang-plugin.cpp
+++ b/build/clang-plugin/clang-plugin.cpp
@@ -1,11 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 #include "clang/AST/ASTConsumer.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 #include "clang/Basic/Version.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/FrontendPluginRegistry.h"
@@ -51,25 +52,23 @@ private:
   public:
     enum Scope {
       eLocal,
       eGlobal
     };
     ScopeChecker(Scope scope_) :
       scope(scope_) {}
     virtual void run(const MatchFinder::MatchResult &Result);
-    void noteInferred(QualType T, DiagnosticsEngine &Diag);
   private:
     Scope scope;
   };
 
   class NonHeapClassChecker : public MatchFinder::MatchCallback {
   public:
     virtual void run(const MatchFinder::MatchResult &Result);
-    void noteInferred(QualType T, DiagnosticsEngine &Diag);
   };
 
   class ArithmeticArgChecker : public MatchFinder::MatchCallback {
   public:
     virtual void run(const MatchFinder::MatchResult &Result);
   };
 
   class TrivialCtorDtorChecker : public MatchFinder::MatchCallback {
@@ -92,25 +91,37 @@ private:
     virtual void run(const MatchFinder::MatchResult &Result);
   };
 
   class ExplicitOperatorBoolChecker : public MatchFinder::MatchCallback {
   public:
     virtual void run(const MatchFinder::MatchResult &Result);
   };
 
+  class NeedsNoVTableTypeChecker : public MatchFinder::MatchCallback {
+  public:
+    virtual void run(const MatchFinder::MatchResult &Result);
+  };
+
+  class NonMemMovableChecker : public MatchFinder::MatchCallback {
+  public:
+    virtual void run(const MatchFinder::MatchResult &Result);
+  };
+
   ScopeChecker stackClassChecker;
   ScopeChecker globalClassChecker;
   NonHeapClassChecker nonheapClassChecker;
   ArithmeticArgChecker arithmeticArgChecker;
   TrivialCtorDtorChecker trivialCtorDtorChecker;
   NaNExprChecker nanExprChecker;
   NoAddRefReleaseOnReturnChecker noAddRefReleaseOnReturnChecker;
   RefCountedInsideLambdaChecker refCountedInsideLambdaChecker;
   ExplicitOperatorBoolChecker explicitOperatorBoolChecker;
+  NeedsNoVTableTypeChecker needsNoVTableTypeChecker;
+  NonMemMovableChecker nonMemMovableChecker;
   MatchFinder astMatcher;
 };
 
 namespace {
 
 std::string getDeclarationNamespace(const Decl *decl) {
   const DeclContext *DC = decl->getDeclContext()->getEnclosingNamespaceContext();
   const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC);
@@ -212,54 +223,99 @@ bool isInterestingDeclForImplicitCtor(co
 
 bool isInterestingDeclForImplicitConversion(const Decl *decl) {
   return !isInIgnoredNamespaceForImplicitConversion(decl) &&
          !isIgnoredPathForImplicitConversion(decl);
 }
 
 }
 
+class CustomTypeAnnotation {
+  enum ReasonKind {
+    RK_None,
+    RK_Direct,
+    RK_ArrayElement,
+    RK_BaseClass,
+    RK_Field,
+  };
+  struct AnnotationReason {
+    QualType Type;
+    ReasonKind Kind;
+    const FieldDecl *Field;
+
+    bool valid() const { return Kind != RK_None; }
+  };
+  typedef DenseMap<void *, AnnotationReason> ReasonCache;
+
+  const char *Spelling;
+  const char *Pretty;
+  ReasonCache Cache;
+
+public:
+  CustomTypeAnnotation(const char *Spelling, const char *Pretty)
+    : Spelling(Spelling), Pretty(Pretty) {};
+
+  // Checks if this custom annotation "effectively affects" the given type.
+  bool hasEffectiveAnnotation(QualType T) {
+    return directAnnotationReason(T).valid();
+  }
+  void dumpAnnotationReason(DiagnosticsEngine &Diag, QualType T, SourceLocation Loc);
+
+private:
+  bool hasLiteralAnnotation(QualType T) const;
+  AnnotationReason directAnnotationReason(QualType T);
+};
+
+static CustomTypeAnnotation StackClass =
+  CustomTypeAnnotation("moz_stack_class", "stack");
+static CustomTypeAnnotation GlobalClass =
+  CustomTypeAnnotation("moz_global_class", "global");
+static CustomTypeAnnotation NonHeapClass =
+  CustomTypeAnnotation("moz_nonheap_class", "non-heap");
+static CustomTypeAnnotation MustUse =
+  CustomTypeAnnotation("moz_must_use", "must-use");
+
 class MozChecker : public ASTConsumer, public RecursiveASTVisitor<MozChecker> {
   DiagnosticsEngine &Diag;
   const CompilerInstance &CI;
   DiagnosticsMatcher matcher;
 public:
   MozChecker(const CompilerInstance &CI) : Diag(CI.getDiagnostics()), CI(CI) {}
 
   ASTConsumerPtr getOtherConsumer() {
     return matcher.makeASTConsumer();
   }
 
   virtual void HandleTranslationUnit(ASTContext &ctx) {
     TraverseDecl(ctx.getTranslationUnitDecl());
   }
 
-  static bool hasCustomAnnotation(const Decl *d, const char *spelling) {
-    AnnotateAttr *attr = d->getAttr<AnnotateAttr>();
-    if (!attr)
-      return false;
+  static bool hasCustomAnnotation(const Decl *D, const char *Spelling) {
+    iterator_range<specific_attr_iterator<AnnotateAttr> > Attrs =
+      D->specific_attrs<AnnotateAttr>();
 
-    return attr->getAnnotation() == spelling;
+    for (AnnotateAttr *Attr : Attrs) {
+      if (Attr->getAnnotation() == Spelling) {
+        return true;
+      }
+    }
+
+    return false;
   }
 
   void HandleUnusedExprResult(const Stmt *stmt) {
     const Expr* E = dyn_cast_or_null<Expr>(stmt);
     if (E) {
-      // XXX It would be nice if we could use getAsTagDecl,
-      // but our version of clang is too old.
-      // (getAsTagDecl would also cover enums etc.)
       QualType T = E->getType();
-      CXXRecordDecl *decl = T->getAsCXXRecordDecl();
-      if (decl) {
-        decl = decl->getDefinition();
-        if (decl && hasCustomAnnotation(decl, "moz_must_use")) {
-          unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
-            DiagnosticIDs::Error, "Unused MOZ_MUST_USE value of type %0");
-          Diag.Report(E->getLocStart(), errorID) << T;
-        }
+      if (MustUse.hasEffectiveAnnotation(T)) {
+        unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
+          DiagnosticIDs::Error, "Unused value of must-use type %0");
+
+        Diag.Report(E->getLocStart(), errorID) << T;
+        MustUse.dumpAnnotationReason(Diag, T, E->getLocStart());
       }
     }
   }
 
   bool VisitCXXRecordDecl(CXXRecordDecl *d) {
     // We need definitions, not declarations
     if (!d->isThisDeclarationADefinition()) return true;
 
@@ -373,112 +429,16 @@ public:
     return true;
   }
   bool VisitBinComma(BinaryOperator* Op) {
     HandleUnusedExprResult(Op->getLHS());
     return true;
   }
 };
 
-/**
- * Where classes may be allocated. Regular classes can be allocated anywhere,
- * non-heap classes on the stack or as static variables, and stack classes only
- * on the stack. Note that stack classes subsumes non-heap classes.
- */
-enum ClassAllocationNature {
-  RegularClass = 0,
-  NonHeapClass = 1,
-  StackClass = 2,
-  GlobalClass = 3
-};
-
-/// A cached data of whether classes are stack classes, non-heap classes, or
-/// neither.
-DenseMap<const CXXRecordDecl *,
-  std::pair<const Decl *, ClassAllocationNature> > inferredAllocCauses;
-
-ClassAllocationNature getClassAttrs(QualType T);
-
-ClassAllocationNature getClassAttrs(CXXRecordDecl *D) {
-  // Normalize so that D points to the definition if it exists. If it doesn't,
-  // then we can't allocate it anyways.
-  if (!D->hasDefinition())
-    return RegularClass;
-  D = D->getDefinition();
-  // Base class: anyone with this annotation is obviously a stack class
-  if (MozChecker::hasCustomAnnotation(D, "moz_stack_class"))
-    return StackClass;
-  // Base class: anyone with this annotation is obviously a global class
-  if (MozChecker::hasCustomAnnotation(D, "moz_global_class"))
-    return GlobalClass;
-
-  // See if we cached the result.
-  DenseMap<const CXXRecordDecl *,
-    std::pair<const Decl *, ClassAllocationNature> >::iterator it =
-    inferredAllocCauses.find(D);
-  if (it != inferredAllocCauses.end()) {
-    return it->second.second;
-  }
-
-  // Continue looking, we might be a stack class yet. Even if we're a nonheap
-  // class, it might be possible that we've inferred to be a stack class.
-  ClassAllocationNature type = RegularClass;
-  if (MozChecker::hasCustomAnnotation(D, "moz_nonheap_class")) {
-    type = NonHeapClass;
-  }
-  inferredAllocCauses.insert(std::make_pair(D,
-    std::make_pair((const Decl *)0, type)));
-
-  // Look through all base cases to figure out if the parent is a stack class or
-  // a non-heap class. Since we might later infer to also be a stack class, keep
-  // going.
-  for (CXXRecordDecl::base_class_iterator base = D->bases_begin(),
-       e = D->bases_end(); base != e; ++base) {
-    ClassAllocationNature super = getClassAttrs(base->getType());
-    if (super == StackClass) {
-      inferredAllocCauses[D] = std::make_pair(
-        base->getType()->getAsCXXRecordDecl(), StackClass);
-      return StackClass;
-    } else if (super == GlobalClass) {
-      inferredAllocCauses[D] = std::make_pair(
-        base->getType()->getAsCXXRecordDecl(), GlobalClass);
-      return GlobalClass;
-    } else if (super == NonHeapClass) {
-      inferredAllocCauses[D] = std::make_pair(
-        base->getType()->getAsCXXRecordDecl(), NonHeapClass);
-      type = NonHeapClass;
-    }
-  }
-
-  // Maybe it has a member which is a stack class.
-  for (RecordDecl::field_iterator field = D->field_begin(), e = D->field_end();
-       field != e; ++field) {
-    ClassAllocationNature fieldType = getClassAttrs(field->getType());
-    if (fieldType == StackClass) {
-      inferredAllocCauses[D] = std::make_pair(*field, StackClass);
-      return StackClass;
-    } else if (fieldType == GlobalClass) {
-      inferredAllocCauses[D] = std::make_pair(*field, GlobalClass);
-      return GlobalClass;
-    } else if (fieldType == NonHeapClass) {
-      inferredAllocCauses[D] = std::make_pair(*field, NonHeapClass);
-      type = NonHeapClass;
-    }
-  }
-
-  return type;
-}
-
-ClassAllocationNature getClassAttrs(QualType T) {
-  while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
-    T = arrTy->getElementType();
-  CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
-  return clazz ? getClassAttrs(clazz) : RegularClass;
-}
-
 /// A cached data of whether classes are refcounted or not.
 typedef DenseMap<const CXXRecordDecl *,
   std::pair<const Decl *, bool> > RefCountedMap;
 RefCountedMap refCountedClasses;
 
 bool classHasAddRefRelease(const CXXRecordDecl *D) {
   const RefCountedMap::iterator& it = refCountedClasses.find(D);
   if (it != refCountedClasses.end()) {
@@ -522,50 +482,138 @@ bool isClassRefCounted(const CXXRecordDe
 
   return false;
 }
 
 bool isClassRefCounted(QualType T) {
   while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
     T = arrTy->getElementType();
   CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
-  return clazz ? isClassRefCounted(clazz) : RegularClass;
+  return clazz ? isClassRefCounted(clazz) : false;
+}
+
+/// A cached data of whether classes are memmovable, and if not, what declaration
+/// makes them non-movable
+typedef DenseMap<const CXXRecordDecl *, const CXXRecordDecl *> InferredMovability;
+InferredMovability inferredMovability;
+
+bool isClassNonMemMovable(QualType T);
+const CXXRecordDecl* isClassNonMemMovableWorker(QualType T);
+
+const CXXRecordDecl* isClassNonMemMovableWorker(const CXXRecordDecl *D) {
+  // If we have a definition, then we want to standardize our reference to point
+  // to the definition node. If we don't have a definition, that means that either
+  // we only have a forward declaration of the type in our file, or we are being
+  // passed a template argument which is not used, and thus never instantiated by
+  // clang.
+  // As the argument isn't used, we can't memmove it (as we don't know it's size),
+  // which means not reporting an error is OK.
+  if (!D->hasDefinition()) {
+    return 0;
+  }
+  D = D->getDefinition();
+
+  // Are we explicitly marked as non-memmovable class?
+  if (MozChecker::hasCustomAnnotation(D, "moz_non_memmovable")) {
+    return D;
+  }
+
+  // Look through all base cases to figure out if the parent is a non-memmovable class.
+  for (CXXRecordDecl::base_class_const_iterator base = D->bases_begin();
+       base != D->bases_end(); ++base) {
+    const CXXRecordDecl *result = isClassNonMemMovableWorker(base->getType());
+    if (result) {
+      return result;
+    }
+  }
+
+  // Look through all members to figure out if a member is a non-memmovable class.
+  for (RecordDecl::field_iterator field = D->field_begin(), e = D->field_end();
+       field != e; ++field) {
+    const CXXRecordDecl *result = isClassNonMemMovableWorker(field->getType());
+    if (result) {
+      return result;
+    }
+  }
+
+  return 0;
+}
+
+const CXXRecordDecl* isClassNonMemMovableWorker(QualType T) {
+  while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
+    T = arrTy->getElementType();
+  const CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
+  return clazz ? isClassNonMemMovableWorker(clazz) : 0;
+}
+
+bool isClassNonMemMovable(const CXXRecordDecl *D) {
+  InferredMovability::iterator it =
+    inferredMovability.find(D);
+  if (it != inferredMovability.end())
+    return !!it->second;
+  const CXXRecordDecl *result = isClassNonMemMovableWorker(D);
+  inferredMovability.insert(std::make_pair(D, result));
+  return !!result;
+}
+
+bool isClassNonMemMovable(QualType T) {
+  while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
+    T = arrTy->getElementType();
+  const CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
+  return clazz ? isClassNonMemMovable(clazz) : false;
+}
+
+const CXXRecordDecl* findWhyClassIsNonMemMovable(QualType T) {
+  while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
+    T = arrTy->getElementType();
+  CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
+  InferredMovability::iterator it =
+    inferredMovability.find(clazz);
+  assert(it != inferredMovability.end());
+  return it->second;
 }
 
 template<class T>
 bool IsInSystemHeader(const ASTContext &AC, const T &D) {
   auto &SourceManager = AC.getSourceManager();
   auto ExpansionLoc = SourceManager.getExpansionLoc(D.getLocStart());
   if (ExpansionLoc.isInvalid()) {
     return false;
   }
   return SourceManager.isInSystemHeader(ExpansionLoc);
 }
 
+bool typeHasVTable(QualType T) {
+  while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
+    T = arrTy->getElementType();
+  CXXRecordDecl* offender = T->getAsCXXRecordDecl();
+  return offender && offender->hasDefinition() && offender->isDynamicClass();
+}
+
 }
 
 namespace clang {
 namespace ast_matchers {
 
 /// This matcher will match any class with the stack class assertion or an
 /// array of such classes.
 AST_MATCHER(QualType, stackClassAggregate) {
-  return getClassAttrs(Node) == StackClass;
+  return StackClass.hasEffectiveAnnotation(Node);
 }
 
 /// This matcher will match any class with the global class assertion or an
 /// array of such classes.
 AST_MATCHER(QualType, globalClassAggregate) {
-  return getClassAttrs(Node) == GlobalClass;
+  return GlobalClass.hasEffectiveAnnotation(Node);
 }
 
 /// This matcher will match any class with the stack class assertion or an
 /// array of such classes.
 AST_MATCHER(QualType, nonheapClassAggregate) {
-  return getClassAttrs(Node) == NonHeapClass;
+  return NonHeapClass.hasEffectiveAnnotation(Node);
 }
 
 /// This matcher will match any function declaration that is declared as a heap
 /// allocator.
 AST_MATCHER(FunctionDecl, heapAllocator) {
   return MozChecker::hasCustomAnnotation(&Node, "moz_heap_allocator");
 }
 
@@ -689,21 +737,136 @@ AST_POLYMORPHIC_MATCHER_P(equalsBoundNod
   };
   Visitor visitor(Node, ID, haveMatchingResult);
   bindings.visitMatches(&visitor);
   return haveMatchingResult;
 }
 
 #endif
 
+AST_MATCHER(QualType, hasVTable) {
+  return typeHasVTable(Node);
+}
+
+AST_MATCHER(CXXRecordDecl, hasNeedsNoVTableTypeAttr) {
+  return MozChecker::hasCustomAnnotation(&Node, "moz_needs_no_vtable_type");
+}
+
+/// This matcher will select classes which are non-memmovable
+AST_MATCHER(QualType, isNonMemMovable) {
+  return isClassNonMemMovable(Node);
+}
+
+/// This matcher will select classes which require a memmovable template arg
+AST_MATCHER(CXXRecordDecl, needsMemMovable) {
+  return MozChecker::hasCustomAnnotation(&Node, "moz_needs_memmovable_type");
+}
+
 }
 }
 
 namespace {
 
+void CustomTypeAnnotation::dumpAnnotationReason(DiagnosticsEngine &Diag, QualType T, SourceLocation Loc) {
+  unsigned InheritsID = Diag.getDiagnosticIDs()->getCustomDiagID(
+    DiagnosticIDs::Note, "%1 is a %0 type because it inherits from a %0 type %2");
+  unsigned MemberID = Diag.getDiagnosticIDs()->getCustomDiagID(
+    DiagnosticIDs::Note, "%1 is a %0 type because member %2 is a %0 type %3");
+  unsigned ArrayID = Diag.getDiagnosticIDs()->getCustomDiagID(
+    DiagnosticIDs::Note, "%1 is a %0 type because it is an array of %0 type %2");
+  unsigned TemplID = Diag.getDiagnosticIDs()->getCustomDiagID(
+    DiagnosticIDs::Note, "%1 is a %0 type because it has a template argument %0 type %2");
+
+  AnnotationReason Reason = directAnnotationReason(T);
+  for (;;) {
+    switch (Reason.Kind) {
+    case RK_ArrayElement:
+      Diag.Report(Loc, ArrayID)
+        << Pretty << T << Reason.Type;
+      break;
+    case RK_BaseClass:
+      {
+        const CXXRecordDecl *Decl = T->getAsCXXRecordDecl();
+        assert(Decl && "This type should be a C++ class");
+
+        Diag.Report(Decl->getLocation(), InheritsID)
+          << Pretty << T << Reason.Type;
+        break;
+      }
+    case RK_Field:
+      Diag.Report(Reason.Field->getLocation(), MemberID)
+        << Pretty << T << Reason.Field << Reason.Type;
+      break;
+    default:
+      return;
+    }
+
+    T = Reason.Type;
+    Reason = directAnnotationReason(T);
+  }
+}
+
+bool CustomTypeAnnotation::hasLiteralAnnotation(QualType T) const {
+  if (const TagDecl *D = T->getAsTagDecl()) {
+    return MozChecker::hasCustomAnnotation(D, Spelling);
+  }
+  return false;
+}
+
+CustomTypeAnnotation::AnnotationReason CustomTypeAnnotation::directAnnotationReason(QualType T) {
+  if (hasLiteralAnnotation(T)) {
+    AnnotationReason Reason = { T, RK_Direct, nullptr };
+    return Reason;
+  }
+
+  // Check if we have a cached answer
+  void *Key = T.getAsOpaquePtr();
+  ReasonCache::iterator Cached = Cache.find(T.getAsOpaquePtr());
+  if (Cached != Cache.end()) {
+    return Cached->second;
+  }
+
+  // Check if we have a type which we can recurse into
+  if (const ArrayType *Array = T->getAsArrayTypeUnsafe()) {
+    if (hasEffectiveAnnotation(Array->getElementType())) {
+      AnnotationReason Reason = { Array->getElementType(), RK_ArrayElement, nullptr };
+      Cache[Key] = Reason;
+      return Reason;
+    }
+  }
+
+  // Recurse into base classes
+  if (const CXXRecordDecl *Decl = T->getAsCXXRecordDecl()) {
+    if (Decl->hasDefinition()) {
+      Decl = Decl->getDefinition();
+
+      for (const CXXBaseSpecifier &Base : Decl->bases()) {
+        if (hasEffectiveAnnotation(Base.getType())) {
+          AnnotationReason Reason = { Base.getType(), RK_BaseClass, nullptr };
+          Cache[Key] = Reason;
+          return Reason;
+        }
+      }
+
+      // Recurse into members
+      for (const FieldDecl *Field : Decl->fields()) {
+        if (hasEffectiveAnnotation(Field->getType())) {
+          AnnotationReason Reason = { Field->getType(), RK_Field, Field };
+          Cache[Key] = Reason;
+          return Reason;
+        }
+      }
+    }
+  }
+
+  AnnotationReason Reason = { QualType(), RK_None, nullptr };
+  Cache[Key] = Reason;
+  return Reason;
+}
+
 bool isPlacementNew(const CXXNewExpr *expr) {
   // Regular new expressions aren't placement new
   if (expr->getNumPlacementArgs() == 0)
     return false;
   if (MozChecker::hasCustomAnnotation(expr->getOperatorNew(),
       "moz_heap_allocator"))
     return false;
   return true;
@@ -811,137 +974,100 @@ DiagnosticsMatcher::DiagnosticsMatcher()
     &refCountedInsideLambdaChecker);
 
   // Older clang versions such as the ones used on the infra recognize these
   // conversions as 'operator _Bool', but newer clang versions recognize these
   // as 'operator bool'.
   astMatcher.addMatcher(methodDecl(anyOf(hasName("operator bool"),
                                          hasName("operator _Bool"))).bind("node"),
     &explicitOperatorBoolChecker);
+
+  astMatcher.addMatcher(classTemplateSpecializationDecl(
+             allOf(hasAnyTemplateArgument(refersToType(hasVTable())),
+                   hasNeedsNoVTableTypeAttr())).bind("node"),
+     &needsNoVTableTypeChecker);
+
+  // Handle non-mem-movable template specializations
+  astMatcher.addMatcher(classTemplateSpecializationDecl(
+      allOf(needsMemMovable(),
+            hasAnyTemplateArgument(refersToType(isNonMemMovable())))
+      ).bind("specialization"),
+      &nonMemMovableChecker);
 }
 
 void DiagnosticsMatcher::ScopeChecker::run(
     const MatchFinder::MatchResult &Result) {
   DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
   unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID(
     DiagnosticIDs::Error, "variable of type %0 only valid on the stack");
   unsigned globalID = Diag.getDiagnosticIDs()->getCustomDiagID(
     DiagnosticIDs::Error, "variable of type %0 only valid as global");
-  unsigned errorID = (scope == eGlobal) ? globalID : stackID;
+
+  SourceLocation Loc;
+  QualType T;
   if (const VarDecl *d = Result.Nodes.getNodeAs<VarDecl>("node")) {
     if (scope == eLocal) {
       // Ignore the match if it's a local variable.
       if (d->hasLocalStorage())
         return;
     } else if (scope == eGlobal) {
       // Ignore the match if it's a global variable or a static member of a
       // class.  The latter is technically not in the global scope, but for the
       // use case of classes that intend to avoid introducing static
       // initializers that is fine.
       if (d->hasGlobalStorage() && !d->isStaticLocal())
         return;
     }
 
-    Diag.Report(d->getLocation(), errorID) << d->getType();
-    noteInferred(d->getType(), Diag);
+    Loc = d->getLocation();
+    T = d->getType();
   } else if (const CXXNewExpr *expr =
       Result.Nodes.getNodeAs<CXXNewExpr>("node")) {
     // If it's placement new, then this match doesn't count.
     if (scope == eLocal && isPlacementNew(expr))
       return;
-    Diag.Report(expr->getStartLoc(), errorID) << expr->getAllocatedType();
-    noteInferred(expr->getAllocatedType(), Diag);
+
+    Loc = expr->getStartLoc();
+    T = expr->getAllocatedType();
   } else if (const CallExpr *expr =
       Result.Nodes.getNodeAs<CallExpr>("node")) {
-    QualType badType = GetCallReturnType(expr)->getPointeeType();
-    Diag.Report(expr->getLocStart(), errorID) << badType;
-    noteInferred(badType, Diag);
-  }
-}
-
-void DiagnosticsMatcher::ScopeChecker::noteInferred(QualType T,
-    DiagnosticsEngine &Diag) {
-  unsigned inheritsID = Diag.getDiagnosticIDs()->getCustomDiagID(
-    DiagnosticIDs::Note,
-    "%0 is a %2 class because it inherits from a %2 class %1");
-  unsigned memberID = Diag.getDiagnosticIDs()->getCustomDiagID(
-    DiagnosticIDs::Note,
-    "%0 is a %3 class because member %1 is a %3 class %2");
-  const char* attribute = (scope == eGlobal) ?
-    "moz_global_class" : "moz_stack_class";
-  const char* type = (scope == eGlobal) ?
-    "global" : "stack";
-
-  // Find the CXXRecordDecl that is the local/global class of interest
-  while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
-    T = arrTy->getElementType();
-  CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
-
-  // Direct result, we're done.
-  if (MozChecker::hasCustomAnnotation(clazz, attribute))
-    return;
-
-  const Decl *cause = inferredAllocCauses[clazz].first;
-  if (const CXXRecordDecl *CRD = dyn_cast<CXXRecordDecl>(cause)) {
-    Diag.Report(clazz->getLocation(), inheritsID) <<
-      T << CRD->getDeclName() << type;
-  } else if (const FieldDecl *FD = dyn_cast<FieldDecl>(cause)) {
-    Diag.Report(FD->getLocation(), memberID) <<
-      T << FD << FD->getType() << type;
+    Loc = expr->getLocStart();
+    T = GetCallReturnType(expr)->getPointeeType();
   }
 
-  // Recursively follow this back.
-  noteInferred(cast<ValueDecl>(cause)->getType(), Diag);
+  if (scope == eLocal) {
+    Diag.Report(Loc, stackID) << T;
+    StackClass.dumpAnnotationReason(Diag, T, Loc);
+  } else if (scope == eGlobal) {
+    Diag.Report(Loc, globalID) << T;
+    GlobalClass.dumpAnnotationReason(Diag, T, Loc);
+  }
 }
 
 void DiagnosticsMatcher::NonHeapClassChecker::run(
     const MatchFinder::MatchResult &Result) {
   DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
   unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID(
     DiagnosticIDs::Error, "variable of type %0 is not valid on the heap");
+
+  SourceLocation Loc;
+  QualType T;
   if (const CXXNewExpr *expr = Result.Nodes.getNodeAs<CXXNewExpr>("node")) {
     // If it's placement new, then this match doesn't count.
     if (isPlacementNew(expr))
       return;
-    Diag.Report(expr->getStartLoc(), stackID) << expr->getAllocatedType();
-    noteInferred(expr->getAllocatedType(), Diag);
+    Loc = expr->getLocStart();
+    T = expr->getAllocatedType();
   } else if (const CallExpr *expr = Result.Nodes.getNodeAs<CallExpr>("node")) {
-    QualType badType = GetCallReturnType(expr)->getPointeeType();
-    Diag.Report(expr->getLocStart(), stackID) << badType;
-    noteInferred(badType, Diag);
+    Loc = expr->getLocStart();
+    T = GetCallReturnType(expr)->getPointeeType();
   }
-}
-
-void DiagnosticsMatcher::NonHeapClassChecker::noteInferred(QualType T,
-    DiagnosticsEngine &Diag) {
-  unsigned inheritsID = Diag.getDiagnosticIDs()->getCustomDiagID(
-    DiagnosticIDs::Note,
-    "%0 is a non-heap class because it inherits from a non-heap class %1");
-  unsigned memberID = Diag.getDiagnosticIDs()->getCustomDiagID(
-    DiagnosticIDs::Note,
-    "%0 is a non-heap class because member %1 is a non-heap class %2");
 
-  // Find the CXXRecordDecl that is the stack class of interest
-  while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
-    T = arrTy->getElementType();
-  CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
-
-  // Direct result, we're done.
-  if (MozChecker::hasCustomAnnotation(clazz, "moz_nonheap_class"))
-    return;
-
-  const Decl *cause = inferredAllocCauses[clazz].first;
-  if (const CXXRecordDecl *CRD = dyn_cast<CXXRecordDecl>(cause)) {
-    Diag.Report(clazz->getLocation(), inheritsID) << T << CRD->getDeclName();
-  } else if (const FieldDecl *FD = dyn_cast<FieldDecl>(cause)) {
-    Diag.Report(FD->getLocation(), memberID) << T << FD << FD->getType();
-  }
-  
-  // Recursively follow this back.
-  noteInferred(cast<ValueDecl>(cause)->getType(), Diag);
+  Diag.Report(Loc, stackID) << T;
+  NonHeapClass.dumpAnnotationReason(Diag, T, Loc);
 }
 
 void DiagnosticsMatcher::ArithmeticArgChecker::run(
     const MatchFinder::MatchResult &Result) {
   DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
   unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
       DiagnosticIDs::Error, "cannot pass an arithmetic expression of built-in types to %0");
   const Expr *expr = Result.Nodes.getNodeAs<Expr>("node");
@@ -1042,16 +1168,82 @@ void DiagnosticsMatcher::ExplicitOperato
       !MozChecker::hasCustomAnnotation(method, "moz_implicit") &&
       !IsInSystemHeader(method->getASTContext(), *method) &&
       isInterestingDeclForImplicitConversion(method)) {
     Diag.Report(method->getLocStart(), errorID) << clazz;
     Diag.Report(method->getLocStart(), noteID) << "'operator bool'";
   }
 }
 
+void DiagnosticsMatcher::NeedsNoVTableTypeChecker::run(
+    const MatchFinder::MatchResult &Result) {
+  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
+  unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Error, "%0 cannot be instantiated because %1 has a VTable");
+  unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Note, "bad instantiation of %0 requested here");
+
+  const ClassTemplateSpecializationDecl *specialization =
+    Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("node");
+
+  // Get the offending template argument
+  QualType offender;
+  const TemplateArgumentList &args =
+    specialization->getTemplateInstantiationArgs();
+  for (unsigned i = 0; i < args.size(); ++i) {
+    offender = args[i].getAsType();
+    if (typeHasVTable(offender)) {
+      break;
+    }
+  }
+
+  Diag.Report(specialization->getLocStart(), errorID) << specialization << offender;
+  Diag.Report(specialization->getPointOfInstantiation(), noteID) << specialization;
+}
+
+void DiagnosticsMatcher::NonMemMovableChecker::run(
+    const MatchFinder::MatchResult &Result) {
+  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
+  unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Error, "Cannot instantiate %0 with non-memmovable template argument %1");
+  unsigned note1ID = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Note, "instantiation of %0 requested here");
+  unsigned note2ID = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Note, "%0 is non-memmovable because of the MOZ_NON_MEMMOVABLE annotation on %1");
+  unsigned note3ID = Diag.getDiagnosticIDs()->getCustomDiagID(DiagnosticIDs::Note, "%0");
+
+  // Get the specialization
+  const ClassTemplateSpecializationDecl *specialization =
+    Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("specialization");
+  SourceLocation requestLoc = specialization->getPointOfInstantiation();
+  const CXXRecordDecl *templ =
+    specialization->getSpecializedTemplate()->getTemplatedDecl();
+
+  // Report an error for every template argument which is non-memmovable
+  const TemplateArgumentList &args =
+    specialization->getTemplateInstantiationArgs();
+  for (unsigned i = 0; i < args.size(); ++i) {
+    QualType argType = args[i].getAsType();
+    if (isClassNonMemMovable(args[i].getAsType())) {
+      const CXXRecordDecl *reason = findWhyClassIsNonMemMovable(argType);
+      Diag.Report(specialization->getLocation(), errorID)
+        << specialization << argType;
+      // XXX It would be really nice if we could get the instantiation stack information
+      // from Sema such that we could print a full template instantiation stack, however,
+      // it seems as though that information is thrown out by the time we get here so we
+      // can only report one level of template specialization (which in many cases won't
+      // be useful)
+      Diag.Report(requestLoc, note1ID)
+        << specialization;
+      Diag.Report(reason->getLocation(), note2ID)
+        << argType << reason;
+    }
+  }
+}
+
 class MozCheckAction : public PluginASTAction {
 public:
   ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI, StringRef fileName) override {
 #if CLANG_VERSION_FULL >= 306
     std::unique_ptr<MozChecker> checker(llvm::make_unique<MozChecker>(CI));
     ASTConsumerPtr other(checker->getOtherConsumer());
 
     std::vector<ASTConsumerPtr> consumers;
--- a/build/clang-plugin/tests/TestGlobalClass.cpp
+++ b/build/clang-plugin/tests/TestGlobalClass.cpp
@@ -11,42 +11,42 @@ template <class T>
 struct MOZ_GLOBAL_CLASS TemplateClass {
   T i;
 };
 
 void gobble(void *) { }
 
 void misuseGlobalClass(int len) {
   Global notValid; // expected-error {{variable of type 'Global' only valid as global}}
-  Global alsoNotValid[2]; // expected-error {{variable of type 'Global [2]' only valid as global}}
+  Global alsoNotValid[2]; // expected-error {{variable of type 'Global [2]' only valid as global}} expected-note {{'Global [2]' is a global type because it is an array of global type 'Global'}}
   static Global valid; // expected-error {{variable of type 'Global' only valid as global}}
-  static Global alsoValid[2]; // expected-error {{variable of type 'Global [2]' only valid as global}}
+  static Global alsoValid[2]; // expected-error {{variable of type 'Global [2]' only valid as global}} expected-note {{'Global [2]' is a global type because it is an array of global type 'Global'}}
 
   gobble(&valid);
   gobble(&notValid);
   gobble(&alsoValid[0]);
 
   gobble(new Global); // expected-error {{variable of type 'Global' only valid as global}}
   gobble(new Global[10]); // expected-error {{variable of type 'Global' only valid as global}}
   gobble(new TemplateClass<int>); // expected-error {{variable of type 'TemplateClass<int>' only valid as global}}
   gobble(len <= 5 ? &valid : new Global); // expected-error {{variable of type 'Global' only valid as global}}
 
   char buffer[sizeof(Global)];
   gobble(new (buffer) Global); // expected-error {{variable of type 'Global' only valid as global}}
 }
 
 Global valid;
 struct RandomClass {
-  Global nonstaticMember; // expected-note {{'RandomClass' is a global class because member 'nonstaticMember' is a global class 'Global'}}
+  Global nonstaticMember; // expected-note {{'RandomClass' is a global type because member 'nonstaticMember' is a global type 'Global'}}
   static Global staticMember;
 };
 struct MOZ_GLOBAL_CLASS RandomGlobalClass {
   Global nonstaticMember;
   static Global staticMember;
 };
 
-struct BadInherit : Global {}; // expected-note {{'BadInherit' is a global class because it inherits from a global class 'Global'}}
+struct BadInherit : Global {}; // expected-note {{'BadInherit' is a global type because it inherits from a global type 'Global'}}
 struct MOZ_GLOBAL_CLASS GoodInherit : Global {};
 
 void misuseGlobalClassEvenMore(int len) {
   BadInherit moreInvalid; // expected-error {{variable of type 'BadInherit' only valid as global}}
   RandomClass evenMoreInvalid; // expected-error {{variable of type 'RandomClass' only valid as global}}
 }
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/tests/TestMultipleAnnotations.cpp
@@ -0,0 +1,17 @@
+#define MOZ_MUST_USE __attribute__((annotate("moz_must_use")))
+#define MOZ_STACK_CLASS __attribute__((annotate("moz_stack_class")))
+
+class MOZ_MUST_USE MOZ_STACK_CLASS TestClass {};
+
+TestClass foo; // expected-error {{variable of type 'TestClass' only valid on the stack}}
+
+TestClass f()
+{
+  TestClass bar;
+  return bar;
+}
+
+void g()
+{
+  f(); // expected-error {{Unused value of must-use type 'TestClass'}}
+}
--- a/build/clang-plugin/tests/TestMustUse.cpp
+++ b/build/clang-plugin/tests/TestMustUse.cpp
@@ -15,131 +15,131 @@ void use(MustUse*);
 void use(MustUse&);
 void use(MustUse&&);
 void use(MayUse*);
 void use(MayUse&);
 void use(MayUse&&);
 void use(bool);
 
 void foo() {
-  producesMustUse(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  producesMustUse(); // expected-error {{Unused value of must-use type 'MustUse'}}
   producesMustUsePointer();
-  producesMustUseRef(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  producesMustUseRef(); // expected-error {{Unused value of must-use type 'MustUse'}}
   producesMayUse();
   producesMayUsePointer();
   producesMayUseRef();
   {
-    producesMustUse(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+    producesMustUse(); // expected-error {{Unused value of must-use type 'MustUse'}}
     producesMustUsePointer();
-    producesMustUseRef(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+    producesMustUseRef(); // expected-error {{Unused value of must-use type 'MustUse'}}
     producesMayUse();
     producesMayUsePointer();
     producesMayUseRef();
   }
   if (true) {
-    producesMustUse(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+    producesMustUse(); // expected-error {{Unused value of must-use type 'MustUse'}}
     producesMustUsePointer();
-    producesMustUseRef(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+    producesMustUseRef(); // expected-error {{Unused value of must-use type 'MustUse'}}
     producesMayUse();
     producesMayUsePointer();
     producesMayUseRef();
   } else {
-    producesMustUse(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+    producesMustUse(); // expected-error {{Unused value of must-use type 'MustUse'}}
     producesMustUsePointer();
-    producesMustUseRef(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+    producesMustUseRef(); // expected-error {{Unused value of must-use type 'MustUse'}}
     producesMayUse();
     producesMayUsePointer();
     producesMayUseRef();
   }
 
-  if(true) producesMustUse(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
-  else producesMustUse(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  if(true) producesMustUse(); // expected-error {{Unused value of must-use type 'MustUse'}}
+  else producesMustUse(); // expected-error {{Unused value of must-use type 'MustUse'}}
   if(true) producesMustUsePointer();
   else producesMustUsePointer();
-  if(true) producesMustUseRef(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
-  else producesMustUseRef(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  if(true) producesMustUseRef(); // expected-error {{Unused value of must-use type 'MustUse'}}
+  else producesMustUseRef(); // expected-error {{Unused value of must-use type 'MustUse'}}
   if(true) producesMayUse();
   else producesMayUse();
   if(true) producesMayUsePointer();
   else producesMayUsePointer();
   if(true) producesMayUseRef();
   else producesMayUseRef();
 
-  while (true) producesMustUse(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  while (true) producesMustUse(); // expected-error {{Unused value of must-use type 'MustUse'}}
   while (true) producesMustUsePointer();
-  while (true) producesMustUseRef(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  while (true) producesMustUseRef(); // expected-error {{Unused value of must-use type 'MustUse'}}
   while (true) producesMayUse();
   while (true) producesMayUsePointer();
   while (true) producesMayUseRef();
 
-  do producesMustUse(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  do producesMustUse(); // expected-error {{Unused value of must-use type 'MustUse'}}
   while (true);
   do producesMustUsePointer();
   while (true);
-  do producesMustUseRef(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  do producesMustUseRef(); // expected-error {{Unused value of must-use type 'MustUse'}}
   while (true);
   do producesMayUse();
   while (true);
   do producesMayUsePointer();
   while (true);
   do producesMayUseRef();
   while (true);
 
-  for (;;) producesMustUse(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  for (;;) producesMustUse(); // expected-error {{Unused value of must-use type 'MustUse'}}
   for (;;) producesMustUsePointer();
-  for (;;) producesMustUseRef(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  for (;;) producesMustUseRef(); // expected-error {{Unused value of must-use type 'MustUse'}}
   for (;;) producesMayUse();
   for (;;) producesMayUsePointer();
   for (;;) producesMayUseRef();
 
-  for (producesMustUse();;); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  for (producesMustUse();;); // expected-error {{Unused value of must-use type 'MustUse'}}
   for (producesMustUsePointer();;);
-  for (producesMustUseRef();;); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  for (producesMustUseRef();;); // expected-error {{Unused value of must-use type 'MustUse'}}
   for (producesMayUse();;);
   for (producesMayUsePointer();;);
   for (producesMayUseRef();;);
 
-  for (;;producesMustUse()); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  for (;;producesMustUse()); // expected-error {{Unused value of must-use type 'MustUse'}}
   for (;;producesMustUsePointer());
-  for (;;producesMustUseRef()); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  for (;;producesMustUseRef()); // expected-error {{Unused value of must-use type 'MustUse'}}
   for (;;producesMayUse());
   for (;;producesMayUsePointer());
   for (;;producesMayUseRef());
 
-  use((producesMustUse(), false)); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  use((producesMustUse(), false)); // expected-error {{Unused value of must-use type 'MustUse'}}
   use((producesMustUsePointer(), false));
-  use((producesMustUseRef(), false)); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+  use((producesMustUseRef(), false)); // expected-error {{Unused value of must-use type 'MustUse'}}
   use((producesMayUse(), false));
   use((producesMayUsePointer(), false));
   use((producesMayUseRef(), false));
 
   switch (1) {
   case 1:
-    producesMustUse(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+    producesMustUse(); // expected-error {{Unused value of must-use type 'MustUse'}}
     producesMustUsePointer();
-    producesMustUseRef(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+    producesMustUseRef(); // expected-error {{Unused value of must-use type 'MustUse'}}
     producesMayUse();
     producesMayUsePointer();
     producesMayUseRef();
   case 2:
-    producesMustUse(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+    producesMustUse(); // expected-error {{Unused value of must-use type 'MustUse'}}
   case 3:
     producesMustUsePointer();
   case 4:
-    producesMustUseRef(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+    producesMustUseRef(); // expected-error {{Unused value of must-use type 'MustUse'}}
   case 5:
     producesMayUse();
   case 6:
     producesMayUsePointer();
   case 7:
     producesMayUseRef();
   default:
-    producesMustUse(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+    producesMustUse(); // expected-error {{Unused value of must-use type 'MustUse'}}
     producesMustUsePointer();
-    producesMustUseRef(); // expected-error {{Unused MOZ_MUST_USE value of type 'MustUse'}}
+    producesMustUseRef(); // expected-error {{Unused value of must-use type 'MustUse'}}
     producesMayUse();
     producesMayUsePointer();
     producesMayUseRef();
   }
 
   use(producesMustUse());
   use(producesMustUsePointer());
   use(producesMustUseRef());
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/tests/TestNeedsNoVTableType.cpp
@@ -0,0 +1,94 @@
+#define MOZ_NEEDS_NO_VTABLE_TYPE __attribute__((annotate("moz_needs_no_vtable_type")))
+
+template <class T>
+struct MOZ_NEEDS_NO_VTABLE_TYPE PickyConsumer { // expected-error {{'PickyConsumer<B>' cannot be instantiated because 'B' has a VTable}} expected-error {{'PickyConsumer<E>' cannot be instantiated because 'E' has a VTable}} expected-error {{'PickyConsumer<F>' cannot be instantiated because 'F' has a VTable}} expected-error {{'PickyConsumer<G>' cannot be instantiated because 'G' has a VTable}}
+  T *m;
+};
+
+template <class T>
+struct MOZ_NEEDS_NO_VTABLE_TYPE PickyConsumer_A { // expected-error {{'PickyConsumer_A<B>' cannot be instantiated because 'B' has a VTable}} expected-error {{'PickyConsumer_A<E>' cannot be instantiated because 'E' has a VTable}} expected-error {{'PickyConsumer_A<F>' cannot be instantiated because 'F' has a VTable}} expected-error {{'PickyConsumer_A<G>' cannot be instantiated because 'G' has a VTable}}
+  T *m;
+};
+template <class T>
+struct PickyConsumerWrapper {
+  PickyConsumer_A<T> m; // expected-note {{bad instantiation of 'PickyConsumer_A<B>' requested here}} expected-note {{bad instantiation of 'PickyConsumer_A<E>' requested here}} expected-note {{bad instantiation of 'PickyConsumer_A<F>' requested here}} expected-note {{bad instantiation of 'PickyConsumer_A<G>' requested here}}
+};
+
+template <class T>
+struct MOZ_NEEDS_NO_VTABLE_TYPE PickyConsumer_B { // expected-error {{'PickyConsumer_B<B>' cannot be instantiated because 'B' has a VTable}} expected-error {{'PickyConsumer_B<E>' cannot be instantiated because 'E' has a VTable}} expected-error {{'PickyConsumer_B<F>' cannot be instantiated because 'F' has a VTable}} expected-error {{'PickyConsumer_B<G>' cannot be instantiated because 'G' has a VTable}}
+  T *m;
+};
+template <class T>
+struct PickyConsumerSubclass : PickyConsumer_B<T> {}; // expected-note {{bad instantiation of 'PickyConsumer_B<B>' requested here}} expected-note {{bad instantiation of 'PickyConsumer_B<E>' requested here}} expected-note {{bad instantiation of 'PickyConsumer_B<F>' requested here}} expected-note {{bad instantiation of 'PickyConsumer_B<G>' requested here}}
+
+template <class T>
+struct NonPickyConsumer {
+  T *m;
+};
+
+struct A {};
+struct B : virtual A {};
+struct C : A {};
+struct D {
+  void d();
+};
+struct E {
+  virtual void e();
+};
+struct F : E {
+  virtual void e() final;
+};
+struct G {
+  virtual void e() = 0;
+};
+
+void f() {
+  {
+    PickyConsumer<A> a1;
+    PickyConsumerWrapper<A> a2;
+    PickyConsumerSubclass<A> a3;
+    NonPickyConsumer<A> a4;
+  }
+
+  {
+    PickyConsumer<B> a1; // expected-note {{bad instantiation of 'PickyConsumer<B>' requested here}}
+    PickyConsumerWrapper<B> a2;
+    PickyConsumerSubclass<B> a3;
+    NonPickyConsumer<B> a4;
+  }
+
+  {
+    PickyConsumer<C> a1;
+    PickyConsumerWrapper<C> a2;
+    PickyConsumerSubclass<C> a3;
+    NonPickyConsumer<C> a4;
+  }
+
+  {
+    PickyConsumer<D> a1;
+    PickyConsumerWrapper<D> a2;
+    PickyConsumerSubclass<D> a3;
+    NonPickyConsumer<D> a4;
+  }
+
+  {
+    PickyConsumer<E> a1; // expected-note {{bad instantiation of 'PickyConsumer<E>' requested here}}
+    PickyConsumerWrapper<E> a2;
+    PickyConsumerSubclass<E> a3;
+    NonPickyConsumer<E> a4;
+  }
+
+  {
+    PickyConsumer<F> a1; // expected-note {{bad instantiation of 'PickyConsumer<F>' requested here}}
+    PickyConsumerWrapper<F> a2;
+    PickyConsumerSubclass<F> a3;
+    NonPickyConsumer<F> a4;
+  }
+
+  {
+    PickyConsumer<G> a1; // expected-note {{bad instantiation of 'PickyConsumer<G>' requested here}}
+    PickyConsumerWrapper<G> a2;
+    PickyConsumerSubclass<G> a3;
+    NonPickyConsumer<G> a4;
+  }
+}
--- a/build/clang-plugin/tests/TestNonHeapClass.cpp
+++ b/build/clang-plugin/tests/TestNonHeapClass.cpp
@@ -31,32 +31,32 @@ void misuseNonHeapClass(int len) {
   gobble(len <= 5 ? &valid : new NonHeap); // expected-error {{variable of type 'NonHeap' is not valid on the heap}}
 
   char buffer[sizeof(NonHeap)];
   gobble(new (buffer) NonHeap);
 }
 
 NonHeap validStatic;
 struct RandomClass {
-  NonHeap nonstaticMember; // expected-note {{'RandomClass' is a non-heap class because member 'nonstaticMember' is a non-heap class 'NonHeap'}}
+  NonHeap nonstaticMember; // expected-note {{'RandomClass' is a non-heap type because member 'nonstaticMember' is a non-heap type 'NonHeap'}}
   static NonHeap staticMember;
 };
 struct MOZ_NONHEAP_CLASS RandomNonHeapClass {
   NonHeap nonstaticMember;
   static NonHeap staticMember;
 };
 
-struct BadInherit : NonHeap {}; // expected-note {{'BadInherit' is a non-heap class because it inherits from a non-heap class 'NonHeap'}}
+struct BadInherit : NonHeap {}; // expected-note {{'BadInherit' is a non-heap type because it inherits from a non-heap type 'NonHeap'}}
 struct MOZ_NONHEAP_CLASS GoodInherit : NonHeap {};
 
 void useStuffWrongly() {
   gobble(new BadInherit); // expected-error {{variable of type 'BadInherit' is not valid on the heap}}
   gobble(new RandomClass); // expected-error {{variable of type 'RandomClass' is not valid on the heap}}
 }
 
-// Stack class overrides non-heap classes.
+// Stack class overrides non-heap typees.
 struct MOZ_STACK_CLASS StackClass {};
 struct MOZ_NONHEAP_CLASS InferredStackClass : GoodInherit {
   NonHeap nonstaticMember;
-  StackClass stackClass; // expected-note {{'InferredStackClass' is a stack class because member 'stackClass' is a stack class 'StackClass'}}
+  StackClass stackClass; // expected-note {{'InferredStackClass' is a stack type because member 'stackClass' is a stack type 'StackClass'}}
 };
 
 InferredStackClass global; // expected-error {{variable of type 'InferredStackClass' only valid on the stack}}
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/tests/TestNonMemMovable.cpp
@@ -0,0 +1,812 @@
+#define MOZ_NON_MEMMOVABLE __attribute__((annotate("moz_non_memmovable")))
+#define MOZ_NEEDS_MEMMOVABLE_TYPE __attribute__((annotate("moz_needs_memmovable_type")))
+
+/*
+  These are a bunch of structs with variable levels of memmovability.
+  They will be used as template parameters to the various NeedyTemplates
+*/
+struct MOZ_NON_MEMMOVABLE NonMovable {}; // expected-note-re + {{'{{.*}}' is non-memmovable because of the MOZ_NON_MEMMOVABLE annotation on 'NonMovable'}}
+struct Movable {};
+
+// Subclasses
+struct S_NonMovable : NonMovable {};
+struct S_Movable : Movable {};
+
+// Members
+struct W_NonMovable {
+  NonMovable m;
+};
+struct W_Movable {
+  Movable m;
+};
+
+// Wrapped Subclasses
+struct WS_NonMovable {
+  S_NonMovable m;
+};
+struct WS_Movable {
+  S_Movable m;
+};
+
+// Combinations of the above
+struct SW_NonMovable : W_NonMovable {};
+struct SW_Movable : W_Movable {};
+
+struct SWS_NonMovable : WS_NonMovable {};
+struct SWS_Movable : WS_Movable {};
+
+// Basic templated wrapper
+template <class T>
+struct Template_Inline {
+  T m;
+};
+
+template <class T>
+struct Template_Ref {
+  T* m;
+};
+
+template <class T>
+struct Template_Unused {};
+
+template <class T>
+struct MOZ_NON_MEMMOVABLE Template_NonMovable {}; // expected-note-re + {{'{{.*}}' is non-memmovable because of the MOZ_NON_MEMMOVABLE annotation on 'Template_NonMovable<{{.*}}>'}}
+
+/*
+  These tests take the following form:
+  DECLARATIONS => Declarations of the templates which are either marked with MOZ_NEEDS_MEMMOVABLE_TYPE
+                  or which instantiate a MOZ_NEEDS_MEMMOVABLE_TYPE through some mechanism.
+  BAD N        => Instantiations of the wrapper template with each of the non-memmovable types.
+                  The prefix S_ means subclass, W_ means wrapped. Each of these rows should produce an error
+                  on the NeedyTemplate in question, and a note at the instantiation location of that template.
+                  Unfortunately, on every case more complicated than bad1, the instantiation location is
+                  within another template. Thus, the notes are expected on the template in question which
+                  actually instantiates the MOZ_NEEDS_MEMMOVABLE_TYPE template.
+  GOOD N       => Instantiations of the wrapper template with each of the memmovable types.
+                  This is meant as a sanity check to ensure that we don't reject valid instantiations of
+                  templates.
+
+
+  Note 1: Each set uses it's own types to ensure that they don't re-use each-other's template specializations.
+  If they did, then some of the error messages would not be emitted (as error messages are emitted for template
+  specializations, rather than for variable declarations)
+
+  Note 2: Every instance of NeedyTemplate contains a member of type T. This is to ensure that T is actually
+  instantiated (if T is a template) by clang. If T isn't instantiated, then we can't actually tell if it is
+  NON_MEMMOVABLE. (This is OK in practice, as you cannot memmove a type which you don't know the size of).
+
+  Note 3: There are a set of tests for specializations of NeedyTemplate at the bottom. For each set of tests,
+  these tests contribute two expected errors to the templates.
+*/
+
+//
+// 1 - Unwrapped MOZ_NEEDS_MEMMOVABLE_TYPE
+//
+
+template <class T>
+struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate1 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate1<{{.*}}>' with non-memmovable template argument '{{.*}}'}}
+
+void bad1() {
+  NeedyTemplate1<NonMovable> a1; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<S_NonMovable> a2; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<W_NonMovable> a3; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<WS_NonMovable> a4; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<SW_NonMovable> a5; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<SWS_NonMovable> a6; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+
+  NeedyTemplate1<Template_Inline<NonMovable> > b1; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_Inline<S_NonMovable> > b2; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_Inline<W_NonMovable> > b3; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_Inline<WS_NonMovable> > b4; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_Inline<SW_NonMovable> > b5; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_Inline<SWS_NonMovable> > b6; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+
+  NeedyTemplate1<Template_NonMovable<NonMovable> > c1; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_NonMovable<S_NonMovable> > c2; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_NonMovable<W_NonMovable> > c3; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_NonMovable<WS_NonMovable> > c4; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_NonMovable<SW_NonMovable> > c5; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_NonMovable<SWS_NonMovable> > c6; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_NonMovable<Movable> > c7; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_NonMovable<S_Movable> > c8; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_NonMovable<W_Movable> > c9; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_NonMovable<WS_Movable> > c10; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_NonMovable<SW_Movable> > c11; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+  NeedyTemplate1<Template_NonMovable<SWS_Movable> > c12; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}}
+}
+
+void good1() {
+  NeedyTemplate1<Movable> a1;
+  NeedyTemplate1<S_Movable> a2;
+  NeedyTemplate1<W_Movable> a3;
+  NeedyTemplate1<WS_Movable> a4;
+  NeedyTemplate1<SW_Movable> a5;
+  NeedyTemplate1<SWS_Movable> a6;
+
+  NeedyTemplate1<Template_Inline<Movable> > b1;
+  NeedyTemplate1<Template_Inline<S_Movable> > b2;
+  NeedyTemplate1<Template_Inline<W_Movable> > b3;
+  NeedyTemplate1<Template_Inline<WS_Movable> > b4;
+  NeedyTemplate1<Template_Inline<SW_Movable> > b5;
+  NeedyTemplate1<Template_Inline<SWS_Movable> > b6;
+
+  NeedyTemplate1<Template_Unused<Movable> > c1;
+  NeedyTemplate1<Template_Unused<S_Movable> > c2;
+  NeedyTemplate1<Template_Unused<W_Movable> > c3;
+  NeedyTemplate1<Template_Unused<WS_Movable> > c4;
+  NeedyTemplate1<Template_Unused<SW_Movable> > c5;
+  NeedyTemplate1<Template_Unused<SWS_Movable> > c6;
+  NeedyTemplate1<Template_Unused<NonMovable> > c7;
+  NeedyTemplate1<Template_Unused<S_NonMovable> > c8;
+  NeedyTemplate1<Template_Unused<W_NonMovable> > c9;
+  NeedyTemplate1<Template_Unused<WS_NonMovable> > c10;
+  NeedyTemplate1<Template_Unused<SW_NonMovable> > c11;
+  NeedyTemplate1<Template_Unused<SWS_NonMovable> > c12;
+
+  NeedyTemplate1<Template_Ref<Movable> > d1;
+  NeedyTemplate1<Template_Ref<S_Movable> > d2;
+  NeedyTemplate1<Template_Ref<W_Movable> > d3;
+  NeedyTemplate1<Template_Ref<WS_Movable> > d4;
+  NeedyTemplate1<Template_Ref<SW_Movable> > d5;
+  NeedyTemplate1<Template_Ref<SWS_Movable> > d6;
+  NeedyTemplate1<Template_Ref<NonMovable> > d7;
+  NeedyTemplate1<Template_Ref<S_NonMovable> > d8;
+  NeedyTemplate1<Template_Ref<W_NonMovable> > d9;
+  NeedyTemplate1<Template_Ref<WS_NonMovable> > d10;
+  NeedyTemplate1<Template_Ref<SW_NonMovable> > d11;
+  NeedyTemplate1<Template_Ref<SWS_NonMovable> > d12;
+}
+
+//
+// 2 - Subclassed MOZ_NEEDS_MEMMOVABLE_TYPE
+//
+
+template <class T>
+struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate2 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate2<{{.*}}>' with non-memmovable template argument '{{.*}}'}}
+template <class T>
+struct S_NeedyTemplate2 : NeedyTemplate2<T> {}; // expected-note-re 26 {{instantiation of 'NeedyTemplate2<{{.*}}>' requested here}}
+
+void bad2() {
+  S_NeedyTemplate2<NonMovable> a1;
+  S_NeedyTemplate2<S_NonMovable> a2;
+  S_NeedyTemplate2<W_NonMovable> a3;
+  S_NeedyTemplate2<WS_NonMovable> a4;
+  S_NeedyTemplate2<SW_NonMovable> a5;
+  S_NeedyTemplate2<SWS_NonMovable> a6;
+
+  S_NeedyTemplate2<Template_Inline<NonMovable> > b1;
+  S_NeedyTemplate2<Template_Inline<S_NonMovable> > b2;
+  S_NeedyTemplate2<Template_Inline<W_NonMovable> > b3;
+  S_NeedyTemplate2<Template_Inline<WS_NonMovable> > b4;
+  S_NeedyTemplate2<Template_Inline<SW_NonMovable> > b5;
+  S_NeedyTemplate2<Template_Inline<SWS_NonMovable> > b6;
+
+  S_NeedyTemplate2<Template_NonMovable<NonMovable> > c1;
+  S_NeedyTemplate2<Template_NonMovable<S_NonMovable> > c2;
+  S_NeedyTemplate2<Template_NonMovable<W_NonMovable> > c3;
+  S_NeedyTemplate2<Template_NonMovable<WS_NonMovable> > c4;
+  S_NeedyTemplate2<Template_NonMovable<SW_NonMovable> > c5;
+  S_NeedyTemplate2<Template_NonMovable<SWS_NonMovable> > c6;
+  S_NeedyTemplate2<Template_NonMovable<Movable> > c7;
+  S_NeedyTemplate2<Template_NonMovable<S_Movable> > c8;
+  S_NeedyTemplate2<Template_NonMovable<W_Movable> > c9;
+  S_NeedyTemplate2<Template_NonMovable<WS_Movable> > c10;
+  S_NeedyTemplate2<Template_NonMovable<SW_Movable> > c11;
+  S_NeedyTemplate2<Template_NonMovable<SWS_Movable> > c12;
+}
+
+void good2() {
+  S_NeedyTemplate2<Movable> a1;
+  S_NeedyTemplate2<S_Movable> a2;
+  S_NeedyTemplate2<W_Movable> a3;
+  S_NeedyTemplate2<WS_Movable> a4;
+  S_NeedyTemplate2<SW_Movable> a5;
+  S_NeedyTemplate2<SWS_Movable> a6;
+
+  S_NeedyTemplate2<Template_Inline<Movable> > b1;
+  S_NeedyTemplate2<Template_Inline<S_Movable> > b2;
+  S_NeedyTemplate2<Template_Inline<W_Movable> > b3;
+  S_NeedyTemplate2<Template_Inline<WS_Movable> > b4;
+  S_NeedyTemplate2<Template_Inline<SW_Movable> > b5;
+  S_NeedyTemplate2<Template_Inline<SWS_Movable> > b6;
+
+  S_NeedyTemplate2<Template_Unused<Movable> > c1;
+  S_NeedyTemplate2<Template_Unused<S_Movable> > c2;
+  S_NeedyTemplate2<Template_Unused<W_Movable> > c3;
+  S_NeedyTemplate2<Template_Unused<WS_Movable> > c4;
+  S_NeedyTemplate2<Template_Unused<SW_Movable> > c5;
+  S_NeedyTemplate2<Template_Unused<SWS_Movable> > c6;
+  S_NeedyTemplate2<Template_Unused<NonMovable> > c7;
+  S_NeedyTemplate2<Template_Unused<S_NonMovable> > c8;
+  S_NeedyTemplate2<Template_Unused<W_NonMovable> > c9;
+  S_NeedyTemplate2<Template_Unused<WS_NonMovable> > c10;
+  S_NeedyTemplate2<Template_Unused<SW_NonMovable> > c11;
+  S_NeedyTemplate2<Template_Unused<SWS_NonMovable> > c12;
+
+  S_NeedyTemplate2<Template_Ref<Movable> > d1;
+  S_NeedyTemplate2<Template_Ref<S_Movable> > d2;
+  S_NeedyTemplate2<Template_Ref<W_Movable> > d3;
+  S_NeedyTemplate2<Template_Ref<WS_Movable> > d4;
+  S_NeedyTemplate2<Template_Ref<SW_Movable> > d5;
+  S_NeedyTemplate2<Template_Ref<SWS_Movable> > d6;
+  S_NeedyTemplate2<Template_Ref<NonMovable> > d7;
+  S_NeedyTemplate2<Template_Ref<S_NonMovable> > d8;
+  S_NeedyTemplate2<Template_Ref<W_NonMovable> > d9;
+  S_NeedyTemplate2<Template_Ref<WS_NonMovable> > d10;
+  S_NeedyTemplate2<Template_Ref<SW_NonMovable> > d11;
+  S_NeedyTemplate2<Template_Ref<SWS_NonMovable> > d12;
+}
+
+//
+// 3 - Wrapped MOZ_NEEDS_MEMMOVABLE_TYPE
+//
+
+template <class T>
+struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate3 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate3<{{.*}}>' with non-memmovable template argument '{{.*}}'}}
+template <class T>
+struct W_NeedyTemplate3 {
+  NeedyTemplate3<T> m; // expected-note-re 26 {{instantiation of 'NeedyTemplate3<{{.*}}>' requested here}}
+};
+void bad3() {
+  W_NeedyTemplate3<NonMovable> a1;
+  W_NeedyTemplate3<S_NonMovable> a2;
+  W_NeedyTemplate3<W_NonMovable> a3;
+  W_NeedyTemplate3<WS_NonMovable> a4;
+  W_NeedyTemplate3<SW_NonMovable> a5;
+  W_NeedyTemplate3<SWS_NonMovable> a6;
+
+  W_NeedyTemplate3<Template_Inline<NonMovable> > b1;
+  W_NeedyTemplate3<Template_Inline<S_NonMovable> > b2;
+  W_NeedyTemplate3<Template_Inline<W_NonMovable> > b3;
+  W_NeedyTemplate3<Template_Inline<WS_NonMovable> > b4;
+  W_NeedyTemplate3<Template_Inline<SW_NonMovable> > b5;
+  W_NeedyTemplate3<Template_Inline<SWS_NonMovable> > b6;
+
+  W_NeedyTemplate3<Template_NonMovable<NonMovable> > c1;
+  W_NeedyTemplate3<Template_NonMovable<S_NonMovable> > c2;
+  W_NeedyTemplate3<Template_NonMovable<W_NonMovable> > c3;
+  W_NeedyTemplate3<Template_NonMovable<WS_NonMovable> > c4;
+  W_NeedyTemplate3<Template_NonMovable<SW_NonMovable> > c5;
+  W_NeedyTemplate3<Template_NonMovable<SWS_NonMovable> > c6;
+  W_NeedyTemplate3<Template_NonMovable<Movable> > c7;
+  W_NeedyTemplate3<Template_NonMovable<S_Movable> > c8;
+  W_NeedyTemplate3<Template_NonMovable<W_Movable> > c9;
+  W_NeedyTemplate3<Template_NonMovable<WS_Movable> > c10;
+  W_NeedyTemplate3<Template_NonMovable<SW_Movable> > c11;
+  W_NeedyTemplate3<Template_NonMovable<SWS_Movable> > c12;
+}
+
+void good3() {
+  W_NeedyTemplate3<Movable> a1;
+  W_NeedyTemplate3<S_Movable> a2;
+  W_NeedyTemplate3<W_Movable> a3;
+  W_NeedyTemplate3<WS_Movable> a4;
+  W_NeedyTemplate3<SW_Movable> a5;
+  W_NeedyTemplate3<SWS_Movable> a6;
+
+  W_NeedyTemplate3<Template_Inline<Movable> > b1;
+  W_NeedyTemplate3<Template_Inline<S_Movable> > b2;
+  W_NeedyTemplate3<Template_Inline<W_Movable> > b3;
+  W_NeedyTemplate3<Template_Inline<WS_Movable> > b4;
+  W_NeedyTemplate3<Template_Inline<SW_Movable> > b5;
+  W_NeedyTemplate3<Template_Inline<SWS_Movable> > b6;
+
+  W_NeedyTemplate3<Template_Unused<Movable> > c1;
+  W_NeedyTemplate3<Template_Unused<S_Movable> > c2;
+  W_NeedyTemplate3<Template_Unused<W_Movable> > c3;
+  W_NeedyTemplate3<Template_Unused<WS_Movable> > c4;
+  W_NeedyTemplate3<Template_Unused<SW_Movable> > c5;
+  W_NeedyTemplate3<Template_Unused<SWS_Movable> > c6;
+  W_NeedyTemplate3<Template_Unused<NonMovable> > c7;
+  W_NeedyTemplate3<Template_Unused<S_NonMovable> > c8;
+  W_NeedyTemplate3<Template_Unused<W_NonMovable> > c9;
+  W_NeedyTemplate3<Template_Unused<WS_NonMovable> > c10;
+  W_NeedyTemplate3<Template_Unused<SW_NonMovable> > c11;
+  W_NeedyTemplate3<Template_Unused<SWS_NonMovable> > c12;
+
+  W_NeedyTemplate3<Template_Ref<Movable> > d1;
+  W_NeedyTemplate3<Template_Ref<S_Movable> > d2;
+  W_NeedyTemplate3<Template_Ref<W_Movable> > d3;
+  W_NeedyTemplate3<Template_Ref<WS_Movable> > d4;
+  W_NeedyTemplate3<Template_Ref<SW_Movable> > d5;
+  W_NeedyTemplate3<Template_Ref<SWS_Movable> > d6;
+  W_NeedyTemplate3<Template_Ref<NonMovable> > d7;
+  W_NeedyTemplate3<Template_Ref<S_NonMovable> > d8;
+  W_NeedyTemplate3<Template_Ref<W_NonMovable> > d9;
+  W_NeedyTemplate3<Template_Ref<WS_NonMovable> > d10;
+  W_NeedyTemplate3<Template_Ref<SW_NonMovable> > d11;
+  W_NeedyTemplate3<Template_Ref<SWS_NonMovable> > d12;
+}
+
+//
+// 4 - Wrapped Subclassed MOZ_NEEDS_MEMMOVABLE_TYPE
+//
+
+template <class T>
+struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate4 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate4<{{.*}}>' with non-memmovable template argument '{{.*}}'}}
+template <class T>
+struct S_NeedyTemplate4 : NeedyTemplate4<T> {}; // expected-note-re 26 {{instantiation of 'NeedyTemplate4<{{.*}}>' requested here}}
+template <class T>
+struct WS_NeedyTemplate4 {
+  S_NeedyTemplate4<T> m;
+};
+void bad4() {
+  WS_NeedyTemplate4<NonMovable> a1;
+  WS_NeedyTemplate4<S_NonMovable> a2;
+  WS_NeedyTemplate4<W_NonMovable> a3;
+  WS_NeedyTemplate4<WS_NonMovable> a4;
+  WS_NeedyTemplate4<SW_NonMovable> a5;
+  WS_NeedyTemplate4<SWS_NonMovable> a6;
+
+  WS_NeedyTemplate4<Template_Inline<NonMovable> > b1;
+  WS_NeedyTemplate4<Template_Inline<S_NonMovable> > b2;
+  WS_NeedyTemplate4<Template_Inline<W_NonMovable> > b3;
+  WS_NeedyTemplate4<Template_Inline<WS_NonMovable> > b4;
+  WS_NeedyTemplate4<Template_Inline<SW_NonMovable> > b5;
+  WS_NeedyTemplate4<Template_Inline<SWS_NonMovable> > b6;
+
+  WS_NeedyTemplate4<Template_NonMovable<NonMovable> > c1;
+  WS_NeedyTemplate4<Template_NonMovable<S_NonMovable> > c2;
+  WS_NeedyTemplate4<Template_NonMovable<W_NonMovable> > c3;
+  WS_NeedyTemplate4<Template_NonMovable<WS_NonMovable> > c4;
+  WS_NeedyTemplate4<Template_NonMovable<SW_NonMovable> > c5;
+  WS_NeedyTemplate4<Template_NonMovable<SWS_NonMovable> > c6;
+  WS_NeedyTemplate4<Template_NonMovable<Movable> > c7;
+  WS_NeedyTemplate4<Template_NonMovable<S_Movable> > c8;
+  WS_NeedyTemplate4<Template_NonMovable<W_Movable> > c9;
+  WS_NeedyTemplate4<Template_NonMovable<WS_Movable> > c10;
+  WS_NeedyTemplate4<Template_NonMovable<SW_Movable> > c11;
+  WS_NeedyTemplate4<Template_NonMovable<SWS_Movable> > c12;
+}
+
+void good4() {
+  WS_NeedyTemplate4<Movable> a1;
+  WS_NeedyTemplate4<S_Movable> a2;
+  WS_NeedyTemplate4<W_Movable> a3;
+  WS_NeedyTemplate4<WS_Movable> a4;
+  WS_NeedyTemplate4<SW_Movable> a5;
+  WS_NeedyTemplate4<SWS_Movable> a6;
+
+  WS_NeedyTemplate4<Template_Inline<Movable> > b1;
+  WS_NeedyTemplate4<Template_Inline<S_Movable> > b2;
+  WS_NeedyTemplate4<Template_Inline<W_Movable> > b3;
+  WS_NeedyTemplate4<Template_Inline<WS_Movable> > b4;
+  WS_NeedyTemplate4<Template_Inline<SW_Movable> > b5;
+  WS_NeedyTemplate4<Template_Inline<SWS_Movable> > b6;
+
+  WS_NeedyTemplate4<Template_Unused<Movable> > c1;
+  WS_NeedyTemplate4<Template_Unused<S_Movable> > c2;
+  WS_NeedyTemplate4<Template_Unused<W_Movable> > c3;
+  WS_NeedyTemplate4<Template_Unused<WS_Movable> > c4;
+  WS_NeedyTemplate4<Template_Unused<SW_Movable> > c5;
+  WS_NeedyTemplate4<Template_Unused<SWS_Movable> > c6;
+  WS_NeedyTemplate4<Template_Unused<NonMovable> > c7;
+  WS_NeedyTemplate4<Template_Unused<S_NonMovable> > c8;
+  WS_NeedyTemplate4<Template_Unused<W_NonMovable> > c9;
+  WS_NeedyTemplate4<Template_Unused<WS_NonMovable> > c10;
+  WS_NeedyTemplate4<Template_Unused<SW_NonMovable> > c11;
+  WS_NeedyTemplate4<Template_Unused<SWS_NonMovable> > c12;
+
+  WS_NeedyTemplate4<Template_Ref<Movable> > d1;
+  WS_NeedyTemplate4<Template_Ref<S_Movable> > d2;
+  WS_NeedyTemplate4<Template_Ref<W_Movable> > d3;
+  WS_NeedyTemplate4<Template_Ref<WS_Movable> > d4;
+  WS_NeedyTemplate4<Template_Ref<SW_Movable> > d5;
+  WS_NeedyTemplate4<Template_Ref<SWS_Movable> > d6;
+  WS_NeedyTemplate4<Template_Ref<NonMovable> > d7;
+  WS_NeedyTemplate4<Template_Ref<S_NonMovable> > d8;
+  WS_NeedyTemplate4<Template_Ref<W_NonMovable> > d9;
+  WS_NeedyTemplate4<Template_Ref<WS_NonMovable> > d10;
+  WS_NeedyTemplate4<Template_Ref<SW_NonMovable> > d11;
+  WS_NeedyTemplate4<Template_Ref<SWS_NonMovable> > d12;
+}
+
+//
+// 5 - Subclassed Wrapped MOZ_NEEDS_MEMMOVABLE_TYPE
+//
+
+template <class T>
+struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate5 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate5<{{.*}}>' with non-memmovable template argument '{{.*}}'}}
+template <class T>
+struct W_NeedyTemplate5 {
+  NeedyTemplate5<T> m; // expected-note-re 26 {{instantiation of 'NeedyTemplate5<{{.*}}>' requested here}}
+};
+template <class T>
+struct SW_NeedyTemplate5 : W_NeedyTemplate5<T> {};
+void bad5() {
+  SW_NeedyTemplate5<NonMovable> a1;
+  SW_NeedyTemplate5<S_NonMovable> a2;
+  SW_NeedyTemplate5<W_NonMovable> a3;
+  SW_NeedyTemplate5<WS_NonMovable> a4;
+  SW_NeedyTemplate5<SW_NonMovable> a5;
+  SW_NeedyTemplate5<SWS_NonMovable> a6;
+
+  SW_NeedyTemplate5<Template_Inline<NonMovable> > b1;
+  SW_NeedyTemplate5<Template_Inline<S_NonMovable> > b2;
+  SW_NeedyTemplate5<Template_Inline<W_NonMovable> > b3;
+  SW_NeedyTemplate5<Template_Inline<WS_NonMovable> > b4;
+  SW_NeedyTemplate5<Template_Inline<SW_NonMovable> > b5;
+  SW_NeedyTemplate5<Template_Inline<SWS_NonMovable> > b6;
+
+  SW_NeedyTemplate5<Template_NonMovable<NonMovable> > c1;
+  SW_NeedyTemplate5<Template_NonMovable<S_NonMovable> > c2;
+  SW_NeedyTemplate5<Template_NonMovable<W_NonMovable> > c3;
+  SW_NeedyTemplate5<Template_NonMovable<WS_NonMovable> > c4;
+  SW_NeedyTemplate5<Template_NonMovable<SW_NonMovable> > c5;
+  SW_NeedyTemplate5<Template_NonMovable<SWS_NonMovable> > c6;
+  SW_NeedyTemplate5<Template_NonMovable<Movable> > c7;
+  SW_NeedyTemplate5<Template_NonMovable<S_Movable> > c8;
+  SW_NeedyTemplate5<Template_NonMovable<W_Movable> > c9;
+  SW_NeedyTemplate5<Template_NonMovable<WS_Movable> > c10;
+  SW_NeedyTemplate5<Template_NonMovable<SW_Movable> > c11;
+  SW_NeedyTemplate5<Template_NonMovable<SWS_Movable> > c12;
+}
+
+void good5() {
+  SW_NeedyTemplate5<Movable> a1;
+  SW_NeedyTemplate5<S_Movable> a2;
+  SW_NeedyTemplate5<W_Movable> a3;
+  SW_NeedyTemplate5<WS_Movable> a4;
+  SW_NeedyTemplate5<SW_Movable> a5;
+  SW_NeedyTemplate5<SWS_Movable> a6;
+
+  SW_NeedyTemplate5<Template_Inline<Movable> > b1;
+  SW_NeedyTemplate5<Template_Inline<S_Movable> > b2;
+  SW_NeedyTemplate5<Template_Inline<W_Movable> > b3;
+  SW_NeedyTemplate5<Template_Inline<WS_Movable> > b4;
+  SW_NeedyTemplate5<Template_Inline<SW_Movable> > b5;
+  SW_NeedyTemplate5<Template_Inline<SWS_Movable> > b6;
+
+  SW_NeedyTemplate5<Template_Unused<Movable> > c1;
+  SW_NeedyTemplate5<Template_Unused<S_Movable> > c2;
+  SW_NeedyTemplate5<Template_Unused<W_Movable> > c3;
+  SW_NeedyTemplate5<Template_Unused<WS_Movable> > c4;
+  SW_NeedyTemplate5<Template_Unused<SW_Movable> > c5;
+  SW_NeedyTemplate5<Template_Unused<SWS_Movable> > c6;
+  SW_NeedyTemplate5<Template_Unused<NonMovable> > c7;
+  SW_NeedyTemplate5<Template_Unused<S_NonMovable> > c8;
+  SW_NeedyTemplate5<Template_Unused<W_NonMovable> > c9;
+  SW_NeedyTemplate5<Template_Unused<WS_NonMovable> > c10;
+  SW_NeedyTemplate5<Template_Unused<SW_NonMovable> > c11;
+  SW_NeedyTemplate5<Template_Unused<SWS_NonMovable> > c12;
+
+  SW_NeedyTemplate5<Template_Ref<Movable> > d1;
+  SW_NeedyTemplate5<Template_Ref<S_Movable> > d2;
+  SW_NeedyTemplate5<Template_Ref<W_Movable> > d3;
+  SW_NeedyTemplate5<Template_Ref<WS_Movable> > d4;
+  SW_NeedyTemplate5<Template_Ref<SW_Movable> > d5;
+  SW_NeedyTemplate5<Template_Ref<SWS_Movable> > d6;
+  SW_NeedyTemplate5<Template_Ref<NonMovable> > d7;
+  SW_NeedyTemplate5<Template_Ref<S_NonMovable> > d8;
+  SW_NeedyTemplate5<Template_Ref<W_NonMovable> > d9;
+  SW_NeedyTemplate5<Template_Ref<WS_NonMovable> > d10;
+  SW_NeedyTemplate5<Template_Ref<SW_NonMovable> > d11;
+  SW_NeedyTemplate5<Template_Ref<SWS_NonMovable> > d12;
+}
+
+//
+// 6 - MOZ_NEEDS_MEMMOVABLE_TYPE instantiated with default template argument
+//
+// Note: This has an extra error, because it also includes a test with the default template argument.
+//
+
+template <class T>
+struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate6 {T m;}; // expected-error-re 27 {{Cannot instantiate 'NeedyTemplate6<{{.*}}>' with non-memmovable template argument '{{.*}}'}}
+template <class T>
+struct W_NeedyTemplate6 {
+  NeedyTemplate6<T> m; // expected-note-re 27 {{instantiation of 'NeedyTemplate6<{{.*}}>' requested here}}
+};
+template <class T>
+struct SW_NeedyTemplate6 : W_NeedyTemplate6<T> {};
+// We create a different NonMovable type here, as NeedyTemplate6 will already be instantiated with NonMovable
+struct MOZ_NON_MEMMOVABLE NonMovable2 {}; // expected-note {{'NonMovable2' is non-memmovable because of the MOZ_NON_MEMMOVABLE annotation on 'NonMovable2'}}
+template <class T = NonMovable2>
+struct Defaulted_SW_NeedyTemplate6 {
+  SW_NeedyTemplate6<T> m;
+};
+void bad6() {
+  Defaulted_SW_NeedyTemplate6<NonMovable> a1;
+  Defaulted_SW_NeedyTemplate6<S_NonMovable> a2;
+  Defaulted_SW_NeedyTemplate6<W_NonMovable> a3;
+  Defaulted_SW_NeedyTemplate6<WS_NonMovable> a4;
+  Defaulted_SW_NeedyTemplate6<SW_NonMovable> a5;
+  Defaulted_SW_NeedyTemplate6<SWS_NonMovable> a6;
+
+  Defaulted_SW_NeedyTemplate6<Template_Inline<NonMovable> > b1;
+  Defaulted_SW_NeedyTemplate6<Template_Inline<S_NonMovable> > b2;
+  Defaulted_SW_NeedyTemplate6<Template_Inline<W_NonMovable> > b3;
+  Defaulted_SW_NeedyTemplate6<Template_Inline<WS_NonMovable> > b4;
+  Defaulted_SW_NeedyTemplate6<Template_Inline<SW_NonMovable> > b5;
+  Defaulted_SW_NeedyTemplate6<Template_Inline<SWS_NonMovable> > b6;
+
+  Defaulted_SW_NeedyTemplate6<Template_NonMovable<NonMovable> > c1;
+  Defaulted_SW_NeedyTemplate6<Template_NonMovable<S_NonMovable> > c2;
+  Defaulted_SW_NeedyTemplate6<Template_NonMovable<W_NonMovable> > c3;
+  Defaulted_SW_NeedyTemplate6<Template_NonMovable<WS_NonMovable> > c4;
+  Defaulted_SW_NeedyTemplate6<Template_NonMovable<SW_NonMovable> > c5;
+  Defaulted_SW_NeedyTemplate6<Template_NonMovable<SWS_NonMovable> > c6;
+  Defaulted_SW_NeedyTemplate6<Template_NonMovable<Movable> > c7;
+  Defaulted_SW_NeedyTemplate6<Template_NonMovable<S_Movable> > c8;
+  Defaulted_SW_NeedyTemplate6<Template_NonMovable<W_Movable> > c9;
+  Defaulted_SW_NeedyTemplate6<Template_NonMovable<WS_Movable> > c10;
+  Defaulted_SW_NeedyTemplate6<Template_NonMovable<SW_Movable> > c11;
+  Defaulted_SW_NeedyTemplate6<Template_NonMovable<SWS_Movable> > c12;
+
+  Defaulted_SW_NeedyTemplate6<> c13;
+}
+
+void good6() {
+  Defaulted_SW_NeedyTemplate6<Movable> a1;
+  Defaulted_SW_NeedyTemplate6<S_Movable> a2;
+  Defaulted_SW_NeedyTemplate6<W_Movable> a3;
+  Defaulted_SW_NeedyTemplate6<WS_Movable> a4;
+  Defaulted_SW_NeedyTemplate6<SW_Movable> a5;
+  Defaulted_SW_NeedyTemplate6<SWS_Movable> a6;
+
+  Defaulted_SW_NeedyTemplate6<Template_Inline<Movable> > b1;
+  Defaulted_SW_NeedyTemplate6<Template_Inline<S_Movable> > b2;
+  Defaulted_SW_NeedyTemplate6<Template_Inline<W_Movable> > b3;
+  Defaulted_SW_NeedyTemplate6<Template_Inline<WS_Movable> > b4;
+  Defaulted_SW_NeedyTemplate6<Template_Inline<SW_Movable> > b5;
+  Defaulted_SW_NeedyTemplate6<Template_Inline<SWS_Movable> > b6;
+
+  Defaulted_SW_NeedyTemplate6<Template_Unused<Movable> > c1;
+  Defaulted_SW_NeedyTemplate6<Template_Unused<S_Movable> > c2;
+  Defaulted_SW_NeedyTemplate6<Template_Unused<W_Movable> > c3;
+  Defaulted_SW_NeedyTemplate6<Template_Unused<WS_Movable> > c4;
+  Defaulted_SW_NeedyTemplate6<Template_Unused<SW_Movable> > c5;
+  Defaulted_SW_NeedyTemplate6<Template_Unused<SWS_Movable> > c6;
+  Defaulted_SW_NeedyTemplate6<Template_Unused<NonMovable> > c7;
+  Defaulted_SW_NeedyTemplate6<Template_Unused<S_NonMovable> > c8;
+  Defaulted_SW_NeedyTemplate6<Template_Unused<W_NonMovable> > c9;
+  Defaulted_SW_NeedyTemplate6<Template_Unused<WS_NonMovable> > c10;
+  Defaulted_SW_NeedyTemplate6<Template_Unused<SW_NonMovable> > c11;
+  Defaulted_SW_NeedyTemplate6<Template_Unused<SWS_NonMovable> > c12;
+
+  Defaulted_SW_NeedyTemplate6<Template_Ref<Movable> > d1;
+  Defaulted_SW_NeedyTemplate6<Template_Ref<S_Movable> > d2;
+  Defaulted_SW_NeedyTemplate6<Template_Ref<W_Movable> > d3;
+  Defaulted_SW_NeedyTemplate6<Template_Ref<WS_Movable> > d4;
+  Defaulted_SW_NeedyTemplate6<Template_Ref<SW_Movable> > d5;
+  Defaulted_SW_NeedyTemplate6<Template_Ref<SWS_Movable> > d6;
+  Defaulted_SW_NeedyTemplate6<Template_Ref<NonMovable> > d7;
+  Defaulted_SW_NeedyTemplate6<Template_Ref<S_NonMovable> > d8;
+  Defaulted_SW_NeedyTemplate6<Template_Ref<W_NonMovable> > d9;
+  Defaulted_SW_NeedyTemplate6<Template_Ref<WS_NonMovable> > d10;
+  Defaulted_SW_NeedyTemplate6<Template_Ref<SW_NonMovable> > d11;
+  Defaulted_SW_NeedyTemplate6<Template_Ref<SWS_NonMovable> > d12;
+}
+
+//
+// 7 - MOZ_NEEDS_MEMMOVABLE_TYPE instantiated as default template argument
+//
+
+template <class T>
+struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate7 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate7<{{.*}}>' with non-memmovable template argument '{{.*}}'}}
+template <class T, class Q = NeedyTemplate7<T> >
+struct Defaulted_Templated_NeedyTemplate7 {Q m;}; // expected-note-re 26 {{instantiation of 'NeedyTemplate7<{{.*}}>' requested here}}
+void bad7() {
+  Defaulted_Templated_NeedyTemplate7<NonMovable> a1;
+  Defaulted_Templated_NeedyTemplate7<S_NonMovable> a2;
+  Defaulted_Templated_NeedyTemplate7<W_NonMovable> a3;
+  Defaulted_Templated_NeedyTemplate7<WS_NonMovable> a4;
+  Defaulted_Templated_NeedyTemplate7<SW_NonMovable> a5;
+  Defaulted_Templated_NeedyTemplate7<SWS_NonMovable> a6;
+
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<NonMovable> > b1;
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<S_NonMovable> > b2;
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<W_NonMovable> > b3;
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<WS_NonMovable> > b4;
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<SW_NonMovable> > b5;
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<SWS_NonMovable> > b6;
+
+  Defaulted_Templated_NeedyTemplate7<Template_NonMovable<NonMovable> > c1;
+  Defaulted_Templated_NeedyTemplate7<Template_NonMovable<S_NonMovable> > c2;
+  Defaulted_Templated_NeedyTemplate7<Template_NonMovable<W_NonMovable> > c3;
+  Defaulted_Templated_NeedyTemplate7<Template_NonMovable<WS_NonMovable> > c4;
+  Defaulted_Templated_NeedyTemplate7<Template_NonMovable<SW_NonMovable> > c5;
+  Defaulted_Templated_NeedyTemplate7<Template_NonMovable<SWS_NonMovable> > c6;
+  Defaulted_Templated_NeedyTemplate7<Template_NonMovable<Movable> > c7;
+  Defaulted_Templated_NeedyTemplate7<Template_NonMovable<S_Movable> > c8;
+  Defaulted_Templated_NeedyTemplate7<Template_NonMovable<W_Movable> > c9;
+  Defaulted_Templated_NeedyTemplate7<Template_NonMovable<WS_Movable> > c10;
+  Defaulted_Templated_NeedyTemplate7<Template_NonMovable<SW_Movable> > c11;
+  Defaulted_Templated_NeedyTemplate7<Template_NonMovable<SWS_Movable> > c12;
+}
+
+void good7() {
+  Defaulted_Templated_NeedyTemplate7<Movable> a1;
+  Defaulted_Templated_NeedyTemplate7<S_Movable> a2;
+  Defaulted_Templated_NeedyTemplate7<W_Movable> a3;
+  Defaulted_Templated_NeedyTemplate7<WS_Movable> a4;
+  Defaulted_Templated_NeedyTemplate7<SW_Movable> a5;
+  Defaulted_Templated_NeedyTemplate7<SWS_Movable> a6;
+
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<Movable> > b1;
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<S_Movable> > b2;
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<W_Movable> > b3;
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<WS_Movable> > b4;
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<SW_Movable> > b5;
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<SWS_Movable> > b6;
+
+  Defaulted_Templated_NeedyTemplate7<Template_Unused<Movable> > c1;
+  Defaulted_Templated_NeedyTemplate7<Template_Unused<S_Movable> > c2;
+  Defaulted_Templated_NeedyTemplate7<Template_Unused<W_Movable> > c3;
+  Defaulted_Templated_NeedyTemplate7<Template_Unused<WS_Movable> > c4;
+  Defaulted_Templated_NeedyTemplate7<Template_Unused<SW_Movable> > c5;
+  Defaulted_Templated_NeedyTemplate7<Template_Unused<SWS_Movable> > c6;
+  Defaulted_Templated_NeedyTemplate7<Template_Unused<NonMovable> > c7;
+  Defaulted_Templated_NeedyTemplate7<Template_Unused<S_NonMovable> > c8;
+  Defaulted_Templated_NeedyTemplate7<Template_Unused<W_NonMovable> > c9;
+  Defaulted_Templated_NeedyTemplate7<Template_Unused<WS_NonMovable> > c10;
+  Defaulted_Templated_NeedyTemplate7<Template_Unused<SW_NonMovable> > c11;
+  Defaulted_Templated_NeedyTemplate7<Template_Unused<SWS_NonMovable> > c12;
+
+  Defaulted_Templated_NeedyTemplate7<Template_Ref<Movable> > d1;
+  Defaulted_Templated_NeedyTemplate7<Template_Ref<S_Movable> > d2;
+  Defaulted_Templated_NeedyTemplate7<Template_Ref<W_Movable> > d3;
+  Defaulted_Templated_NeedyTemplate7<Template_Ref<WS_Movable> > d4;
+  Defaulted_Templated_NeedyTemplate7<Template_Ref<SW_Movable> > d5;
+  Defaulted_Templated_NeedyTemplate7<Template_Ref<SWS_Movable> > d6;
+  Defaulted_Templated_NeedyTemplate7<Template_Ref<NonMovable> > d7;
+  Defaulted_Templated_NeedyTemplate7<Template_Ref<S_NonMovable> > d8;
+  Defaulted_Templated_NeedyTemplate7<Template_Ref<W_NonMovable> > d9;
+  Defaulted_Templated_NeedyTemplate7<Template_Ref<WS_NonMovable> > d10;
+  Defaulted_Templated_NeedyTemplate7<Template_Ref<SW_NonMovable> > d11;
+  Defaulted_Templated_NeedyTemplate7<Template_Ref<SWS_NonMovable> > d12;
+}
+
+//
+// 8 - Wrapped MOZ_NEEDS_MEMMOVABLE_TYPE instantiated as default template argument
+//
+
+template <class T>
+struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate8 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate8<{{.*}}>' with non-memmovable template argument '{{.*}}'}}
+template <class T, class Q = NeedyTemplate8<T> >
+struct Defaulted_Templated_NeedyTemplate8 {Q m;}; // expected-note-re 26 {{instantiation of 'NeedyTemplate8<{{.*}}>' requested here}}
+template <class T>
+struct W_Defaulted_Templated_NeedyTemplate8 {
+  Defaulted_Templated_NeedyTemplate8<T> m;
+};
+void bad8() {
+  W_Defaulted_Templated_NeedyTemplate8<NonMovable> a1;
+  W_Defaulted_Templated_NeedyTemplate8<S_NonMovable> a2;
+  W_Defaulted_Templated_NeedyTemplate8<W_NonMovable> a3;
+  W_Defaulted_Templated_NeedyTemplate8<WS_NonMovable> a4;
+  W_Defaulted_Templated_NeedyTemplate8<SW_NonMovable> a5;
+  W_Defaulted_Templated_NeedyTemplate8<SWS_NonMovable> a6;
+
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<NonMovable> > b1;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<S_NonMovable> > b2;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<W_NonMovable> > b3;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<WS_NonMovable> > b4;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<SW_NonMovable> > b5;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<SWS_NonMovable> > b6;
+
+  W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<NonMovable> > c1;
+  W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<S_NonMovable> > c2;
+  W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<W_NonMovable> > c3;
+  W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<WS_NonMovable> > c4;
+  W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<SW_NonMovable> > c5;
+  W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<SWS_NonMovable> > c6;
+  W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<Movable> > c7;
+  W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<S_Movable> > c8;
+  W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<W_Movable> > c9;
+  W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<WS_Movable> > c10;
+  W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<SW_Movable> > c11;
+  W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<SWS_Movable> > c12;
+}
+
+void good8() {
+  W_Defaulted_Templated_NeedyTemplate8<Movable> a1;
+  W_Defaulted_Templated_NeedyTemplate8<S_Movable> a2;
+  W_Defaulted_Templated_NeedyTemplate8<W_Movable> a3;
+  W_Defaulted_Templated_NeedyTemplate8<WS_Movable> a4;
+  W_Defaulted_Templated_NeedyTemplate8<SW_Movable> a5;
+  W_Defaulted_Templated_NeedyTemplate8<SWS_Movable> a6;
+
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<Movable> > b1;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<S_Movable> > b2;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<W_Movable> > b3;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<WS_Movable> > b4;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<SW_Movable> > b5;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<SWS_Movable> > b6;
+
+  W_Defaulted_Templated_NeedyTemplate8<Template_Unused<Movable> > c1;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Unused<S_Movable> > c2;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Unused<W_Movable> > c3;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Unused<WS_Movable> > c4;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Unused<SW_Movable> > c5;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Unused<SWS_Movable> > c6;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Unused<NonMovable> > c7;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Unused<S_NonMovable> > c8;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Unused<W_NonMovable> > c9;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Unused<WS_NonMovable> > c10;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Unused<SW_NonMovable> > c11;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Unused<SWS_NonMovable> > c12;
+
+  W_Defaulted_Templated_NeedyTemplate8<Template_Ref<Movable> > d1;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Ref<S_Movable> > d2;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Ref<W_Movable> > d3;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Ref<WS_Movable> > d4;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Ref<SW_Movable> > d5;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Ref<SWS_Movable> > d6;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Ref<NonMovable> > d7;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Ref<S_NonMovable> > d8;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Ref<W_NonMovable> > d9;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Ref<WS_NonMovable> > d10;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Ref<SW_NonMovable> > d11;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Ref<SWS_NonMovable> > d12;
+}
+
+/*
+  SpecializedNonMovable is a non-movable class which has an explicit specialization of NeedyTemplate
+  for it. Instantiations of NeedyTemplateN<SpecializedNonMovable> should be legal as the explicit
+  specialization isn't annotated with MOZ_NEEDS_MEMMOVABLE_TYPE.
+
+  However, as it is MOZ_NON_MEMMOVABLE, derived classes and members shouldn't be able to be used to
+  instantiate NeedyTemplate.
+*/
+
+struct MOZ_NON_MEMMOVABLE SpecializedNonMovable {}; // expected-note 8 {{'S_SpecializedNonMovable' is non-memmovable because of the MOZ_NON_MEMMOVABLE annotation on 'SpecializedNonMovable'}} expected-note 8 {{'Template_Inline<SpecializedNonMovable>' is non-memmovable because of the MOZ_NON_MEMMOVABLE annotation on 'SpecializedNonMovable'}}
+struct S_SpecializedNonMovable : SpecializedNonMovable {};
+
+// Specialize all of the NeedyTemplates with SpecializedNonMovable.
+template <>
+struct NeedyTemplate1<SpecializedNonMovable> {};
+template <>
+struct NeedyTemplate2<SpecializedNonMovable> {};
+template <>
+struct NeedyTemplate3<SpecializedNonMovable> {};
+template <>
+struct NeedyTemplate4<SpecializedNonMovable> {};
+template <>
+struct NeedyTemplate5<SpecializedNonMovable> {};
+template <>
+struct NeedyTemplate6<SpecializedNonMovable> {};
+template <>
+struct NeedyTemplate7<SpecializedNonMovable> {};
+template <>
+struct NeedyTemplate8<SpecializedNonMovable> {};
+
+void specialization() {
+  /*
+    SpecializedNonMovable has a specialization for every variant of NeedyTemplate,
+    so these templates are valid, even though SpecializedNonMovable isn't
+    memmovable
+  */
+  NeedyTemplate1<SpecializedNonMovable> a1;
+  S_NeedyTemplate2<SpecializedNonMovable> a2;
+  W_NeedyTemplate3<SpecializedNonMovable> a3;
+  WS_NeedyTemplate4<SpecializedNonMovable> a4;
+  SW_NeedyTemplate5<SpecializedNonMovable> a5;
+  Defaulted_SW_NeedyTemplate6<SpecializedNonMovable> a6;
+  Defaulted_Templated_NeedyTemplate7<SpecializedNonMovable> a7;
+  W_Defaulted_Templated_NeedyTemplate8<SpecializedNonMovable> a8;
+
+  /*
+    These entries contain an element which is SpecializedNonMovable, and are non-movable
+    as there is no valid specialization, and their member is non-memmovable
+  */
+  NeedyTemplate1<Template_Inline<SpecializedNonMovable> > b1;  // expected-note {{instantiation of 'NeedyTemplate1<Template_Inline<SpecializedNonMovable> >' requested here}}
+  S_NeedyTemplate2<Template_Inline<SpecializedNonMovable> > b2;
+  W_NeedyTemplate3<Template_Inline<SpecializedNonMovable> > b3;
+  WS_NeedyTemplate4<Template_Inline<SpecializedNonMovable> > b4;
+  SW_NeedyTemplate5<Template_Inline<SpecializedNonMovable> > b5;
+  Defaulted_SW_NeedyTemplate6<Template_Inline<SpecializedNonMovable> > b6;
+  Defaulted_Templated_NeedyTemplate7<Template_Inline<SpecializedNonMovable> > b7;
+  W_Defaulted_Templated_NeedyTemplate8<Template_Inline<SpecializedNonMovable> > b8;
+
+  /*
+    The subclass of SpecializedNonMovable, is also non-memmovable,
+    as there is no valid specialization.
+  */
+  NeedyTemplate1<S_SpecializedNonMovable> c1; // expected-note {{instantiation of 'NeedyTemplate1<S_SpecializedNonMovable>' requested here}}
+  S_NeedyTemplate2<S_SpecializedNonMovable> c2;
+  W_NeedyTemplate3<S_SpecializedNonMovable> c3;
+  WS_NeedyTemplate4<S_SpecializedNonMovable> c4;
+  SW_NeedyTemplate5<S_SpecializedNonMovable> c5;
+  Defaulted_SW_NeedyTemplate6<S_SpecializedNonMovable> c6;
+  Defaulted_Templated_NeedyTemplate7<S_SpecializedNonMovable> c7;
+  W_Defaulted_Templated_NeedyTemplate8<S_SpecializedNonMovable> c8;
+}
--- a/build/clang-plugin/tests/TestStackClass.cpp
+++ b/build/clang-plugin/tests/TestStackClass.cpp
@@ -13,38 +13,38 @@ struct MOZ_STACK_CLASS TemplateClass {
 };
 
 void gobble(void *) { }
 
 void misuseStackClass(int len) {
   Stack valid;
   Stack alsoValid[2];
   static Stack notValid; // expected-error {{variable of type 'Stack' only valid on the stack}}
-  static Stack alsoNotValid[2]; // expected-error {{variable of type 'Stack [2]' only valid on the stack}}
+  static Stack alsoNotValid[2]; // expected-error {{variable of type 'Stack [2]' only valid on the stack}} expected-note {{'Stack [2]' is a stack type because it is an array of stack type 'Stack'}}
 
   gobble(&valid);
   gobble(&notValid);
   gobble(&alsoValid[0]);
 
   gobble(new Stack); // expected-error {{variable of type 'Stack' only valid on the stack}}
   gobble(new Stack[10]); // expected-error {{variable of type 'Stack' only valid on the stack}}
   gobble(new TemplateClass<int>); // expected-error {{variable of type 'TemplateClass<int>' only valid on the stack}}
   gobble(len <= 5 ? &valid : new Stack); // expected-error {{variable of type 'Stack' only valid on the stack}}
 
   char buffer[sizeof(Stack)];
   gobble(new (buffer) Stack);
 }
 
 Stack notValid; // expected-error {{variable of type 'Stack' only valid on the stack}}
 struct RandomClass {
-  Stack nonstaticMember; // expected-note {{'RandomClass' is a stack class because member 'nonstaticMember' is a stack class 'Stack'}}
+  Stack nonstaticMember; // expected-note {{'RandomClass' is a stack type because member 'nonstaticMember' is a stack type 'Stack'}}
   static Stack staticMember; // expected-error {{variable of type 'Stack' only valid on the stack}}
 };
 struct MOZ_STACK_CLASS RandomStackClass {
   Stack nonstaticMember;
   static Stack staticMember; // expected-error {{variable of type 'Stack' only valid on the stack}}
 };
 
-struct BadInherit : Stack {}; // expected-note {{'BadInherit' is a stack class because it inherits from a stack class 'Stack'}}
+struct BadInherit : Stack {}; // expected-note {{'BadInherit' is a stack type because it inherits from a stack type 'Stack'}}
 struct MOZ_STACK_CLASS GoodInherit : Stack {};
 
 BadInherit moreInvalid; // expected-error {{variable of type 'BadInherit' only valid on the stack}}
 RandomClass evenMoreInvalid; // expected-error {{variable of type 'RandomClass' only valid on the stack}}
--- a/build/clang-plugin/tests/moz.build
+++ b/build/clang-plugin/tests/moz.build
@@ -4,22 +4,25 @@
 # 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/.
 
 SOURCES += [
     'TestBadImplicitConversionCtor.cpp',
     'TestCustomHeap.cpp',
     'TestExplicitOperatorBool.cpp',
     'TestGlobalClass.cpp',
+    'TestMultipleAnnotations.cpp',
     'TestMustOverride.cpp',
     'TestMustUse.cpp',
     'TestNANTestingExpr.cpp',
     'TestNANTestingExprC.c',
+    'TestNeedsNoVTableType.cpp',
     'TestNoAddRefReleaseOnReturn.cpp',
     'TestNoArithmeticExprInArgument.cpp',
     'TestNonHeapClass.cpp',
+    'TestNonMemMovable.cpp',
     'TestNoRefcountedInsideLambdas.cpp',
     'TestStackClass.cpp',
     'TestTrivialCtorDtor.cpp',
 ]
 
 DISABLE_STL_WRAPPING = True
 NO_VISIBILITY_FLAGS = True
--- a/build/valgrind/mach_commands.py
+++ b/build/valgrind/mach_commands.py
@@ -104,16 +104,17 @@ class MachCommands(MachCommandBase):
                 '--smc-check=all-non-file',
                 '--vex-iropt-register-updates=allregs-at-mem-access',
                 '--gen-suppressions=all',
                 '--num-callers=36',
                 '--leak-check=full',
                 '--show-possibly-lost=no',
                 '--track-origins=yes',
                 '--trace-children=yes',
+                '-v',  # Enable verbosity to get the list of used suppressions
             ]
 
             for s in suppressions:
                 valgrind_args.append('--suppressions=' + s)
 
             supps_dir = os.path.join(build_dir, 'valgrind')
             supps_file1 = os.path.join(supps_dir, 'cross-architecture.sup')
             valgrind_args.append('--suppressions=' + supps_file1)
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -1705,30 +1705,32 @@ nsDocShell::LoadStream(nsIInputStream* a
     // For now, just use a bogus protocol called "internal"
     rv = uri->SetSpec(NS_LITERAL_CSTRING("internal:load-stream"));
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
   uint32_t loadType = LOAD_NORMAL;
+  nsCOMPtr<nsIPrincipal> requestingPrincipal;
   if (aLoadInfo) {
     nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal;
     (void)aLoadInfo->GetLoadType(&lt);
     // Get the appropriate LoadType from nsIDocShellLoadInfo type
     loadType = ConvertDocShellLoadInfoToLoadType(lt);
+  
+    nsCOMPtr<nsISupports> owner;
+    aLoadInfo->GetOwner(getter_AddRefs(owner));
+    requestingPrincipal = do_QueryInterface(owner);
   }
 
   NS_ENSURE_SUCCESS(Stop(nsIWebNavigation::STOP_NETWORK), NS_ERROR_FAILURE);
 
   mLoadType = loadType;
 
-  nsCOMPtr<nsISupports> owner;
-  aLoadInfo->GetOwner(getter_AddRefs(owner));
-  nsCOMPtr<nsIPrincipal> requestingPrincipal = do_QueryInterface(owner);
   if (!requestingPrincipal) {
     requestingPrincipal = nsContentUtils::GetSystemPrincipal();
   }
 
   // build up a channel for this stream.
   nsCOMPtr<nsIChannel> channel;
   nsresult rv = NS_NewInputStreamChannel(getter_AddRefs(channel),
                                          uri,
@@ -2948,139 +2950,26 @@ nsDocShell::GetRecordProfileTimelineMark
 {
   *aValue = IsObserved();
   return NS_OK;
 }
 
 nsresult
 nsDocShell::PopProfileTimelineMarkers(
     JSContext* aCx,
-    JS::MutableHandle<JS::Value> aProfileTimelineMarkers)
-{
-  // Looping over all markers gathered so far at the docShell level, whenever a
-  // START marker is found, look for the corresponding END marker and build a
-  // {name,start,end} JS object.
-  // Paint markers are different because paint is handled at root docShell level
-  // in the information that a paint was done is then stored at each sub
-  // docShell level but we can only be sure that a paint did happen in a
-  // docShell if an Layer marker type was recorded too.
-
-  nsTArray<mozilla::dom::ProfileTimelineMarker> profileTimelineMarkers;
-  SequenceRooter<mozilla::dom::ProfileTimelineMarker> rooter(
-    aCx, &profileTimelineMarkers);
-
-  if (!IsObserved()) {
-    if (!ToJSValue(aCx, profileTimelineMarkers, aProfileTimelineMarkers)) {
-      JS_ClearPendingException(aCx);
-      return NS_ERROR_UNEXPECTED;
-    }
-    return NS_OK;
-  }
-
-  nsTArray<UniquePtr<TimelineMarker>>& markersStore = mObserved.get()->mTimelineMarkers;
-
-  // If we see an unpaired START, we keep it around for the next call
-  // to PopProfileTimelineMarkers.  We store the kept START objects in
-  // this array.
-  nsTArray<UniquePtr<TimelineMarker>> keptMarkers;
-
-  for (uint32_t i = 0; i < markersStore.Length(); ++i) {
-    UniquePtr<TimelineMarker>& startPayload = markersStore[i];
-    const char* startMarkerName = startPayload->GetName();
-
-    bool hasSeenPaintedLayer = false;
-    bool isPaint = strcmp(startMarkerName, "Paint") == 0;
-
-    // If we are processing a Paint marker, we append information from
-    // all the embedded Layer markers to this array.
-    dom::Sequence<dom::ProfileTimelineLayerRect> layerRectangles;
-
-    // If this is a TRACING_TIMESTAMP marker, there's no corresponding "end"
-    // marker, as it's a single unit of time, not a duration, create the final
-    // marker here.
-    if (startPayload->GetMetaData() == TRACING_TIMESTAMP) {
-      mozilla::dom::ProfileTimelineMarker* marker =
-        profileTimelineMarkers.AppendElement();
-
-      marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
-      marker->mStart = startPayload->GetTime();
-      marker->mEnd = startPayload->GetTime();
-      marker->mStack = startPayload->GetStack();
-      startPayload->AddDetails(aCx, *marker);
-      continue;
-    }
-
-    if (startPayload->GetMetaData() == TRACING_INTERVAL_START) {
-      bool hasSeenEnd = false;
-
-      // DOM events can be nested, so we must take care when searching
-      // for the matching end.  It doesn't hurt to apply this logic to
-      // all event types.
-      uint32_t markerDepth = 0;
-
-      // The assumption is that the devtools timeline flushes markers frequently
-      // enough for the amount of markers to always be small enough that the
-      // nested for loop isn't going to be a performance problem.
-      for (uint32_t j = i + 1; j < markersStore.Length(); ++j) {
-        UniquePtr<TimelineMarker>& endPayload = markersStore[j];
-        const char* endMarkerName = endPayload->GetName();
-
-        // Look for Layer markers to stream out paint markers.
-        if (isPaint && strcmp(endMarkerName, "Layer") == 0) {
-          hasSeenPaintedLayer = true;
-          endPayload->AddLayerRectangles(layerRectangles);
-        }
-
-        if (!startPayload->Equals(*endPayload)) {
-          continue;
-        }
-
-        // Pair start and end markers.
-        if (endPayload->GetMetaData() == TRACING_INTERVAL_START) {
-          ++markerDepth;
-        } else if (endPayload->GetMetaData() == TRACING_INTERVAL_END) {
-          if (markerDepth > 0) {
-            --markerDepth;
-          } else {
-            // But ignore paint start/end if no layer has been painted.
-            if (!isPaint || (isPaint && hasSeenPaintedLayer)) {
-              mozilla::dom::ProfileTimelineMarker* marker =
-                profileTimelineMarkers.AppendElement();
-
-              marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
-              marker->mStart = startPayload->GetTime();
-              marker->mEnd = endPayload->GetTime();
-              marker->mStack = startPayload->GetStack();
-              if (isPaint) {
-                marker->mRectangles.Construct(layerRectangles);
-              }
-              startPayload->AddDetails(aCx, *marker);
-              endPayload->AddDetails(aCx, *marker);
-            }
-
-            // We want the start to be dropped either way.
-            hasSeenEnd = true;
-
-            break;
-          }
-        }
-      }
-
-      // If we did not see the corresponding END, keep the START.
-      if (!hasSeenEnd) {
-        keptMarkers.AppendElement(Move(markersStore[i]));
-        markersStore.RemoveElementAt(i);
-        --i;
-      }
-    }
-  }
-
-  markersStore.SwapElements(keptMarkers);
-
-  if (!ToJSValue(aCx, profileTimelineMarkers, aProfileTimelineMarkers)) {
+    JS::MutableHandle<JS::Value> aOut)
+{
+  nsTArray<dom::ProfileTimelineMarker> store;
+  SequenceRooter<dom::ProfileTimelineMarker> rooter(aCx, &store);
+
+  if (IsObserved()) {
+    mObserved->PopMarkers(aCx, store);
+  }
+
+  if (!ToJSValue(aCx, store, aOut)) {
     JS_ClearPendingException(aCx);
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
 }
 
 nsresult
--- a/docshell/base/timeline/ObservedDocShell.cpp
+++ b/docshell/base/timeline/ObservedDocShell.cpp
@@ -29,9 +29,106 @@ ObservedDocShell::AddMarker(UniquePtr<Ti
 }
 
 void
 ObservedDocShell::ClearMarkers()
 {
   mTimelineMarkers.Clear();
 }
 
+void
+ObservedDocShell::PopMarkers(JSContext* aCx,
+                             nsTArray<dom::ProfileTimelineMarker>& aStore)
+{
+  // If we see an unpaired START, we keep it around for the next call
+  // to ObservedDocShell::PopMarkers. We store the kept START objects here.
+  nsTArray<UniquePtr<TimelineMarker>> keptStartMarkers;
+
+  for (uint32_t i = 0; i < mTimelineMarkers.Length(); ++i) {
+    UniquePtr<TimelineMarker>& startPayload = mTimelineMarkers[i];
+
+    // If this is a TRACING_TIMESTAMP marker, there's no corresponding END
+    // as it's a single unit of time, not a duration.
+    if (startPayload->GetMetaData() == TRACING_TIMESTAMP) {
+      dom::ProfileTimelineMarker* marker = aStore.AppendElement();
+      marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
+      marker->mStart = startPayload->GetTime();
+      marker->mEnd = startPayload->GetTime();
+      marker->mStack = startPayload->GetStack();
+      startPayload->AddDetails(aCx, *marker);
+      continue;
+    }
+
+    // Whenever a START marker is found, look for the corresponding END
+    // and build a {name,start,end} JS object.
+    if (startPayload->GetMetaData() == TRACING_INTERVAL_START) {
+      bool hasSeenEnd = false;
+
+      // "Paint" markers are different because painting is handled at root
+      // docshell level. The information that a paint was done is stored at
+      // sub-docshell level, but we can only be sure that a paint did actually
+      // happen in if a "Layer" marker was recorded too.
+      bool startIsPaintType = strcmp(startPayload->GetName(), "Paint") == 0;
+      bool hasSeenLayerType = false;
+
+      // If we are processing a "Paint" marker, we append information from
+      // all the embedded "Layer" markers to this array.
+      dom::Sequence<dom::ProfileTimelineLayerRect> layerRectangles;
+
+      // DOM events can be nested, so we must take care when searching
+      // for the matching end. It doesn't hurt to apply this logic to
+      // all event types.
+      uint32_t markerDepth = 0;
+
+      // The assumption is that the devtools timeline flushes markers frequently
+      // enough for the amount of markers to always be small enough that the
+      // nested for loop isn't going to be a performance problem.
+      for (uint32_t j = i + 1; j < mTimelineMarkers.Length(); ++j) {
+        UniquePtr<TimelineMarker>& endPayload = mTimelineMarkers[j];
+        bool endIsLayerType = strcmp(endPayload->GetName(), "Layer") == 0;
+
+        // Look for "Layer" markers to stream out "Paint" markers.
+        if (startIsPaintType && endIsLayerType) {
+          hasSeenLayerType = true;
+          endPayload->AddLayerRectangles(layerRectangles);
+        }
+        if (!startPayload->Equals(*endPayload)) {
+          continue;
+        }
+        if (endPayload->GetMetaData() == TRACING_INTERVAL_START) {
+          ++markerDepth;
+          continue;
+        }
+        if (endPayload->GetMetaData() == TRACING_INTERVAL_END) {
+          if (markerDepth > 0) {
+            --markerDepth;
+            continue;
+          }
+          if (!startIsPaintType || (startIsPaintType && hasSeenLayerType)) {
+            dom::ProfileTimelineMarker* marker = aStore.AppendElement();
+            marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
+            marker->mStart = startPayload->GetTime();
+            marker->mEnd = endPayload->GetTime();
+            marker->mStack = startPayload->GetStack();
+            if (hasSeenLayerType) {
+              marker->mRectangles.Construct(layerRectangles);
+            }
+            startPayload->AddDetails(aCx, *marker);
+            endPayload->AddDetails(aCx, *marker);
+          }
+          hasSeenEnd = true;
+          break;
+        }
+      }
+
+      // If we did not see the corresponding END, keep the START.
+      if (!hasSeenEnd) {
+        keptStartMarkers.AppendElement(Move(mTimelineMarkers[i]));
+        mTimelineMarkers.RemoveElementAt(i);
+        --i;
+      }
+    }
+  }
+
+  mTimelineMarkers.SwapElements(keptStartMarkers);
+}
+
 } // namespace mozilla
--- a/docshell/base/timeline/ObservedDocShell.h
+++ b/docshell/base/timeline/ObservedDocShell.h
@@ -10,34 +10,35 @@
 #include "GeckoProfiler.h"
 #include "nsTArray.h"
 #include "nsRefPtr.h"
 
 class nsDocShell;
 class TimelineMarker;
 
 namespace mozilla {
+namespace dom {
+struct ProfileTimelineMarker;
+}
 
 // # ObservedDocShell
 //
 // A wrapper around a docshell for which docshell-specific markers are
 // allowed to exist. See TimelineConsumers for register/unregister logic.
 class ObservedDocShell : public LinkedListElement<ObservedDocShell>
 {
 private:
   nsRefPtr<nsDocShell> mDocShell;
+  nsTArray<UniquePtr<TimelineMarker>> mTimelineMarkers;
 
 public:
-  // FIXME: make this private once all marker-specific logic has been
-  // moved out of nsDocShell.
-  nsTArray<UniquePtr<TimelineMarker>> mTimelineMarkers;
-
   explicit ObservedDocShell(nsDocShell* aDocShell);
   nsDocShell* operator*() const { return mDocShell.get(); }
 
   void AddMarker(const char* aName, TracingMetadata aMetaData);
   void AddMarker(UniquePtr<TimelineMarker>&& aMarker);
   void ClearMarkers();
+  void PopMarkers(JSContext* aCx, nsTArray<dom::ProfileTimelineMarker>& aStore);
 };
 
 } // namespace mozilla
 
 #endif /* ObservedDocShell_h_ */
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -2723,18 +2723,62 @@ Navigator::GetUserAgent(nsPIDOMWindow* a
 }
 
 #ifdef MOZ_EME
 already_AddRefed<Promise>
 Navigator::RequestMediaKeySystemAccess(const nsAString& aKeySystem,
                                        const Optional<Sequence<MediaKeySystemOptions>>& aOptions,
                                        ErrorResult& aRv)
 {
+  nsAutoCString logMsg;
+  logMsg.AppendPrintf("Navigator::RequestMediaKeySystemAccess(keySystem='%s' options=[",
+                      NS_ConvertUTF16toUTF8(aKeySystem).get());
+  if (aOptions.WasPassed()) {
+    const Sequence<MediaKeySystemOptions>& options = aOptions.Value();
+    for (size_t i = 0; i < options.Length(); i++) {
+      const MediaKeySystemOptions& op = options[i];
+      if (i > 0) {
+        logMsg.AppendLiteral(",");
+      }
+      logMsg.AppendLiteral("{");
+      logMsg.AppendPrintf("stateful='%s'",
+        MediaKeysRequirementValues::strings[(size_t)op.mStateful].value);
+
+      logMsg.AppendPrintf(", uniqueIdentifier='%s'",
+        MediaKeysRequirementValues::strings[(size_t)op.mUniqueidentifier].value);
+
+      if (!op.mAudioCapability.IsEmpty()) {
+        logMsg.AppendPrintf(", audioCapability='%s'",
+                            NS_ConvertUTF16toUTF8(op.mAudioCapability).get());
+      }
+      if (!op.mAudioType.IsEmpty()) {
+        logMsg.AppendPrintf(", audioType='%s'",
+          NS_ConvertUTF16toUTF8(op.mAudioType).get());
+      }
+      if (!op.mInitDataType.IsEmpty()) {
+        logMsg.AppendPrintf(", initDataType='%s'",
+          NS_ConvertUTF16toUTF8(op.mInitDataType).get());
+      }
+      if (!op.mVideoCapability.IsEmpty()) {
+        logMsg.AppendPrintf(", videoCapability='%s'",
+          NS_ConvertUTF16toUTF8(op.mVideoCapability).get());
+      }
+      if (!op.mVideoType.IsEmpty()) {
+        logMsg.AppendPrintf(", videoType='%s'",
+          NS_ConvertUTF16toUTF8(op.mVideoType).get());
+      }
+      logMsg.AppendLiteral("}");
+    }
+  }
+  logMsg.AppendPrintf("])");
+  EME_LOG(logMsg.get());
+
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
-  nsRefPtr<DetailedPromise> promise = DetailedPromise::Create(go, aRv);
+  nsRefPtr<DetailedPromise> promise = DetailedPromise::Create(go, aRv,
+    NS_LITERAL_CSTRING("navigator.requestMediaKeySystemAccess"));
   if (aRv.Failed()) {
     return nullptr;
   }
 
   if (!mMediaKeySystemAccessManager) {
     mMediaKeySystemAccessManager = new MediaKeySystemAccessManager(mWindow);
   }
 
--- a/dom/base/nsMimeTypeArray.cpp
+++ b/dom/base/nsMimeTypeArray.cpp
@@ -4,21 +4,23 @@
  * 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 "nsMimeTypeArray.h"
 
 #include "mozilla/dom/MimeTypeArrayBinding.h"
 #include "mozilla/dom/MimeTypeBinding.h"
 #include "nsIDOMNavigator.h"
+#include "nsPIDOMWindow.h"
 #include "nsPluginArray.h"
 #include "nsIMIMEService.h"
 #include "nsIMIMEInfo.h"
 #include "Navigator.h"
 #include "nsServiceManagerUtils.h"
+#include "nsPluginTags.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsMimeTypeArray)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsMimeTypeArray)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsMimeTypeArray)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
@@ -211,29 +213,32 @@ nsMimeTypeArray::EnsurePluginMimeTypes()
   pluginArray->GetMimeTypes(mMimeTypes);
 }
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsMimeType, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsMimeType, Release)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsMimeType, mWindow, mPluginElement)
 
-nsMimeType::nsMimeType(nsPIDOMWindow* aWindow, nsPluginElement* aPluginElement,
-                       uint32_t aPluginTagMimeIndex, const nsAString& aType)
+nsMimeType::nsMimeType(nsPIDOMWindow* aWindow,
+                       nsPluginElement* aPluginElement,
+                       const nsAString& aType,
+                       const nsAString& aDescription,
+                       const nsAString& aExtension)
   : mWindow(aWindow),
     mPluginElement(aPluginElement),
-    mPluginTagMimeIndex(aPluginTagMimeIndex),
-    mType(aType)
+    mType(aType),
+    mDescription(aDescription),
+    mExtension(aExtension)
 {
 }
 
 nsMimeType::nsMimeType(nsPIDOMWindow* aWindow, const nsAString& aType)
   : mWindow(aWindow),
     mPluginElement(nullptr),
-    mPluginTagMimeIndex(0),
     mType(aType)
 {
 }
 
 nsMimeType::~nsMimeType()
 {
 }
 
@@ -246,41 +251,31 @@ nsMimeType::GetParentObject() const
 
 JSObject*
 nsMimeType::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return MimeTypeBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
-nsMimeType::GetDescription(nsString& retval) const
+nsMimeType::GetDescription(nsString& aRetval) const
 {
-  retval.Truncate();
-
-  if (mPluginElement) {
-    CopyUTF8toUTF16(mPluginElement->PluginTag()->
-                    mMimeDescriptions[mPluginTagMimeIndex], retval);
-  }
+  aRetval = mDescription;
 }
 
 nsPluginElement*
 nsMimeType::GetEnabledPlugin() const
 {
   return (mPluginElement && mPluginElement->PluginTag()->IsEnabled()) ?
     mPluginElement : nullptr;
 }
 
 void
-nsMimeType::GetSuffixes(nsString& retval) const
+nsMimeType::GetSuffixes(nsString& aRetval) const
 {
-  retval.Truncate();
-
-  if (mPluginElement) {
-    CopyUTF8toUTF16(mPluginElement->PluginTag()->
-                    mExtensions[mPluginTagMimeIndex], retval);
-  }
+  aRetval = mExtension;
 }
 
 void
 nsMimeType::GetType(nsString& aRetval) const
 {
   aRetval = mType;
 }
--- a/dom/base/nsMimeTypeArray.h
+++ b/dom/base/nsMimeTypeArray.h
@@ -53,18 +53,21 @@ protected:
 };
 
 class nsMimeType final : public nsWrapperCache
 {
 public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsMimeType)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(nsMimeType)
 
-  nsMimeType(nsPIDOMWindow* aWindow, nsPluginElement* aPluginElement,
-             uint32_t aPluginTagMimeIndex, const nsAString& aMimeType);
+  nsMimeType(nsPIDOMWindow* aWindow,
+             nsPluginElement* aPluginElement,
+             const nsAString& aType,
+             const nsAString& aDescription,
+             const nsAString& aExtension);
   nsMimeType(nsPIDOMWindow* aWindow, const nsAString& aMimeType);
   nsPIDOMWindow* GetParentObject() const;
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   const nsString& Type() const
   {
     return mType;
   }
@@ -80,13 +83,14 @@ protected:
 
   nsCOMPtr<nsPIDOMWindow> mWindow;
 
   // Strong reference to the active plugin, if any. Note that this
   // creates an explicit reference cycle through the plugin element's
   // mimetype array. We rely on the cycle collector to break this
   // cycle.
   nsRefPtr<nsPluginElement> mPluginElement;
-  uint32_t mPluginTagMimeIndex;
   nsString mType;
+  nsString mDescription;
+  nsString mExtension;
 };
 
 #endif /* nsMimeTypeArray_h___ */
--- a/dom/base/nsPluginArray.cpp
+++ b/dom/base/nsPluginArray.cpp
@@ -125,17 +125,17 @@ nsPluginArray::Refresh(bool aReloadDocum
   if(!AllowPlugins() || !pluginHost) {
     return;
   }
 
   // NS_ERROR_PLUGINS_PLUGINSNOTCHANGED on reloading plugins indicates
   // that plugins did not change and was not reloaded
   if (pluginHost->ReloadPlugins() ==
       NS_ERROR_PLUGINS_PLUGINSNOTCHANGED) {
-    nsTArray<nsRefPtr<nsPluginTag> > newPluginTags;
+    nsTArray<nsCOMPtr<nsIInternalPluginTag> > newPluginTags;
     pluginHost->GetPlugins(newPluginTags);
 
     // Check if the number of plugins we know about are different from
     // the number of plugin tags the plugin host knows about. If the
     // lengths are different, we refresh. This is safe because we're
     // notified for every plugin enabling/disabling event that
     // happens, and therefore the lengths will be in sync only when
     // the both arrays contain the same plugin tags (though as
@@ -274,41 +274,40 @@ nsPluginArray::AllowPlugins() const
   return docShell && docShell->PluginsAllowedInCurrentDoc();
 }
 
 static bool
 operator<(const nsRefPtr<nsPluginElement>& lhs,
           const nsRefPtr<nsPluginElement>& rhs)
 {
   // Sort plugins alphabetically by name.
-  return lhs->PluginTag()->mName < rhs->PluginTag()->mName;
+  return lhs->PluginTag()->Name() < rhs->PluginTag()->Name();
 }
 
 void
 nsPluginArray::EnsurePlugins()
 {
   if (!mPlugins.IsEmpty()) {
     // We already have an array of plugin elements.
     return;
   }
 
   nsRefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
   if (!pluginHost) {
     // We have no plugin host.
     return;
   }
 
-  nsTArray<nsRefPtr<nsPluginTag> > pluginTags;
+  nsTArray<nsCOMPtr<nsIInternalPluginTag> > pluginTags;
   pluginHost->GetPlugins(pluginTags);
 
   // need to wrap each of these with a nsPluginElement, which is
   // scriptable.
   for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
-    nsPluginTag* pluginTag = pluginTags[i];
-    mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTag));
+    mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
   }
 
   // Alphabetize the enumeration order of non-hidden plugins to reduce
   // fingerprintable entropy based on plugins' installation file times.
   mPlugins.Sort();
 }
 
 // nsPluginElement implementation.
@@ -318,17 +317,17 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPlugi
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPluginElement)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginElement, mWindow, mMimeTypes)
 
 nsPluginElement::nsPluginElement(nsPIDOMWindow* aWindow,
-                                 nsPluginTag* aPluginTag)
+                                 nsIInternalPluginTag* aPluginTag)
   : mWindow(aWindow),
     mPluginTag(aPluginTag)
 {
 }
 
 nsPluginElement::~nsPluginElement()
 {
 }
@@ -344,35 +343,35 @@ JSObject*
 nsPluginElement::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PluginBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 nsPluginElement::GetDescription(nsString& retval) const
 {
-  CopyUTF8toUTF16(mPluginTag->mDescription, retval);
+  CopyUTF8toUTF16(mPluginTag->Description(), retval);
 }
 
 void
 nsPluginElement::GetFilename(nsString& retval) const
 {
-  CopyUTF8toUTF16(mPluginTag->mFileName, retval);
+  CopyUTF8toUTF16(mPluginTag->FileName(), retval);
 }
 
 void
 nsPluginElement::GetVersion(nsString& retval) const
 {
-  CopyUTF8toUTF16(mPluginTag->mVersion, retval);
+  CopyUTF8toUTF16(mPluginTag->Version(), retval);
 }
 
 void
 nsPluginElement::GetName(nsString& retval) const
 {
-  CopyUTF8toUTF16(mPluginTag->mName, retval);
+  CopyUTF8toUTF16(mPluginTag->Name(), retval);
 }
 
 nsMimeType*
 nsPluginElement::Item(uint32_t aIndex)
 {
   EnsurePluginMimeTypes();
 
   return mMimeTypes.SafeElementAt(aIndex);
@@ -447,13 +446,23 @@ nsPluginElement::MimeTypes()
 
 void
 nsPluginElement::EnsurePluginMimeTypes()
 {
   if (!mMimeTypes.IsEmpty()) {
     return;
   }
 
-  for (uint32_t i = 0; i < mPluginTag->mMimeTypes.Length(); ++i) {
-    NS_ConvertUTF8toUTF16 type(mPluginTag->mMimeTypes[i]);
-    mMimeTypes.AppendElement(new nsMimeType(mWindow, this, i, type));
+  if (mPluginTag->MimeTypes().Length() != mPluginTag->MimeDescriptions().Length() ||
+      mPluginTag->MimeTypes().Length() != mPluginTag->Extensions().Length()) {
+    MOZ_ASSERT(false, "mime type arrays expected to be the same length");
+    return;
+  }
+
+  for (uint32_t i = 0; i < mPluginTag->MimeTypes().Length(); ++i) {
+    NS_ConvertUTF8toUTF16 type(mPluginTag->MimeTypes()[i]);
+    NS_ConvertUTF8toUTF16 description(mPluginTag->MimeDescriptions()[i]);
+    NS_ConvertUTF8toUTF16 extension(mPluginTag->Extensions()[i]);
+
+    mMimeTypes.AppendElement(new nsMimeType(mWindow, this, type, description,
+                                            extension));
   }
 }
--- a/dom/base/nsPluginArray.h
+++ b/dom/base/nsPluginArray.h
@@ -6,21 +6,21 @@
 
 #ifndef nsPluginArray_h___
 #define nsPluginArray_h___
 
 #include "nsTArray.h"
 #include "nsWeakReference.h"
 #include "nsIObserver.h"
 #include "nsWrapperCache.h"
-#include "nsPluginTags.h"
 #include "nsPIDOMWindow.h"
 
 class nsPluginElement;
 class nsMimeType;
+class nsIInternalPluginTag;
 
 class nsPluginArray final : public nsIObserver,
                             public nsSupportsWeakReference,
                             public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsPluginArray,
@@ -65,22 +65,22 @@ private:
 
 class nsPluginElement final : public nsISupports,
                               public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsPluginElement)
 
-  nsPluginElement(nsPIDOMWindow* aWindow, nsPluginTag* aPluginTag);
+  nsPluginElement(nsPIDOMWindow* aWindow, nsIInternalPluginTag* aPluginTag);
 
   nsPIDOMWindow* GetParentObject() const;
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
-  nsPluginTag* PluginTag() const
+  nsIInternalPluginTag* PluginTag() const
   {
     return mPluginTag;
   }
 
   // Plugin WebIDL methods
 
   void GetDescription(nsString& retval) const;
   void GetFilename(nsString& retval) const;
@@ -97,13 +97,13 @@ public:
   nsTArray<nsRefPtr<nsMimeType> >& MimeTypes();
 
 protected:
   ~nsPluginElement();
 
   void EnsurePluginMimeTypes();
 
   nsCOMPtr<nsPIDOMWindow> mWindow;
-  nsRefPtr<nsPluginTag> mPluginTag;
+  nsCOMPtr<nsIInternalPluginTag> mPluginTag;
   nsTArray<nsRefPtr<nsMimeType> > mMimeTypes;
 };
 
 #endif /* nsPluginArray_h___ */
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -955,16 +955,35 @@ DOMInterfaces = {
 },
 
 'PushMessageData': {
     'headerFile': 'ServiceWorkerEvents.h',
     'nativeType': 'mozilla::dom::workers::PushMessageData',
     'workers': True
 },
 
+'PushManager': [{
+    'workers': False,
+    'headerFile': 'mozilla/dom/PushManager.h',
+    'nativeType': 'mozilla::dom::PushManager',
+}, {
+    'workers': True,
+    'headerFile': 'mozilla/dom/PushManager.h',
+    'nativeType': 'mozilla::dom::WorkerPushManager',
+}],
+
+'PushSubscription': [{
+    'workers': False,
+    'headerFile': 'mozilla/dom/PushManager.h',
+}, {
+    'workers': True,
+    'headerFile': 'mozilla/dom/PushManager.h',
+    'nativeType': 'mozilla::dom::WorkerPushSubscription',
+}],
+
 'Range': {
     'nativeType': 'nsRange',
     'binaryNames': {
         '__stringifier': 'ToString'
     }
 },
 
 'Rect': {
--- a/dom/broadcastchannel/BroadcastChannel.cpp
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -7,17 +7,16 @@
 #include "BroadcastChannel.h"
 #include "BroadcastChannelChild.h"
 #include "mozilla/dom/BroadcastChannelBinding.h"
 #include "mozilla/dom/Navigator.h"
 #include "mozilla/dom/StructuredCloneUtils.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundChild.h"
-#include "mozilla/Preferences.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 
 #include "nsIDocument.h"
 #include "nsISupportsPrimitives.h"
 
 #ifdef XP_WIN
 #undef PostMessage
@@ -294,60 +293,18 @@ public:
 
 private:
   ~BroadcastChannelFeature()
   {
     MOZ_COUNT_DTOR(BroadcastChannelFeature);
   }
 };
 
-class PrefEnabledRunnable final : public WorkerMainThreadRunnable
-{
-public:
-  explicit PrefEnabledRunnable(WorkerPrivate* aWorkerPrivate)
-    : WorkerMainThreadRunnable(aWorkerPrivate)
-    , mEnabled(false)
-  { }
-
-  bool MainThreadRun() override
-  {
-    AssertIsOnMainThread();
-    mEnabled = Preferences::GetBool("dom.broadcastChannel.enabled", false);
-    return true;
-  }
-
-  bool IsEnabled() const
-  {
-    return mEnabled;
-  }
-
-private:
-  bool mEnabled;
-};