Merge m-c to m-i
authorPhil Ringnalda <philringnalda@gmail.com>
Sun, 13 Sep 2015 12:07:56 -0700
changeset 294853 503da58c2e6c3fe1d1b2dc62ce1e73f74a118efc
parent 294852 e617c037515ba7c98b1d549832a3fc8decb52663 (current diff)
parent 294839 9ed17db42e3e46f1c712e4dffd62d54e915e0fac (diff)
child 294854 faaafe8c3d1e6be8611b7f56297914a92ca1478d
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to m-i
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="2a7461a2c839280440239214552d3c2cf79b5e2d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4d9b996be4b1935651057d0651461c1a36d98a18"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="2a7461a2c839280440239214552d3c2cf79b5e2d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4d9b996be4b1935651057d0651461c1a36d98a18"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="2a7461a2c839280440239214552d3c2cf79b5e2d"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4d9b996be4b1935651057d0651461c1a36d98a18"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="27eb2f04e149fc2c9976d881b1b5984bbe7ee089"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="067c08fb3e5744b42b68d1f861245f7d507109bc"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="2a7461a2c839280440239214552d3c2cf79b5e2d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4d9b996be4b1935651057d0651461c1a36d98a18"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="2a7461a2c839280440239214552d3c2cf79b5e2d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4d9b996be4b1935651057d0651461c1a36d98a18"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="2a7461a2c839280440239214552d3c2cf79b5e2d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4d9b996be4b1935651057d0651461c1a36d98a18"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="2a7461a2c839280440239214552d3c2cf79b5e2d"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4d9b996be4b1935651057d0651461c1a36d98a18"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="27eb2f04e149fc2c9976d881b1b5984bbe7ee089"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="067c08fb3e5744b42b68d1f861245f7d507109bc"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="2a7461a2c839280440239214552d3c2cf79b5e2d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4d9b996be4b1935651057d0651461c1a36d98a18"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "2a7461a2c839280440239214552d3c2cf79b5e2d", 
+        "git_revision": "4d9b996be4b1935651057d0651461c1a36d98a18", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "229ccafd68fd865b99244b220ba2d1d13ff62621", 
+    "revision": "616fa9b10c5e498a185d34f6da3cbd69fe2d0a24", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="2a7461a2c839280440239214552d3c2cf79b5e2d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4d9b996be4b1935651057d0651461c1a36d98a18"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="2a7461a2c839280440239214552d3c2cf79b5e2d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4d9b996be4b1935651057d0651461c1a36d98a18"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
--- a/browser/components/loop/content/js/conversationViews.js
+++ b/browser/components/loop/content/js/conversationViews.js
@@ -649,28 +649,28 @@ loop.conversationViews = (function(mozL1
     /**
      * 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;
+      return !this.state.localSrcMediaElement && !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 &&
+      return !!(!this.state.remoteSrcMediaElement &&
                 !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.
@@ -697,24 +697,24 @@ loop.conversationViews = (function(mozL1
         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, 
+            localSrcMediaElement: this.state.localSrcMediaElement, 
             localVideoMuted: !this.props.video.enabled, 
             matchMedia: this.state.matchMedia || window.matchMedia.bind(window), 
             remotePosterUrl: this.props.remotePosterUrl, 
-            remoteSrcVideoObject: this.state.remoteSrcVideoObject, 
+            remoteSrcMediaElement: this.state.remoteSrcMediaElement, 
             renderRemoteVideo: this.shouldRenderRemoteVideo(), 
+            screenShareMediaElement: this.state.screenShareMediaElement, 
             screenSharePosterUrl: null, 
-            screenShareVideoObject: this.state.screenShareVideoObject, 
             showContextRoomName: false, 
             useDesktopPaths: true}, 
             React.createElement(loop.shared.views.ConversationToolbar, {
               audio: this.props.audio, 
               dispatcher: this.props.dispatcher, 
               hangup: this.hangup, 
               mozLoop: this.props.mozLoop, 
               publishStream: this.publishStream, 
@@ -817,17 +817,17 @@ loop.conversationViews = (function(mozL1
         }
         case CALL_STATES.ONGOING: {
           return (React.createElement(OngoingConversationView, {
             audio: { enabled: !this.state.audioMuted, visible: true}, 
             conversationStore: this.getStore(), 
             dispatcher: this.props.dispatcher, 
             mediaConnected: this.state.mediaConnected, 
             mozLoop: this.props.mozLoop, 
-            remoteSrcVideoObject: this.state.remoteSrcVideoObject, 
+            remoteSrcMediaElement: this.state.remoteSrcMediaElement, 
             remoteVideoEnabled: this.state.remoteVideoEnabled, 
             video: { enabled: !this.state.videoMuted, visible: true}})
           );
         }
         case CALL_STATES.FINISHED: {
           this.play("terminated");
 
           // When conversation ended we either display a feedback form or
--- a/browser/components/loop/content/js/conversationViews.jsx
+++ b/browser/components/loop/content/js/conversationViews.jsx
@@ -649,28 +649,28 @@ loop.conversationViews = (function(mozL1
     /**
      * 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;
+      return !this.state.localSrcMediaElement && !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 &&
+      return !!(!this.state.remoteSrcMediaElement &&
                 !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.
@@ -697,24 +697,24 @@ loop.conversationViews = (function(mozL1
         <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}
+            localSrcMediaElement={this.state.localSrcMediaElement}
             localVideoMuted={!this.props.video.enabled}
             matchMedia={this.state.matchMedia || window.matchMedia.bind(window)}
             remotePosterUrl={this.props.remotePosterUrl}
-            remoteSrcVideoObject={this.state.remoteSrcVideoObject}
+            remoteSrcMediaElement={this.state.remoteSrcMediaElement}
             renderRemoteVideo={this.shouldRenderRemoteVideo()}
+            screenShareMediaElement={this.state.screenShareMediaElement}
             screenSharePosterUrl={null}
-            screenShareVideoObject={this.state.screenShareVideoObject}
             showContextRoomName={false}
             useDesktopPaths={true}>
             <loop.shared.views.ConversationToolbar
               audio={this.props.audio}
               dispatcher={this.props.dispatcher}
               hangup={this.hangup}
               mozLoop={this.props.mozLoop}
               publishStream={this.publishStream}
@@ -817,17 +817,17 @@ loop.conversationViews = (function(mozL1
         }
         case CALL_STATES.ONGOING: {
           return (<OngoingConversationView
             audio={{ enabled: !this.state.audioMuted, visible: true }}
             conversationStore={this.getStore()}
             dispatcher={this.props.dispatcher}
             mediaConnected={this.state.mediaConnected}
             mozLoop={this.props.mozLoop}
-            remoteSrcVideoObject={this.state.remoteSrcVideoObject}
+            remoteSrcMediaElement={this.state.remoteSrcMediaElement}
             remoteVideoEnabled={this.state.remoteVideoEnabled}
             video={{ enabled: !this.state.videoMuted, visible: true }} />
           );
         }
         case CALL_STATES.FINISHED: {
           this.play("terminated");
 
           // When conversation ended we either display a feedback form or
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -702,29 +702,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
      */
     _isLocalLoading: function () {
       return this.state.roomState === ROOM_STATES.MEDIA_WAIT &&
-             !this.state.localSrcVideoObject;
+             !this.state.localSrcMediaElement;
     },
 
     /**
      * 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.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
-                !this.state.remoteSrcVideoObject &&
+                !this.state.remoteSrcMediaElement &&
                 !this.state.mediaConnected);
     },
 
     handleAddContextClick: function() {
       this.setState({ showEditContext: true });
     },
 
     handleEditContextClick: function() {
@@ -796,24 +796,24 @@ loop.roomViews = (function(mozL10n) {
             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, 
+                localSrcMediaElement: this.state.localSrcMediaElement, 
                 localVideoMuted: this.state.videoMuted, 
                 matchMedia: this.state.matchMedia || window.matchMedia.bind(window), 
                 remotePosterUrl: this.props.remotePosterUrl, 
-                remoteSrcVideoObject: this.state.remoteSrcVideoObject, 
+                remoteSrcMediaElement: this.state.remoteSrcMediaElement, 
                 renderRemoteVideo: this.shouldRenderRemoteVideo(), 
+                screenShareMediaElement: this.state.screenShareMediaElement, 
                 screenSharePosterUrl: null, 
-                screenShareVideoObject: this.state.screenShareVideoObject, 
                 showContextRoomName: false, 
                 useDesktopPaths: true}, 
                 React.createElement(sharedViews.ConversationToolbar, {
                   audio: {enabled: !this.state.audioMuted, visible: true}, 
                   dispatcher: this.props.dispatcher, 
                   hangup: this.leaveRoom, 
                   mozLoop: this.props.mozLoop, 
                   publishStream: this.publishStream, 
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -702,29 +702,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
      */
     _isLocalLoading: function () {
       return this.state.roomState === ROOM_STATES.MEDIA_WAIT &&
-             !this.state.localSrcVideoObject;
+             !this.state.localSrcMediaElement;
     },
 
     /**
      * 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.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
-                !this.state.remoteSrcVideoObject &&
+                !this.state.remoteSrcMediaElement &&
                 !this.state.mediaConnected);
     },
 
     handleAddContextClick: function() {
       this.setState({ showEditContext: true });
     },
 
     handleEditContextClick: function() {
@@ -796,24 +796,24 @@ loop.roomViews = (function(mozL10n) {
             <div className="room-conversation-wrapper desktop-room-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}
+                localSrcMediaElement={this.state.localSrcMediaElement}
                 localVideoMuted={this.state.videoMuted}
                 matchMedia={this.state.matchMedia || window.matchMedia.bind(window)}
                 remotePosterUrl={this.props.remotePosterUrl}
-                remoteSrcVideoObject={this.state.remoteSrcVideoObject}
+                remoteSrcMediaElement={this.state.remoteSrcMediaElement}
                 renderRemoteVideo={this.shouldRenderRemoteVideo()}
+                screenShareMediaElement={this.state.screenShareMediaElement}
                 screenSharePosterUrl={null}
-                screenShareVideoObject={this.state.screenShareVideoObject}
                 showContextRoomName={false}
                 useDesktopPaths={true}>
                 <sharedViews.ConversationToolbar
                   audio={{enabled: !this.state.audioMuted, visible: true}}
                   dispatcher={this.props.dispatcher}
                   hangup={this.leaveRoom}
                   mozLoop={this.props.mozLoop}
                   publishStream={this.publishStream}
--- a/browser/components/loop/content/shared/js/actions.js
+++ b/browser/components/loop/content/shared/js/actions.js
@@ -228,17 +228,17 @@ loop.shared.actions = (function() {
     }),
 
     /**
      * A stream from local or remote media has been created.
      */
     MediaStreamCreated: Action.define("mediaStreamCreated", {
       hasVideo: Boolean,
       isLocal: Boolean,
-      srcVideoObject: Object
+      srcMediaElement: Object
     }),
 
     /**
      * A stream from local or remote media has been destroyed.
      */
     MediaStreamDestroyed: Action.define("mediaStreamDestroyed", {
       isLocal: Boolean
     }),
@@ -289,17 +289,17 @@ loop.shared.actions = (function() {
 
     /**
      * Used to notify that a shared screen is being received (or not).
      *
      * XXX this should be split into multiple actions to make the code clearer.
      */
     ReceivingScreenShare: Action.define("receivingScreenShare", {
       receiving: Boolean
-      // srcVideoObject: Object (only present if receiving is true)
+      // srcMediaElement: Object (only present if receiving is true)
     }),
 
     /**
      * Creates a new room.
      * XXX: should move to some roomActions module - refs bug 1079284
      */
     CreateRoom: Action.define("createRoom", {
       // The localized template to use to name the new room
--- a/browser/components/loop/content/shared/js/activeRoomStore.js
+++ b/browser/components/loop/content/shared/js/activeRoomStore.js
@@ -101,25 +101,25 @@ loop.store.ActiveRoomStore = (function()
      * due to user choice, failure or other reason. It is a subset of
      * getInitialStoreState as some items (e.g. roomState, failureReason,
      * context information) can persist across room exit & re-entry.
      *
      * @type {Array}
      */
     _statesToResetOnLeave: [
       "audioMuted",
-      "localSrcVideoObject",
+      "localSrcMediaElement",
       "localVideoDimensions",
       "mediaConnected",
       "receivingScreenShare",
-      "remoteSrcVideoObject",
+      "remoteSrcMediaElement",
       "remoteVideoDimensions",
       "remoteVideoEnabled",
       "screenSharingState",
-      "screenShareVideoObject",
+      "screenShareMediaElement",
       "videoMuted"
     ],
 
     /**
      * Returns initial state data for this active room.
      *
      * When adding states, consider if _statesToResetOnLeave needs updating
      * as well.
@@ -635,42 +635,42 @@ loop.store.ActiveRoomStore = (function()
      * Handles a media stream being created. This may be a local or a remote stream.
      *
      * @param {sharedActions.MediaStreamCreated} actionData
      */
     mediaStreamCreated: function(actionData) {
       if (actionData.isLocal) {
         this.setStoreState({
           localVideoEnabled: actionData.hasVideo,
-          localSrcVideoObject: actionData.srcVideoObject
+          localSrcMediaElement: actionData.srcMediaElement
         });
         return;
       }
 
       this.setStoreState({
         remoteVideoEnabled: actionData.hasVideo,
-        remoteSrcVideoObject: actionData.srcVideoObject
+        remoteSrcMediaElement: actionData.srcMediaElement
       });
     },
 
     /**
      * Handles a media stream being destroyed. This may be a local or a remote stream.
      *
      * @param {sharedActions.MediaStreamDestroyed} actionData
      */
     mediaStreamDestroyed: function(actionData) {
       if (actionData.isLocal) {
         this.setStoreState({
-          localSrcVideoObject: null
+          localSrcMediaElement: null
         });
         return;
       }
 
       this.setStoreState({
-        remoteSrcVideoObject: null
+        remoteSrcMediaElement: null
       });
     },
 
     /**
      * Handles a remote stream having video enabled or disabled.
      *
      * @param {sharedActions.RemoteVideoStatus} actionData
      */
@@ -708,23 +708,23 @@ loop.store.ActiveRoomStore = (function()
           this.getStoreState().remoteVideoDimensions.screen) {
         // Remove the remote video dimensions for type screen as we're not
         // getting the share anymore.
         var newDimensions = _.extend(this.getStoreState().remoteVideoDimensions);
         delete newDimensions.screen;
         this.setStoreState({
           receivingScreenShare: actionData.receiving,
           remoteVideoDimensions: newDimensions,
-          screenShareVideoObject: null
+          screenShareMediaElement: null
         });
       } else {
         this.setStoreState({
           receivingScreenShare: actionData.receiving,
-          screenShareVideoObject: actionData.srcVideoObject ?
-                                  actionData.srcVideoObject : null
+          screenShareMediaElement: actionData.srcMediaElement ?
+                                  actionData.srcMediaElement : null
         });
       }
     },
 
     /**
      * Handles switching browser (aka tab) sharing to a new window. Should
      * only be used for browser sharing.
      *
@@ -824,17 +824,17 @@ loop.store.ActiveRoomStore = (function()
           return participant.owner;
         });
       }
 
       this.setStoreState({
         mediaConnected: false,
         participants: participants,
         roomState: ROOM_STATES.SESSION_CONNECTED,
-        remoteSrcVideoObject: null
+        remoteSrcMediaElement: null
       });
     },
 
     /**
      * Handles an SDK status update, forwarding it to the server.
      *
      * @param {sharedActions.ConnectionStatus} actionData
      */
--- a/browser/components/loop/content/shared/js/conversationStore.js
+++ b/browser/components/loop/content/shared/js/conversationStore.js
@@ -443,42 +443,42 @@ loop.store = loop.store || {};
      * Handles a media stream being created. This may be a local or a remote stream.
      *
      * @param {sharedActions.MediaStreamCreated} actionData
      */
     mediaStreamCreated: function(actionData) {
       if (actionData.isLocal) {
         this.setStoreState({
           localVideoEnabled: actionData.hasVideo,
-          localSrcVideoObject: actionData.srcVideoObject
+          localSrcMediaElement: actionData.srcMediaElement
         });
         return;
       }
 
       this.setStoreState({
         remoteVideoEnabled: actionData.hasVideo,
-        remoteSrcVideoObject: actionData.srcVideoObject
+        remoteSrcMediaElement: actionData.srcMediaElement
       });
     },
 
     /**
      * Handles a media stream being destroyed. This may be a local or a remote stream.
      *
      * @param {sharedActions.MediaStreamDestroyed} actionData
      */
     mediaStreamDestroyed: function(actionData) {
       if (actionData.isLocal) {
         this.setStoreState({
-          localSrcVideoObject: null
+          localSrcMediaElement: null
         });
         return;
       }
 
       this.setStoreState({
-        remoteSrcVideoObject: null
+        remoteSrcMediaElement: null
       });
     },
 
     /**
      * Handles a remote stream having video enabled or disabled.
      *
      * @param {sharedActions.RemoteVideoStatus} actionData
      */
--- a/browser/components/loop/content/shared/js/otSdkDriver.js
+++ b/browser/components/loop/content/shared/js/otSdkDriver.js
@@ -596,17 +596,17 @@ loop.OTSdkDriver = (function() {
       }
 
       sdkSubscriberObject.on("videoEnabled", this._onVideoEnabled.bind(this));
       sdkSubscriberObject.on("videoDisabled", this._onVideoDisabled.bind(this));
 
       this.dispatcher.dispatch(new sharedActions.MediaStreamCreated({
         hasVideo: sdkSubscriberObject.stream[STREAM_PROPERTIES.HAS_VIDEO],
         isLocal: false,
-        srcVideoObject: sdkSubscriberVideo
+        srcMediaElement: sdkSubscriberVideo
       }));
 
       this._subscribedRemoteStream = true;
       if (this._checkAllStreamsConnected()) {
         this._setTwoWayMediaStartTime(performance.now());
         this.dispatcher.dispatch(new sharedActions.MediaConnected());
       }
 
@@ -630,17 +630,17 @@ loop.OTSdkDriver = (function() {
 
       var sdkSubscriberVideo = subscriberVideo ? subscriberVideo :
         this._mockScreenShareEl.querySelector("video");
 
       // XXX no idea why this is necessary in addition to the dispatch in
       // _handleRemoteScreenShareCreated.  Maybe these should be separate
       // actions.  But even so, this shouldn't be necessary....
       this.dispatcher.dispatch(new sharedActions.ReceivingScreenShare({
-        receiving: true, srcVideoObject: sdkSubscriberVideo
+        receiving: true, srcMediaElement: sdkSubscriberVideo
       }));
 
     },
 
     /**
      * Once a remote stream has been subscribed to, this triggers the data
      * channel set-up routines. A data channel cannot be requested before this
      * time as the peer connection is not set up.
@@ -762,17 +762,17 @@ loop.OTSdkDriver = (function() {
       this._notifyMetricsEvent("Publisher.streamCreated");
 
       var sdkLocalVideo = this._mockPublisherEl.querySelector("video");
       var hasVideo = event.stream[STREAM_PROPERTIES.HAS_VIDEO];
 
       this.dispatcher.dispatch(new sharedActions.MediaStreamCreated({
         hasVideo: hasVideo,
         isLocal: true,
-        srcVideoObject: sdkLocalVideo
+        srcMediaElement: sdkLocalVideo
       }));
 
       // Only dispatch the video dimensions if we actually have video.
       if (hasVideo) {
         this.dispatcher.dispatch(new sharedActions.VideoDimensionsChanged({
           isLocal: true,
           videoType: event.stream.videoType,
           dimensions: event.stream[STREAM_PROPERTIES.VIDEO_DIMENSIONS]
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -1070,53 +1070,53 @@ loop.shared.views = (function(_, mozL10n
     }
   });
 
   /**
    * Renders a media element for display. This also handles displaying an avatar
    * instead of the video, and attaching a video stream to the video element.
    */
   var MediaView = React.createClass({displayName: "MediaView",
-    // srcVideoObject should be ok for a shallow comparison, so we are safe
+    // srcMediaElement should be ok for a shallow comparison, so we are safe
     // to use the pure render mixin here.
     mixins: [React.addons.PureRenderMixin],
 
     propTypes: {
       displayAvatar: React.PropTypes.bool.isRequired,
       isLoading: React.PropTypes.bool.isRequired,
       mediaType: React.PropTypes.string.isRequired,
       posterUrl: React.PropTypes.string,
       // Expecting "local" or "remote".
-      srcVideoObject: React.PropTypes.object
+      srcMediaElement: React.PropTypes.object
     },
 
     componentDidMount: function() {
       if (!this.props.displayAvatar) {
-        this.attachVideo(this.props.srcVideoObject);
+        this.attachVideo(this.props.srcMediaElement);
       }
     },
 
     componentDidUpdate: function() {
       if (!this.props.displayAvatar) {
-        this.attachVideo(this.props.srcVideoObject);
+        this.attachVideo(this.props.srcMediaElement);
       }
     },
 
     /**
      * Attaches a video stream from a donor video element to this component's
      * video element if the component is displaying one.
      *
-     * @param {Object} srcVideoObject The src video object to clone the stream
+     * @param {Object} srcMediaElement The src video object to clone the stream
      *                                from.
      *
      * XXX need to have a corresponding detachVideo or change this to syncVideo
      * to protect from leaks (bug 1171978)
      */
-    attachVideo: function(srcVideoObject) {
-      if (!srcVideoObject) {
+    attachVideo: function(srcMediaElement) {
+      if (!srcMediaElement) {
         // Not got anything to display.
         return;
       }
 
       var videoElement = this.getDOMNode();
 
       if (videoElement.tagName.toLowerCase() !== "video") {
         // Must be displaying the avatar view, so don't try and attach video.
@@ -1136,32 +1136,32 @@ loop.shared.views = (function(_, mozL10n
         attrName = "src";
       } else {
         console.error("Error attaching stream to element - no supported" +
                       "attribute found");
         return;
       }
 
       // If the object hasn't changed it, then don't reattach it.
-      if (videoElement[attrName] !== srcVideoObject[attrName]) {
-        videoElement[attrName] = srcVideoObject[attrName];
+      if (videoElement[attrName] !== srcMediaElement[attrName]) {
+        videoElement[attrName] = srcMediaElement[attrName];
       }
       videoElement.play();
     },
 
     render: function() {
       if (this.props.isLoading) {
         return React.createElement(LoadingView, null);
       }
 
       if (this.props.displayAvatar) {
         return React.createElement(AvatarView, null);
       }
 
-      if (!this.props.srcVideoObject && !this.props.posterUrl) {
+      if (!this.props.srcMediaElement && !this.props.posterUrl) {
         return React.createElement("div", {className: "no-video"});
       }
 
       var optionalPoster = {};
       if (this.props.posterUrl) {
         optionalPoster.poster = this.props.posterUrl;
       }
 
@@ -1187,26 +1187,26 @@ loop.shared.views = (function(_, mozL10n
       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,
+      localSrcMediaElement: 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,
+      remoteSrcMediaElement: React.PropTypes.object,
       renderRemoteVideo: React.PropTypes.bool.isRequired,
+      screenShareMediaElement: React.PropTypes.object,
       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;
       }
@@ -1250,17 +1250,17 @@ loop.shared.views = (function(_, mozL10n
 
     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})
+            srcMediaElement: this.props.localSrcMediaElement})
         )
       );
     },
 
     render: function() {
       var remoteStreamClasses = React.addons.classSet({
         "remote": true,
         "focus-stream": !this.props.displayScreenShare
@@ -1269,45 +1269,45 @@ loop.shared.views = (function(_, mozL10n
       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 ||
+        "showing-local-streams": this.props.localSrcMediaElement ||
           this.props.localPosterUrl,
-        "showing-remote-streams": this.props.remoteSrcVideoObject ||
+        "showing-remote-streams": this.props.remoteSrcMediaElement ||
           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}), 
+                srcMediaElement: this.props.remoteSrcMediaElement}), 
                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})
+                srcMediaElement: this.props.screenShareMediaElement})
             ), 
             React.createElement(loop.shared.views.chat.TextChatView, {
               dispatcher: this.props.dispatcher, 
               showRoomName: this.props.showContextRoomName, 
               useDesktopPaths: this.props.useDesktopPaths}), 
              this.state.localMediaAboslutelyPositioned ?
               null : this.renderLocalVideo()
           )
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -1070,53 +1070,53 @@ loop.shared.views = (function(_, mozL10n
     }
   });
 
   /**
    * Renders a media element for display. This also handles displaying an avatar
    * instead of the video, and attaching a video stream to the video element.
    */
   var MediaView = React.createClass({
-    // srcVideoObject should be ok for a shallow comparison, so we are safe
+    // srcMediaElement should be ok for a shallow comparison, so we are safe
     // to use the pure render mixin here.
     mixins: [React.addons.PureRenderMixin],
 
     propTypes: {
       displayAvatar: React.PropTypes.bool.isRequired,
       isLoading: React.PropTypes.bool.isRequired,
       mediaType: React.PropTypes.string.isRequired,
       posterUrl: React.PropTypes.string,
       // Expecting "local" or "remote".
-      srcVideoObject: React.PropTypes.object
+      srcMediaElement: React.PropTypes.object
     },
 
     componentDidMount: function() {
       if (!this.props.displayAvatar) {
-        this.attachVideo(this.props.srcVideoObject);
+        this.attachVideo(this.props.srcMediaElement);
       }
     },
 
     componentDidUpdate: function() {
       if (!this.props.displayAvatar) {
-        this.attachVideo(this.props.srcVideoObject);
+        this.attachVideo(this.props.srcMediaElement);
       }
     },
 
     /**
      * Attaches a video stream from a donor video element to this component's
      * video element if the component is displaying one.
      *
-     * @param {Object} srcVideoObject The src video object to clone the stream
+     * @param {Object} srcMediaElement The src video object to clone the stream
      *                                from.
      *
      * XXX need to have a corresponding detachVideo or change this to syncVideo
      * to protect from leaks (bug 1171978)
      */
-    attachVideo: function(srcVideoObject) {
-      if (!srcVideoObject) {
+    attachVideo: function(srcMediaElement) {
+      if (!srcMediaElement) {
         // Not got anything to display.
         return;
       }
 
       var videoElement = this.getDOMNode();
 
       if (videoElement.tagName.toLowerCase() !== "video") {
         // Must be displaying the avatar view, so don't try and attach video.
@@ -1136,32 +1136,32 @@ loop.shared.views = (function(_, mozL10n
         attrName = "src";
       } else {
         console.error("Error attaching stream to element - no supported" +
                       "attribute found");
         return;
       }
 
       // If the object hasn't changed it, then don't reattach it.
-      if (videoElement[attrName] !== srcVideoObject[attrName]) {
-        videoElement[attrName] = srcVideoObject[attrName];
+      if (videoElement[attrName] !== srcMediaElement[attrName]) {
+        videoElement[attrName] = srcMediaElement[attrName];
       }
       videoElement.play();
     },
 
     render: function() {
       if (this.props.isLoading) {
         return <LoadingView />;
       }
 
       if (this.props.displayAvatar) {
         return <AvatarView />;
       }
 
-      if (!this.props.srcVideoObject && !this.props.posterUrl) {
+      if (!this.props.srcMediaElement && !this.props.posterUrl) {
         return <div className="no-video"/>;
       }
 
       var optionalPoster = {};
       if (this.props.posterUrl) {
         optionalPoster.poster = this.props.posterUrl;
       }
 
@@ -1187,26 +1187,26 @@ loop.shared.views = (function(_, mozL10n
       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,
+      localSrcMediaElement: 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,
+      remoteSrcMediaElement: React.PropTypes.object,
       renderRemoteVideo: React.PropTypes.bool.isRequired,
+      screenShareMediaElement: React.PropTypes.object,
       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;
       }
@@ -1250,17 +1250,17 @@ loop.shared.views = (function(_, mozL10n
 
     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} />
+            srcMediaElement={this.props.localSrcMediaElement} />
         </div>
       );
     },
 
     render: function() {
       var remoteStreamClasses = React.addons.classSet({
         "remote": true,
         "focus-stream": !this.props.displayScreenShare
@@ -1269,45 +1269,45 @@ loop.shared.views = (function(_, mozL10n
       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 ||
+        "showing-local-streams": this.props.localSrcMediaElement ||
           this.props.localPosterUrl,
-        "showing-remote-streams": this.props.remoteSrcVideoObject ||
+        "showing-remote-streams": this.props.remoteSrcMediaElement ||
           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} />
+                srcMediaElement={this.props.remoteSrcMediaElement} />
               { 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} />
+                srcMediaElement={this.props.screenShareMediaElement} />
             </div>
             <loop.shared.views.chat.TextChatView
               dispatcher={this.props.dispatcher}
               showRoomName={this.props.showContextRoomName}
               useDesktopPaths={this.props.useDesktopPaths} />
             { this.state.localMediaAboslutelyPositioned ?
               null : this.renderLocalVideo() }
           </div>
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -508,42 +508,42 @@ loop.standaloneRoomViews = (function(moz
      * 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.roomState === ROOM_STATES.MEDIA_WAIT &&
-             !this.state.localSrcVideoObject;
+             !this.state.localSrcMediaElement;
     },
 
     /**
      * 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.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
-                !this.state.remoteSrcVideoObject &&
+                !this.state.remoteSrcMediaElement &&
                 !this.state.mediaConnected);
     },
 
     /**
      * 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.screenShareMediaElement &&
              !this.props.screenSharePosterUrl;
     },
 
     render: function() {
       var displayScreenShare = !!(this.state.receivingScreenShare ||
         this.props.screenSharePosterUrl);
 
       return (
@@ -552,24 +552,24 @@ loop.standaloneRoomViews = (function(moz
           React.createElement(StandaloneRoomHeader, {dispatcher: this.props.dispatcher}), 
           React.createElement(sharedViews.MediaLayoutView, {
             dispatcher: this.props.dispatcher, 
             displayScreenShare: displayScreenShare, 
             isLocalLoading: this._isLocalLoading(), 
             isRemoteLoading: this._isRemoteLoading(), 
             isScreenShareLoading: this._isScreenShareLoading(), 
             localPosterUrl: this.props.localPosterUrl, 
-            localSrcVideoObject: this.state.localSrcVideoObject, 
+            localSrcMediaElement: this.state.localSrcMediaElement, 
             localVideoMuted: this.state.videoMuted, 
             matchMedia: this.state.matchMedia || window.matchMedia.bind(window), 
             remotePosterUrl: this.props.remotePosterUrl, 
-            remoteSrcVideoObject: this.state.remoteSrcVideoObject, 
+            remoteSrcMediaElement: this.state.remoteSrcMediaElement, 
             renderRemoteVideo: this.shouldRenderRemoteVideo(), 
+            screenShareMediaElement: this.state.screenShareMediaElement, 
             screenSharePosterUrl: this.props.screenSharePosterUrl, 
-            screenShareVideoObject: this.state.screenShareVideoObject, 
             showContextRoomName: true, 
             useDesktopPaths: false}, 
             React.createElement(StandaloneRoomInfoArea, {activeRoomStore: this.props.activeRoomStore, 
               dispatcher: this.props.dispatcher, 
               failureReason: this.state.failureReason, 
               isFirefox: this.props.isFirefox, 
               joinRoom: this.joinRoom, 
               roomState: this.state.roomState, 
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -508,42 +508,42 @@ loop.standaloneRoomViews = (function(moz
      * 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.roomState === ROOM_STATES.MEDIA_WAIT &&
-             !this.state.localSrcVideoObject;
+             !this.state.localSrcMediaElement;
     },
 
     /**
      * 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.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
-                !this.state.remoteSrcVideoObject &&
+                !this.state.remoteSrcMediaElement &&
                 !this.state.mediaConnected);
     },
 
     /**
      * 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.screenShareMediaElement &&
              !this.props.screenSharePosterUrl;
     },
 
     render: function() {
       var displayScreenShare = !!(this.state.receivingScreenShare ||
         this.props.screenSharePosterUrl);
 
       return (
@@ -552,24 +552,24 @@ loop.standaloneRoomViews = (function(moz
           <StandaloneRoomHeader dispatcher={this.props.dispatcher} />
           <sharedViews.MediaLayoutView
             dispatcher={this.props.dispatcher}
             displayScreenShare={displayScreenShare}
             isLocalLoading={this._isLocalLoading()}
             isRemoteLoading={this._isRemoteLoading()}
             isScreenShareLoading={this._isScreenShareLoading()}
             localPosterUrl={this.props.localPosterUrl}
-            localSrcVideoObject={this.state.localSrcVideoObject}
+            localSrcMediaElement={this.state.localSrcMediaElement}
             localVideoMuted={this.state.videoMuted}
             matchMedia={this.state.matchMedia || window.matchMedia.bind(window)}
             remotePosterUrl={this.props.remotePosterUrl}
-            remoteSrcVideoObject={this.state.remoteSrcVideoObject}
+            remoteSrcMediaElement={this.state.remoteSrcMediaElement}
             renderRemoteVideo={this.shouldRenderRemoteVideo()}
+            screenShareMediaElement={this.state.screenShareMediaElement}
             screenSharePosterUrl={this.props.screenSharePosterUrl}
-            screenShareVideoObject={this.state.screenShareVideoObject}
             showContextRoomName={true}
             useDesktopPaths={false}>
             <StandaloneRoomInfoArea activeRoomStore={this.props.activeRoomStore}
               dispatcher={this.props.dispatcher}
               failureReason={this.state.failureReason}
               isFirefox={this.props.isFirefox}
               joinRoom={this.joinRoom}
               roomState={this.state.roomState}
--- a/browser/components/loop/test/desktop-local/conversationViews_test.js
+++ b/browser/components/loop/test/desktop-local/conversationViews_test.js
@@ -533,30 +533,30 @@ describe("loop.conversationViews", funct
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithMatch(dispatcher.dispatch,
           sinon.match.hasOwn("name", "setupStreamElements"));
       });
 
     it("should display the remote video when the stream is enabled", function() {
       conversationStore.setStoreState({
-        remoteSrcVideoObject: { fake: 1 }
+        remoteSrcMediaElement: { fake: 1 }
       });
 
       view = mountTestComponent({
         mediaConnected: true,
         remoteVideoEnabled: true
       });
 
       expect(view.getDOMNode().querySelector(".remote video")).not.eql(null);
     });
 
     it("should display the local video when the stream is enabled", function() {
       conversationStore.setStoreState({
-        localSrcVideoObject: { fake: 1 }
+        localSrcMediaElement: { fake: 1 }
       });
 
       view = mountTestComponent({
         video: {
           enabled: true
         }
       });
 
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -589,60 +589,60 @@ describe("loop.roomViews", function () {
 
         view = mountTestComponent();
         // Force a state change so that it triggers componentDidUpdate
         view.setState({ foo: "bar" });
 
         sinon.assert.calledOnce(onCallTerminatedStub);
       });
 
-      it("should display loading spinner when localSrcVideoObject is null",
+      it("should display loading spinner when localSrcMediaElement is null",
          function() {
            activeRoomStore.setStoreState({
              roomState: ROOM_STATES.MEDIA_WAIT,
-             localSrcVideoObject: null
+             localSrcMediaElement: null
            });
 
            view = mountTestComponent();
 
            expect(view.getDOMNode().querySelector(".local .loading-stream"))
                .not.eql(null);
          });
 
       it("should not display a loading spinner when local stream available",
          function() {
            activeRoomStore.setStoreState({
              roomState: ROOM_STATES.MEDIA_WAIT,
-             localSrcVideoObject: { fake: "video" }
+             localSrcMediaElement: { fake: "video" }
            });
 
            view = mountTestComponent();
 
            expect(view.getDOMNode().querySelector(".local .loading-stream"))
                .eql(null);
          });
 
       it("should display loading spinner when remote stream is not available",
          function() {
            activeRoomStore.setStoreState({
              roomState: ROOM_STATES.HAS_PARTICIPANTS,
-             remoteSrcVideoObject: null
+             remoteSrcMediaElement: null
            });
 
            view = mountTestComponent();
 
            expect(view.getDOMNode().querySelector(".remote .loading-stream"))
                .not.eql(null);
          });
 
       it("should not display a loading spinner when remote stream available",
          function() {
            activeRoomStore.setStoreState({
              roomState: ROOM_STATES.HAS_PARTICIPANTS,
-             remoteSrcVideoObject: { fake: "video" }
+             remoteSrcMediaElement: { fake: "video" }
            });
 
            view = mountTestComponent();
 
            expect(view.getDOMNode().querySelector(".remote .loading-stream"))
                .eql(null);
          });
 
@@ -659,17 +659,17 @@ describe("loop.roomViews", function () {
           TestUtils.findRenderedComponentWithType(view, sharedViews.AvatarView);
         });
 
       it("should display the remote video when there are participants and video is enabled", function() {
         activeRoomStore.setStoreState({
           roomState: ROOM_STATES.HAS_PARTICIPANTS,
           mediaConnected: true,
           remoteVideoEnabled: true,
-          remoteSrcVideoObject: { fake: 1 }
+          remoteSrcMediaElement: { fake: 1 }
         });
 
         view = mountTestComponent();
 
         expect(view.getDOMNode().querySelector(".remote video")).not.eql(null);
       });
 
       it("should display an avatar for local video when the stream is muted", function() {
@@ -679,17 +679,17 @@ describe("loop.roomViews", function () {
 
         view = mountTestComponent();
 
         TestUtils.findRenderedComponentWithType(view, sharedViews.AvatarView);
       });
 
       it("should display the local video when the stream is enabled", function() {
         activeRoomStore.setStoreState({
-          localSrcVideoObject: { fake: 1 },
+          localSrcMediaElement: { fake: 1 },
           videoMuted: false
         });
 
         view = mountTestComponent();
 
         expect(view.getDOMNode().querySelector(".local video")).not.eql(null);
       });
 
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -990,109 +990,109 @@ describe("loop.store.ActiveRoomStore", f
         enabled: false
       }));
 
       expect(store.getStoreState().videoMuted).eql(true);
     });
   });
 
   describe("#mediaStreamCreated", function() {
-    var fakeVideoElement;
+    var fakeStreamElement;
 
     beforeEach(function() {
-      fakeVideoElement = {name: "fakeVideoElement"};
+      fakeStreamElement = {name: "fakeStreamElement"};
     });
 
     it("should add a local video object to the store", function() {
-      expect(store.getStoreState()).to.not.have.property("localSrcVideoObject");
+      expect(store.getStoreState()).to.not.have.property("localSrcMediaElement");
 
       store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
         hasVideo: false,
         isLocal: true,
-        srcVideoObject: fakeVideoElement
+        srcMediaElement: fakeStreamElement
       }));
 
-      expect(store.getStoreState().localSrcVideoObject).eql(fakeVideoElement);
-      expect(store.getStoreState()).to.not.have.property("remoteSrcVideoObject");
+      expect(store.getStoreState().localSrcMediaElement).eql(fakeStreamElement);
+      expect(store.getStoreState()).to.not.have.property("remoteSrcMediaElement");
     });
 
     it("should set the local video enabled", function() {
       store.setStoreState({
         localVideoEnabled: false,
         remoteVideoEnabled: false
       });
 
       store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
         hasVideo: true,
         isLocal: true,
-        srcVideoObject: fakeVideoElement
+        srcMediaElement: fakeStreamElement
       }));
 
       expect(store.getStoreState().localVideoEnabled).eql(true);
       expect(store.getStoreState().remoteVideoEnabled).eql(false);
     });
 
     it("should add a remote video object to the store", function() {
-      expect(store.getStoreState()).to.not.have.property("remoteSrcVideoObject");
+      expect(store.getStoreState()).to.not.have.property("remoteSrcMediaElement");
 
       store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
         hasVideo: false,
         isLocal: false,
-        srcVideoObject: fakeVideoElement
+        srcMediaElement: fakeStreamElement
       }));
 
-      expect(store.getStoreState()).not.have.property("localSrcVideoObject");
-      expect(store.getStoreState().remoteSrcVideoObject).eql(fakeVideoElement);
+      expect(store.getStoreState()).not.have.property("localSrcMediaElement");
+      expect(store.getStoreState().remoteSrcMediaElement).eql(fakeStreamElement);
     });
 
     it("should set the remote video enabled", function() {
       store.setStoreState({
         localVideoEnabled: false,
         remoteVideoEnabled: false
       });
 
       store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
         hasVideo: true,
         isLocal: false,
-        srcVideoObject: fakeVideoElement
+        srcMediaElement: fakeStreamElement
       }));
 
       expect(store.getStoreState().localVideoEnabled).eql(false);
       expect(store.getStoreState().remoteVideoEnabled).eql(true);
     });
   });
 
   describe("#mediaStreamDestroyed", function() {
-    var fakeVideoElement;
+    var fakeStreamElement;
 
     beforeEach(function() {
-      fakeVideoElement = {name: "fakeVideoElement"};
+      fakeStreamElement = {name: "fakeStreamElement"};
 
       store.setStoreState({
-        localSrcVideoObject: fakeVideoElement,
-        remoteSrcVideoObject: fakeVideoElement
+        localSrcMediaElement: fakeStreamElement,
+        remoteSrcMediaElement: fakeStreamElement
       });
     });
 
     it("should clear the local video object", function() {
       store.mediaStreamDestroyed(new sharedActions.MediaStreamDestroyed({
         isLocal: true
       }));
 
-      expect(store.getStoreState().localSrcVideoObject).eql(null);
-      expect(store.getStoreState().remoteSrcVideoObject).eql(fakeVideoElement);
+      expect(store.getStoreState().localSrcMediaElement).eql(null);
+      expect(store.getStoreState().remoteSrcMediaElement).eql(fakeStreamElement);
     });
 
     it("should clear the remote video object", function() {
       store.mediaStreamDestroyed(new sharedActions.MediaStreamDestroyed({
         isLocal: false
       }));
 
-      expect(store.getStoreState().localSrcVideoObject).eql(fakeVideoElement);
-      expect(store.getStoreState().remoteSrcVideoObject).eql(null);
+      expect(store.getStoreState().localSrcMediaElement).eql(fakeStreamElement);
+      expect(store.getStoreState().remoteSrcMediaElement).eql(null);
     });
   });
 
   describe("#remoteVideoStatus", function() {
     it("should set remoteVideoEnabled to true", function() {
       store.setStoreState({
         remoteVideoEnabled: false
       });
@@ -1161,42 +1161,42 @@ describe("loop.store.ActiveRoomStore", f
     it("should save the state", function() {
       store.receivingScreenShare(new sharedActions.ReceivingScreenShare({
         receiving: true
       }));
 
       expect(store.getStoreState().receivingScreenShare).eql(true);
     });
 
-    it("should add a screenShareVideoObject to the store when sharing is active", function() {
-      var fakeVideoElement = {name: "fakeVideoElement"};
-      expect(store.getStoreState()).to.not.have.property("screenShareVideoObject");
+    it("should add a screenShareMediaElement to the store when sharing is active", function() {
+      var fakeStreamElement = {name: "fakeStreamElement"};
+      expect(store.getStoreState()).to.not.have.property("screenShareMediaElement");
 
       store.receivingScreenShare(new sharedActions.ReceivingScreenShare({
         receiving: true,
-        srcVideoObject: fakeVideoElement
+        srcMediaElement: fakeStreamElement
       }));
 
-      expect(store.getStoreState()).to.have.property("screenShareVideoObject",
-        fakeVideoElement);
+      expect(store.getStoreState()).to.have.property("screenShareMediaElement",
+        fakeStreamElement);
     });
 
-    it("should clear the screenShareVideoObject from the store when sharing is inactive", function() {
+    it("should clear the screenShareMediaElement from the store when sharing is inactive", function() {
       store.setStoreState({
-        screenShareVideoObject: {
-          name: "fakeVideoElement"
+        screenShareMediaElement: {
+          name: "fakeStreamElement"
         }
       });
 
       store.receivingScreenShare(new sharedActions.ReceivingScreenShare({
         receiving: false,
-        srcVideoObject: null
+        srcMediaElement: null
       }));
 
-      expect(store.getStoreState().screenShareVideoObject).eql(null);
+      expect(store.getStoreState().screenShareMediaElement).eql(null);
     });
 
     it("should delete the screen remote video dimensions if screen sharing is not active", function() {
       store.setStoreState({
         remoteVideoDimensions: {
           screen: {fake: 10},
           camera: {fake: 20}
         }
@@ -1351,24 +1351,24 @@ describe("loop.store.ActiveRoomStore", f
         mediaConnected: true
       });
 
       store.remotePeerDisconnected();
 
       expect(store.getStoreState().mediaConnected).eql(false);
     });
 
-    it("should clear the remoteSrcVideoObject", function() {
+    it("should clear the remoteSrcMediaElement", function() {
       store.setStoreState({
-        remoteSrcVideoObject: { name: "fakeVideoElement" }
+        remoteSrcMediaElement: { name: "fakeStreamElement" }
       });
 
       store.remotePeerDisconnected();
 
-      expect(store.getStoreState().remoteSrcVideoObject).eql(null);
+      expect(store.getStoreState().remoteSrcMediaElement).eql(null);
     });
 
     it("should remove non-owner participants", function() {
       store.setStoreState({
         participants: [{owner: true}, {}]
       });
 
       store.remotePeerDisconnected();
--- a/browser/components/loop/test/shared/conversationStore_test.js
+++ b/browser/components/loop/test/shared/conversationStore_test.js
@@ -9,17 +9,17 @@ describe("loop.store.ConversationStore",
   var WS_STATES = loop.store.WS_STATES;
   var CALL_TYPES = loop.shared.utils.CALL_TYPES;
   var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
   var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
   var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
   var sharedActions = loop.shared.actions;
   var sharedUtils = loop.shared.utils;
   var sandbox, dispatcher, client, store, fakeSessionData, sdkDriver;
-  var contact, fakeMozLoop, fakeVideoElement;
+  var contact, fakeMozLoop, fakeStreamElement;
   var connectPromise, resolveConnectPromise, rejectConnectPromise;
   var wsCancelSpy, wsCloseSpy, wsDeclineSpy, wsMediaUpSpy, fakeWebsocket;
 
   function checkFailures(done, f) {
     try {
       f();
       done();
     } catch (err) {
@@ -90,17 +90,17 @@ describe("loop.store.ConversationStore",
       callId: "142536",
       sessionId: "321456",
       sessionToken: "341256",
       websocketToken: "543216",
       windowId: "28",
       progressURL: "fakeURL"
     };
 
-    fakeVideoElement = { id: "fakeVideoElement" };
+    fakeStreamElement = { id: "fakeStreamElement" };
 
     var dummySocket = {
       close: sinon.spy(),
       send: sinon.spy()
     };
 
     connectPromise = new Promise(function(resolve, reject) {
       resolveConnectPromise = resolve;
@@ -952,98 +952,98 @@ describe("loop.store.ConversationStore",
       store.mediaConnected(new sharedActions.MediaConnected());
 
       expect(store.getStoreState("mediaConnected")).eql(true);
     });
   });
 
   describe("#mediaStreamCreated", function() {
     it("should add a local video object to the store", function() {
-      expect(store.getStoreState()).to.not.have.property("localSrcVideoObject");
+      expect(store.getStoreState()).to.not.have.property("localSrcMediaElement");
 
       store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
         hasVideo: false,
         isLocal: true,
-        srcVideoObject: fakeVideoElement
+        srcMediaElement: fakeStreamElement
       }));
 
-      expect(store.getStoreState().localSrcVideoObject).eql(fakeVideoElement);
-      expect(store.getStoreState()).to.not.have.property("remoteSrcVideoObject");
+      expect(store.getStoreState().localSrcMediaElement).eql(fakeStreamElement);
+      expect(store.getStoreState()).to.not.have.property("remoteSrcMediaElement");
     });
 
     it("should set the local video enabled", function() {
       store.setStoreState({
         localVideoEnabled: false,
         remoteVideoEnabled: false
       });
 
       store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
         hasVideo: true,
         isLocal: true,
-        srcVideoObject: fakeVideoElement
+        srcMediaElement: fakeStreamElement
       }));
 
       expect(store.getStoreState().localVideoEnabled).eql(true);
       expect(store.getStoreState().remoteVideoEnabled).eql(false);
     });
 
     it("should add a remote video object to the store", function() {
-      expect(store.getStoreState()).to.not.have.property("remoteSrcVideoObject");
+      expect(store.getStoreState()).to.not.have.property("remoteSrcMediaElement");
 
       store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
         hasVideo: false,
         isLocal: false,
-        srcVideoObject: fakeVideoElement
+        srcMediaElement: fakeStreamElement
       }));
 
-      expect(store.getStoreState()).not.have.property("localSrcVideoObject");
-      expect(store.getStoreState().remoteSrcVideoObject).eql(fakeVideoElement);
+      expect(store.getStoreState()).not.have.property("localSrcMediaElement");
+      expect(store.getStoreState().remoteSrcMediaElement).eql(fakeStreamElement);
     });
 
     it("should set the remote video enabled", function() {
       store.setStoreState({
         localVideoEnabled: false,
         remoteVideoEnabled: false
       });
 
       store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
         hasVideo: true,
         isLocal: false,
-        srcVideoObject: fakeVideoElement
+        srcMediaElement: fakeStreamElement
       }));
 
       expect(store.getStoreState().localVideoEnabled).eql(false);
       expect(store.getStoreState().remoteVideoEnabled).eql(true);
     });
   });
 
   describe("#mediaStreamDestroyed", function() {
     beforeEach(function() {
       store.setStoreState({
-        localSrcVideoObject: fakeVideoElement,
-        remoteSrcVideoObject: fakeVideoElement
+        localSrcMediaElement: fakeStreamElement,
+        remoteSrcMediaElement: fakeStreamElement
       });
     });
 
     it("should clear the local video object", function() {
       store.mediaStreamDestroyed(new sharedActions.MediaStreamDestroyed({
         isLocal: true
       }));
 
-      expect(store.getStoreState().localSrcVideoObject).eql(null);
-      expect(store.getStoreState().remoteSrcVideoObject).eql(fakeVideoElement);
+      expect(store.getStoreState().localSrcMediaElement).eql(null);
+      expect(store.getStoreState().remoteSrcMediaElement).eql(fakeStreamElement);
     });
 
     it("should clear the remote video object", function() {
       store.mediaStreamDestroyed(new sharedActions.MediaStreamDestroyed({
         isLocal: false
       }));
 
-      expect(store.getStoreState().localSrcVideoObject).eql(fakeVideoElement);
-      expect(store.getStoreState().remoteSrcVideoObject).eql(null);
+      expect(store.getStoreState().localSrcMediaElement).eql(fakeStreamElement);
+      expect(store.getStoreState().remoteSrcMediaElement).eql(null);
     });
   });
 
   describe("#remoteVideoStatus", function() {
     it("should set remoteVideoEnabled to true", function() {
       store.setStoreState({
         remoteVideoEnabled: false
       });
--- a/browser/components/loop/test/shared/otSdkDriver_test.js
+++ b/browser/components/loop/test/shared/otSdkDriver_test.js
@@ -896,30 +896,30 @@ describe("loop.OTSdkDriver", function ()
       it("should dispatch a MediaStreamCreated action", function() {
         publisher.trigger("streamCreated", { stream: stream });
 
         sinon.assert.called(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.MediaStreamCreated({
             hasVideo: true,
             isLocal: true,
-            srcVideoObject: fakeMockVideo
+            srcMediaElement: fakeMockVideo
           }));
       });
 
       it("should dispatch a MediaStreamCreated action with hasVideo false for audio-only streams", function() {
         stream.hasVideo = false;
         publisher.trigger("streamCreated", { stream: stream });
 
         sinon.assert.called(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.MediaStreamCreated({
             hasVideo: false,
             isLocal: true,
-            srcVideoObject: fakeMockVideo
+            srcMediaElement: fakeMockVideo
           }));
       });
 
       it("should dispatch a ConnectionStatus action", function() {
         driver._metrics.recvStreams = 1;
         driver._metrics.connections = 2;
 
         publisher.trigger("streamCreated", {stream: stream});
@@ -984,34 +984,34 @@ describe("loop.OTSdkDriver", function ()
 
         session.trigger("streamCreated", { stream: fakeStream });
 
         sinon.assert.called(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.MediaStreamCreated({
             hasVideo: true,
             isLocal: false,
-            srcVideoObject: videoElement
+            srcMediaElement: videoElement
           }));
       });
 
       it("should dispatch MediaStreamCreated after subscribe with audio-only indication if hasVideo=false", function() {
         session.subscribe.yieldsOn(driver, null, fakeSubscriberObject,
           videoElement);
         fakeStream.connection = fakeConnection;
         fakeStream.hasVideo = false;
 
         session.trigger("streamCreated", { stream: fakeStream });
 
         sinon.assert.called(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.MediaStreamCreated({
             hasVideo: false,
             isLocal: false,
-            srcVideoObject: videoElement
+            srcMediaElement: videoElement
           }));
       });
 
       it("should trigger a readyForDataChannel signal after subscribe is complete", function() {
         session.subscribe.yieldsOn(driver, null, fakeSubscriberObject,
           document.createElement("video"));
         driver._useDataChannels = true;
         fakeStream.connection = fakeConnection;
--- a/browser/components/loop/test/shared/views_test.js
+++ b/browser/components/loop/test/shared/views_test.js
@@ -1241,17 +1241,17 @@ describe("loop.shared.views", function()
     });
 
     it("should display a video element if a source object is supplied", function() {
       view = mountTestComponent({
         displayAvatar: false,
         mediaType: "local",
         // This doesn't actually get assigned to the video element, but is enough
         // for this test to check display of the video element.
-        srcVideoObject: {
+        srcMediaElement: {
           fake: 1
         }
       });
 
       var element = view.getDOMNode();
 
       expect(element).not.eql(null);
       expect(element.className).eql("local-video");
@@ -1267,17 +1267,17 @@ describe("loop.shared.views", function()
         fakeViewElement = {
           play: sinon.stub(),
           tagName: "VIDEO"
         };
 
         view = mountTestComponent({
           displayAvatar: false,
           mediaType: "local",
-          srcVideoObject: {
+          srcMediaElement: {
             fake: 1
           }
         });
       });
 
       it("should not throw if no source object is specified", function() {
         expect(function() {
           view.attachVideo(null);
@@ -1399,67 +1399,67 @@ describe("loop.shared.views", function()
       });
 
       expect(view.getDOMNode().querySelector(".media-wrapper")
         .classList.contains("receiving-screen-share")).eql(true);
     });
 
     it("should not mark the wrapper as showing local streams when not displaying a stream", function() {
       view = mountTestComponent({
-        localSrcVideoObject: null,
+        localSrcMediaElement: null,
         localPosterUrl: null
       });
 
       expect(view.getDOMNode().querySelector(".media-wrapper")
         .classList.contains("showing-local-streams")).eql(false);
     });
 
     it("should mark the wrapper as showing local streams when displaying a stream", function() {
       view = mountTestComponent({
-        localSrcVideoObject: {},
+        localSrcMediaElement: {},
         localPosterUrl: null
       });
 
       expect(view.getDOMNode().querySelector(".media-wrapper")
         .classList.contains("showing-local-streams")).eql(true);
     });
 
     it("should mark the wrapper as showing local streams when displaying a poster url", function() {
       view = mountTestComponent({
-        localSrcVideoObject: {},
+        localSrcMediaElement: {},
         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,
+        remoteSrcMediaElement: 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: {},
+        remoteSrcMediaElement: {},
         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: {},
+        remoteSrcMediaElement: {},
         remotePosterUrl: "fake/url"
       });
 
       expect(view.getDOMNode().querySelector(".media-wrapper")
         .classList.contains("showing-remote-streams")).eql(true);
     });
   });
 });
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -481,44 +481,44 @@ describe("loop.standaloneRoomViews", fun
 
           sinon.assert.calledOnce(dispatch);
           sinon.assert.calledWithExactly(dispatch, new sharedActions.JoinRoom());
         });
       });
 
       describe("screenShare", function() {
         it("should show a loading screen if receivingScreenShare is true " +
-           "but no screenShareVideoObject is present", function() {
+           "but no screenShareMediaElement is present", function() {
           view.setState({
             "receivingScreenShare": true,
-            "screenShareVideoObject": null
+            "screenShareMediaElement": null
           });
 
           expect(view.getDOMNode().querySelector(".screen .loading-stream"))
               .not.eql(null);
         });
 
         it("should not show loading screen if receivingScreenShare is false " +
-           "and screenShareVideoObject is null", function() {
+           "and screenShareMediaElement is null", function() {
              view.setState({
                "receivingScreenShare": false,
-               "screenShareVideoObject": null
+               "screenShareMediaElement": null
              });
 
              expect(view.getDOMNode().querySelector(".screen .loading-stream"))
                  .eql(null);
         });
 
-        it("should not show a loading screen if screenShareVideoObject is set",
+        it("should not show a loading screen if screenShareMediaElement is set",
            function() {
              var videoElement = document.createElement("video");
 
              view.setState({
                "receivingScreenShare": true,
-               "screenShareVideoObject": videoElement
+               "screenShareMediaElement": videoElement
              });
 
              expect(view.getDOMNode().querySelector(".screen .loading-stream"))
                  .eql(null);
         });
       });
 
       describe("Participants", function() {
@@ -526,149 +526,149 @@ describe("loop.standaloneRoomViews", fun
 
         beforeEach(function() {
           videoElement = document.createElement("video");
         });
 
         it("should render local video when video_muted is false", function() {
           activeRoomStore.setStoreState({
             roomState: ROOM_STATES.HAS_PARTICIPANTS,
-            localSrcVideoObject: videoElement,
+            localSrcMediaElement: videoElement,
             videoMuted: false
           });
 
           expect(view.getDOMNode().querySelector(".local video")).not.eql(null);
         });
 
         it("should not render a local avatar when video_muted is false", function() {
           activeRoomStore.setStoreState({
             roomState: ROOM_STATES.HAS_PARTICIPANTS,
             videoMuted: false
           });
 
           expect(view.getDOMNode().querySelector(".local .avatar")).eql(null);
         });
 
-        it("should render local loading screen when no srcVideoObject",
+        it("should render local loading screen when no srcMediaElement",
            function() {
              activeRoomStore.setStoreState({
                roomState: ROOM_STATES.MEDIA_WAIT,
-               remoteSrcVideoObject: null
+               remoteSrcMediaElement: null
              });
 
              expect(view.getDOMNode().querySelector(".local .loading-stream"))
                  .not.eql(null);
         });
 
-        it("should not render local loading screen when srcVideoObject is set",
+        it("should not render local loading screen when srcMediaElement is set",
            function() {
              activeRoomStore.setStoreState({
                roomState: ROOM_STATES.MEDIA_WAIT,
-               localSrcVideoObject: videoElement
+               localSrcMediaElement: videoElement
              });
 
              expect(view.getDOMNode().querySelector(".local .loading-stream"))
                   .eql(null);
         });
 
-        it("should not render remote loading screen when srcVideoObject is set",
+        it("should not render remote loading screen when srcMediaElement is set",
            function() {
              activeRoomStore.setStoreState({
                roomState: ROOM_STATES.HAS_PARTICIPANTS,
-               remoteSrcVideoObject: videoElement
+               remoteSrcMediaElement: videoElement
              });
 
              expect(view.getDOMNode().querySelector(".remote .loading-stream"))
                   .eql(null);
         });
 
         it("should render remote video when the room HAS_PARTICIPANTS and" +
           " remoteVideoEnabled is true", function() {
           activeRoomStore.setStoreState({
             roomState: ROOM_STATES.HAS_PARTICIPANTS,
-            remoteSrcVideoObject: videoElement,
+            remoteSrcMediaElement: videoElement,
             remoteVideoEnabled: true
           });
 
           expect(view.getDOMNode().querySelector(".remote video")).not.eql(null);
         });
 
         it("should render remote video when the room HAS_PARTICIPANTS and" +
           " remoteVideoEnabled is true", function() {
           activeRoomStore.setStoreState({
             roomState: ROOM_STATES.HAS_PARTICIPANTS,
-            remoteSrcVideoObject: videoElement,
+            remoteSrcMediaElement: videoElement,
             remoteVideoEnabled: true
           });
 
           expect(view.getDOMNode().querySelector(".remote video")).not.eql(null);
         });
 
         it("should not render remote video when the room HAS_PARTICIPANTS," +
           " remoteVideoEnabled is false, and mediaConnected is true", function() {
           activeRoomStore.setStoreState({
             roomState: ROOM_STATES.HAS_PARTICIPANTS,
-            remoteSrcVideoObject: videoElement,
+            remoteSrcMediaElement: videoElement,
             mediaConnected: true,
             remoteVideoEnabled: false
           });
 
           expect(view.getDOMNode().querySelector(".remote video")).eql(null);
         });
 
         it("should render remote video when the room HAS_PARTICIPANTS," +
           " and both remoteVideoEnabled and mediaConnected are false", function() {
           activeRoomStore.setStoreState({
             roomState: ROOM_STATES.HAS_PARTICIPANTS,
-            remoteSrcVideoObject: videoElement,
+            remoteSrcMediaElement: videoElement,
             mediaConnected: false,
             remoteVideoEnabled: false
           });
 
           expect(view.getDOMNode().querySelector(".remote video")).not.eql(null);
         });
 
         it("should not render a remote avatar when the room is in MEDIA_WAIT", function() {
           activeRoomStore.setStoreState({
             roomState: ROOM_STATES.MEDIA_WAIT,
-            remoteSrcVideoObject: videoElement,
+            remoteSrcMediaElement: videoElement,
             remoteVideoEnabled: false
           });
 
           expect(view.getDOMNode().querySelector(".remote .avatar")).eql(null);
         });
 
         it("should not render a remote avatar when the room is CLOSING and" +
           " remoteVideoEnabled is false", function() {
           activeRoomStore.setStoreState({
             roomState: ROOM_STATES.CLOSING,
-            remoteSrcVideoObject: videoElement,
+            remoteSrcMediaElement: videoElement,
             remoteVideoEnabled: false
           });
 
           expect(view.getDOMNode().querySelector(".remote .avatar")).eql(null);
         });
 
         it("should render a remote avatar when the room HAS_PARTICIPANTS, " +
           "remoteVideoEnabled is false, and mediaConnected is true", function() {
           activeRoomStore.setStoreState({
             roomState: ROOM_STATES.HAS_PARTICIPANTS,
-            remoteSrcVideoObject: videoElement,
+            remoteSrcMediaElement: videoElement,
             remoteVideoEnabled: false,
             mediaConnected: true
           });
 
           expect(view.getDOMNode().querySelector(".remote .avatar")).not.eql(null);
         });
 
         it("should render a remote avatar when the room HAS_PARTICIPANTS, " +
-          "remoteSrcVideoObject is false, mediaConnected is true", function() {
+          "remoteSrcMediaElement is false, mediaConnected is true", function() {
           activeRoomStore.setStoreState({
             roomState: ROOM_STATES.HAS_PARTICIPANTS,
-            remoteSrcVideoObject: null,
+            remoteSrcMediaElement: null,
             remoteVideoEnabled: false,
             mediaConnected: true
           });
 
           expect(view.getDOMNode().querySelector(".remote .avatar")).not.eql(null);
         });
       });
 
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -200,17 +200,17 @@
     mediaConnected: false,
     roomState: ROOM_STATES.JOINED,
     remoteVideoEnabled: false
   });
 
   var loadingRemoteVideoRoomStore = makeActiveRoomStore({
     mediaConnected: false,
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
-    remoteSrcVideoObject: false
+    remoteSrcMediaElement: false
   });
 
   var readyRoomStore = makeActiveRoomStore({
     mediaConnected: false,
     roomState: ROOM_STATES.READY
   });
 
   var updatingActiveRoomStore = makeActiveRoomStore({
@@ -241,31 +241,31 @@
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     receivingScreenShare: true
   });
 
   var loadingRemoteLoadingScreenStore = makeActiveRoomStore({
     mediaConnected: false,
     receivingScreenShare: true,
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
-    remoteSrcVideoObject: false
+    remoteSrcMediaElement: 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
+    srcMediaElement: false
   });
   loadingRemoteLoadingScreenStore.receivingScreenShare({
     receiving: true,
-    srcVideoObject: false
+    srcMediaElement: false
   });
 
   var fullActiveRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.FULL
   });
 
   var failedRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.FAILED
@@ -290,17 +290,17 @@
     })
   });
 
   var desktopRoomStoreLoading = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: makeActiveRoomStore({
       roomState: ROOM_STATES.HAS_PARTICIPANTS,
       mediaConnected: false,
-      remoteSrcVideoObject: false
+      remoteSrcMediaElement: false
     })
   });
 
   var desktopRoomStoreMedium = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: makeActiveRoomStore({
       roomState: ROOM_STATES.HAS_PARTICIPANTS
     })
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -200,17 +200,17 @@
     mediaConnected: false,
     roomState: ROOM_STATES.JOINED,
     remoteVideoEnabled: false
   });
 
   var loadingRemoteVideoRoomStore = makeActiveRoomStore({
     mediaConnected: false,
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
-    remoteSrcVideoObject: false
+    remoteSrcMediaElement: false
   });
 
   var readyRoomStore = makeActiveRoomStore({
     mediaConnected: false,
     roomState: ROOM_STATES.READY
   });
 
   var updatingActiveRoomStore = makeActiveRoomStore({
@@ -241,31 +241,31 @@
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     receivingScreenShare: true
   });
 
   var loadingRemoteLoadingScreenStore = makeActiveRoomStore({
     mediaConnected: false,
     receivingScreenShare: true,
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
-    remoteSrcVideoObject: false
+    remoteSrcMediaElement: 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
+    srcMediaElement: false
   });
   loadingRemoteLoadingScreenStore.receivingScreenShare({
     receiving: true,
-    srcVideoObject: false
+    srcMediaElement: false
   });
 
   var fullActiveRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.FULL
   });
 
   var failedRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.FAILED
@@ -290,17 +290,17 @@
     })
   });
 
   var desktopRoomStoreLoading = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: makeActiveRoomStore({
       roomState: ROOM_STATES.HAS_PARTICIPANTS,
       mediaConnected: false,
-      remoteSrcVideoObject: false
+      remoteSrcMediaElement: false
     })
   });
 
   var desktopRoomStoreMedium = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: makeActiveRoomStore({
       roomState: ROOM_STATES.HAS_PARTICIPANTS
     })
--- a/dom/bluetooth/bluedroid/BluetoothMapSmsManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothMapSmsManager.cpp
@@ -329,16 +329,18 @@ BluetoothMapSmsManager::MasDataHandler(U
         ReplyToPut();
 
         if (type.EqualsLiteral("x-bt/MAP-NotificationRegistration")) {
           HandleNotificationRegistration(pktHeaders);
         } else if (type.EqualsLiteral("x-bt/MAP-event-report")) {
           HandleEventReport(pktHeaders);
         } else if (type.EqualsLiteral("x-bt/messageStatus")) {
           HandleSetMessageStatus(pktHeaders);
+        } else if (type.EqualsLiteral("x-bt/message")) {
+          HandleSmsMmsPushMessage(pktHeaders);
         }
       }
       break;
     case ObexRequestCode::Get:
     case ObexRequestCode::GetFinal: {
         // [opcode:1][length:2][Headers:var]
         if (receivedLength < 3 ||
           !ParseHeaders(&data[3], receivedLength - 3, &pktHeaders)) {
@@ -814,16 +816,33 @@ BluetoothMapSmsManager::AppendBtNamedVal
       uint8_t statusValue = *((uint8_t *)buf);
       // convert big endian to little endian
       statusValue = (statusValue >> 8) | (statusValue << 8);
       BT_LOGR("msg filter statusvalue: %d", statusValue);
       AppendNamedValue(aValues, "statusValue",
                        static_cast<uint32_t>(statusValue));
       break;
     }
+    case Map::AppParametersTagId::Transparent: {
+      uint8_t transparent = *((uint8_t *)buf);
+      // convert big endian to little endian
+      transparent = (transparent >> 8) | (transparent << 8);
+      BT_LOGR("msg filter statusvalue: %d", transparent);
+      AppendNamedValue(aValues, "transparent",
+                       static_cast<uint32_t>(transparent));
+      break;
+    }
+    case Map::AppParametersTagId::Retry: {
+      uint8_t retry = *((uint8_t *)buf);
+      // convert big endian to little endian
+      retry = (retry >> 8) | (retry << 8);
+      BT_LOGR("msg filter retry: %d", retry);
+      AppendNamedValue(aValues, "retry", static_cast<uint32_t>(retry));
+      break;
+    }
     default:
       BT_LOGR("Unsupported AppParameterTag: %x", aTagId);
       break;
   }
 }
 
 void
 BluetoothMapSmsManager::HandleSmsMmsMsgListing(const ObexHeaderSet& aHeader)
@@ -976,16 +995,38 @@ BluetoothMapSmsManager::HandleSetMessage
   AppendBtNamedValueByTagId(aHeader, data,
                             Map::AppParametersTagId::StatusValue);
 
   bs->DistributeSignal(NS_LITERAL_STRING(MAP_SET_MESSAGE_STATUS_REQ_ID),
                        NS_LITERAL_STRING(KEY_ADAPTER), data);
 }
 
 void
+BluetoothMapSmsManager::HandleSmsMmsPushMessage(const ObexHeaderSet& aHeader)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  InfallibleTArray<BluetoothNamedValue> data;
+  nsString name;
+  aHeader.GetName(name);
+  AppendNamedValue(data, "folderName", name);
+
+  AppendBtNamedValueByTagId(aHeader, data,
+                            Map::AppParametersTagId::Transparent);
+  AppendBtNamedValueByTagId(aHeader, data, Map::AppParametersTagId::Retry);
+  AppendBtNamedValueByTagId(aHeader, data, Map::AppParametersTagId::Charset);
+
+  bs->DistributeSignal(NS_LITERAL_STRING(MAP_PUSH_MESSAGE_REQ_ID),
+                       NS_LITERAL_STRING(KEY_ADAPTER), data);
+}
+
+void
 BluetoothMapSmsManager::ReplyError(uint8_t aError)
 {
   BT_LOGR("[0x%x]", aError);
 
   // Section 3.2 "Response Format", IrOBEX 1.2
   // [opcode:1][length:2][Headers:var]
   uint8_t req[255];
   int index = 3;
--- a/dom/bluetooth/bluedroid/BluetoothMapSmsManager.h
+++ b/dom/bluetooth/bluedroid/BluetoothMapSmsManager.h
@@ -93,16 +93,18 @@ private:
   void ReplyError(uint8_t aError);
 
   void HandleNotificationRegistration(const ObexHeaderSet& aHeader);
   void HandleEventReport(const ObexHeaderSet& aHeader);
   void HandleSetMessageStatus(const ObexHeaderSet& aHeader);
   void HandleSmsMmsFolderListing(const ObexHeaderSet& aHeader);
   void HandleSmsMmsMsgListing(const ObexHeaderSet& aHeader);
   void HandleSmsMmsGetMessage(const ObexHeaderSet& aHeader);
+  void HandleSmsMmsPushMessage(const ObexHeaderSet& aHeader);
+
   void AppendBtNamedValueByTagId(const ObexHeaderSet& aHeader,
     InfallibleTArray<BluetoothNamedValue>& aValues,
     const Map::AppParametersTagId aTagId);
   void SendMasObexData(uint8_t* aData, uint8_t aOpcode, int aSize);
   void SendMnsObexData(uint8_t* aData, uint8_t aOpcode, int aSize);
 
   uint8_t SetPath(uint8_t flags, const ObexHeaderSet& aHeader);
   bool CompareHeaderTarget(const ObexHeaderSet& aHeader);
--- a/dom/bluetooth/common/BluetoothCommon.h
+++ b/dom/bluetooth/common/BluetoothCommon.h
@@ -181,32 +181,23 @@ extern bool gBluetoothDebugFlag;
 /**
  * When receiving a PBAP request from a remote device, we'll dispatch an event.
  */
 #define PULL_PHONEBOOK_REQ_ID                "pullphonebookreq"
 #define PULL_VCARD_ENTRY_REQ_ID              "pullvcardentryreq"
 #define PULL_VCARD_LISTING_REQ_ID            "pullvcardlistingreq"
 
 /**
- * When receiving a MAP request of 'messages listing' from a remote device,
+ * When receiving a MAP request from a remote device,
  * we'll dispatch an event.
  */
 #define MAP_MESSAGES_LISTING_REQ_ID          "mapmessageslistingreq"
-
-/**
- * When receiving a MAP request of 'get message' from a remote device,
- * we'll dispatch an event.
- */
 #define MAP_GET_MESSAGE_REQ_ID               "mapgetmessagereq"
-
-/**
- * When receiving a MAP request of 'set message' from a remote device,
- * we'll dispatch an event.
- */
 #define MAP_SET_MESSAGE_STATUS_REQ_ID        "mapsetmessagestatusreq"
+#define MAP_PUSH_MESSAGE_REQ_ID              "mappushmessagereq"
 
 /**
  * When the value of a characteristic of a remote BLE device changes, we'll
  * dispatch an event
  */
 #define GATT_CHARACTERISTIC_CHANGED_ID       "characteristicchanged"
 
 /**
new file mode 100644
index 0000000000000000000000000000000000000000..d08f88ea52b2e5e7eb8b19cf1f072f8d14d0fc4f
GIT binary patch
literal 13292
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
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/unit/test_schema21upgrade.js
@@ -0,0 +1,336 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+function testSteps()
+{
+  const testName = "schema21upgrade";
+  const testKeys = [
+    -1/0,
+    -1.7e308,
+    -10000,
+    -2,
+    -1.5,
+    -1,
+    -1.00001e-200,
+    -1e-200,
+    0,
+    1e-200,
+    1.00001e-200,
+    1,
+    2,
+    10000,
+    1.7e308,
+    1/0,
+    new Date("1750-01-02"),
+    new Date("1800-12-31T12:34:56.001Z"),
+    new Date(-1000),
+    new Date(-10),
+    new Date(-1),
+    new Date(0),
+    new Date(1),
+    new Date(2),
+    new Date(1000),
+    new Date("1971-01-01"),
+    new Date("1971-01-01T01:01:01Z"),
+    new Date("1971-01-01T01:01:01.001Z"),
+    new Date("1971-01-01T01:01:01.01Z"),
+    new Date("1971-01-01T01:01:01.1Z"),
+    new Date("1980-02-02"),
+    new Date("3333-03-19T03:33:33.333Z"),
+    "",
+    "\x00",
+    "\x00\x00",
+    "\x00\x01",
+    "\x01",
+    "\x02",
+    "\x03",
+    "\x04",
+    "\x07",
+    "\x08",
+    "\x0F",
+    "\x10",
+    "\x1F",
+    "\x20",
+    "01234",
+    "\x3F",
+    "\x40",
+    "A",
+    "A\x00",
+    "A1",
+    "ZZZZ",
+    "a",
+    "a\x00",
+    "aa",
+    "azz",
+    "}",
+    "\x7E",
+    "\x7F",
+    "\x80",
+    "\xFF",
+    "\u0100",
+    "\u01FF",
+    "\u0200",
+    "\u03FF",
+    "\u0400",
+    "\u07FF",
+    "\u0800",
+    "\u0FFF",
+    "\u1000",
+    "\u1FFF",
+    "\u2000",
+    "\u3FFF",
+    "\u4000",
+    "\u7FFF",
+    "\u8000",
+    "\uD800",
+    "\uD800a",
+    "\uD800\uDC01",
+    "\uDBFF",
+    "\uDC00",
+    "\uDFFF\uD800",
+    "\uFFFE",
+    "\uFFFF",
+      "\uFFFF\x00",
+    "\uFFFFZZZ",
+    [],
+    [-1/0],
+    [-1],
+    [0],
+    [1],
+    [1, "a"],
+    [1, []],
+    [1, [""]],
+    [2, 3],
+    [2, 3.0000000000001],
+    [12, [[]]],
+    [12, [[[]]]],
+    [12, [[[""]]]],
+    [12, [[["foo"]]]],
+    [12, [[[[[3]]]]]],
+    [12, [[[[[[3]]]]]]],
+    [12, [[[[[[3],[[[[[4.2]]]]]]]]]]],
+    [new Date(-1)],
+    [new Date(1)],
+    [""],
+    ["", [[]]],
+    ["", [[[]]]],
+    ["abc"],
+    ["abc", "def"],
+    ["abc\x00"],
+    ["abc\x00", "\x00\x01"],
+    ["abc\x00", "\x00def"],
+    ["abc\x00\x00def"],
+    ["x", [[]]],
+    ["x", [[[]]]],
+    [[]],
+    [[],"foo"],
+    [[],[]],
+    [[[]]],
+    [[[]], []],
+    [[[]], [[]]],
+    [[[]], [[1]]],
+    [[[]], [[[]]]],
+    [[[1]]],
+    [[[[]], []]],
+  ];
+  const testString =
+    "abcdefghijklmnopqrstuvwxyz0123456789`~!@#$%^&*()-_+=,<.>/?\\|";
+
+  clearAllDatabases(continueToNextStepSync);
+  yield undefined;
+
+  info("Installing profile");
+
+  installPackagedProfile(testName + "_profile");
+
+  info("Opening database with no version");
+
+  let request = indexedDB.open(testName);
+  request.onerror = errorHandler;
+  request.onupgradeneeded = unexpectedSuccessHandler;
+  request.onsuccess = grabEventAndContinueHandler;
+  let event = yield undefined;
+
+  let db = event.target.result;
+
+  is(db.version, 1, "Correct db version");
+
+  let transaction = db.transaction(testName);
+  transaction.oncomplete = grabEventAndContinueHandler;
+
+  let objectStore = transaction.objectStore(testName);
+  let index = objectStore.index("uniqueIndex");
+
+  info("Starting 'uniqueIndex' cursor");
+
+  let keyIndex = 0;
+  index.openCursor().onsuccess = event => {
+    let cursor = event.target.result;
+    if (cursor) {
+      info("Comparing " + JSON.stringify(cursor.primaryKey) + " to " +
+           JSON.stringify(testKeys[cursor.key]) +
+           " [" + cursor.key + "]");
+      is(indexedDB.cmp(cursor.primaryKey, testKeys[cursor.key]), 0,
+         "Keys compare equally via 'indexedDB.cmp'");
+      is(compareKeys(cursor.primaryKey, testKeys[cursor.key]), true,
+         "Keys compare equally via 'compareKeys'");
+
+      let indexProperty = cursor.value.index;
+      is(Array.isArray(indexProperty), true, "index property is Array");
+      is(indexProperty[0], cursor.key, "index property first item correct");
+      is(indexProperty[1], cursor.key + 1, "index property second item correct");
+
+      is(cursor.key, keyIndex, "Cursor key property is correct");
+
+      is(cursor.value.testString, testString, "Test string compared equally");
+
+      keyIndex++;
+      cursor.continue();
+    }
+  };
+  yield undefined;
+
+  is(keyIndex, testKeys.length, "Saw all keys");
+
+  transaction = db.transaction(testName, "readwrite");
+  transaction.oncomplete = grabEventAndContinueHandler;
+
+  objectStore = transaction.objectStore(testName);
+  index = objectStore.index("index");
+
+  info("Getting all 'index' keys");
+
+  index.getAllKeys().onsuccess = grabEventAndContinueHandler;
+  event = yield undefined;
+
+  is(event.target.result.length, testKeys.length * 2, "Got all keys");
+
+  info("Starting objectStore cursor");
+
+  objectStore.openCursor().onsuccess = event => {
+    let cursor = event.target.result;
+    if (cursor) {
+      let value = cursor.value;
+      is(value.testString, testString, "Test string compared equally");
+
+      delete value.index;
+      cursor.update(value);
+
+      cursor.continue();
+    } else {
+      continueToNextStepSync();
+    }
+  };
+  yield undefined;
+
+  info("Getting all 'index' keys");
+
+  index.getAllKeys().onsuccess = grabEventAndContinueHandler;
+  event = yield undefined;
+
+  is(event.target.result.length, 0, "Removed all keys");
+  yield undefined;
+
+  db.close();
+
+  info("Opening database with new version");
+
+  request = indexedDB.open(testName, 2);
+  request.onerror = errorHandler;
+  request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = grabEventAndContinueHandler;
+  event = yield undefined;
+
+  info("Deleting indexes");
+
+  objectStore = event.target.transaction.objectStore(testName);
+  objectStore.deleteIndex("index");
+  objectStore.deleteIndex("uniqueIndex");
+
+  event = yield undefined;
+
+  db = event.target.result;
+
+  transaction = db.transaction(testName, "readwrite");
+  transaction.oncomplete = grabEventAndContinueHandler;
+
+  info("Starting objectStore cursor");
+
+  objectStore = transaction.objectStore(testName);
+  objectStore.openCursor().onsuccess = event => {
+    let cursor = event.target.result;
+    if (cursor) {
+      let value = cursor.value;
+      is(value.testString, testString, "Test string compared equally");
+
+      value.index = value.keyPath;
+      cursor.update(value);
+
+      cursor.continue();
+    }
+  };
+  event = yield undefined;
+
+  db.close();
+
+  info("Opening database with new version");
+
+  request = indexedDB.open(testName, 3);
+  request.onerror = errorHandler;
+  request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = grabEventAndContinueHandler;
+  event = yield undefined;
+
+  info("Creating indexes");
+
+  objectStore = event.target.transaction.objectStore(testName);
+  objectStore.createIndex("index", "index");
+
+  event = yield undefined;
+
+  db = event.target.result;
+
+  transaction = db.transaction(testName);
+  transaction.oncomplete = grabEventAndContinueHandler;
+
+  objectStore = transaction.objectStore(testName);
+  index = objectStore.index("index");
+
+  info("Starting 'index' cursor");
+
+  keyIndex = 0;
+  index.openCursor().onsuccess = event => {
+    let cursor = event.target.result;
+    if (cursor) {
+      is(indexedDB.cmp(cursor.primaryKey, testKeys[keyIndex]), 0,
+         "Keys compare equally via 'indexedDB.cmp'");
+      is(compareKeys(cursor.primaryKey, testKeys[keyIndex]), true,
+         "Keys compare equally via 'compareKeys'");
+      is(indexedDB.cmp(cursor.key, testKeys[keyIndex]), 0,
+         "Keys compare equally via 'indexedDB.cmp'");
+      is(compareKeys(cursor.key, testKeys[keyIndex]), true,
+         "Keys compare equally via 'compareKeys'");
+
+      let indexProperty = cursor.value.index;
+      is(indexedDB.cmp(indexProperty, testKeys[keyIndex]), 0,
+         "Keys compare equally via 'indexedDB.cmp'");
+      is(compareKeys(indexProperty, testKeys[keyIndex]), true,
+         "Keys compare equally via 'compareKeys'");
+
+      is(cursor.value.testString, testString, "Test string compared equally");
+
+      keyIndex++;
+      cursor.continue();
+    }
+  };
+  yield undefined;
+
+  is(keyIndex, testKeys.length, "Added all keys again");
+
+  finishTest();
+  yield undefined;
+}
--- a/dom/indexedDB/test/unit/xpcshell-parent-process.ini
+++ b/dom/indexedDB/test/unit/xpcshell-parent-process.ini
@@ -13,16 +13,17 @@ support-files =
   mutableFileUpgrade_profile.zip
   GlobalObjectsChild.js
   GlobalObjectsComponent.js
   GlobalObjectsComponent.manifest
   GlobalObjectsModule.jsm
   GlobalObjectsSandbox.js
   metadataRestore_profile.zip
   schema18upgrade_profile.zip
+  schema21upgrade_profile.zip
   xpcshell-shared.ini
 
 [include:xpcshell-shared.ini]
 
 [test_blob_file_backed.js]
 [test_bug1056939.js]
 [test_defaultStorageUpgrade.js]
 [test_globalObjects_ipc.js]
@@ -31,11 +32,12 @@ skip-if = toolkit == 'android'
 [test_invalidate.js]
 # disabled for the moment.
 skip-if = true
 [test_lowDiskSpace.js]
 [test_metadataRestore.js]
 [test_mutableFileUpgrade.js]
 [test_readwriteflush_disabled.js]
 [test_schema18upgrade.js]
+[test_schema21upgrade.js]
 [test_temporary_storage.js]
 # bug 951017: intermittent failure on Android x86 emulator
 skip-if = os == "android" && processor == "x86"
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -2109,17 +2109,17 @@ public abstract class GeckoApp
 
         final HealthRecorder rec = mHealthRecorder;
         mHealthRecorder = null;
         if (rec != null && rec.isEnabled()) {
             // Closing a BrowserHealthRecorder could incur a write.
             ThreadUtils.postToBackgroundThread(new Runnable() {
                 @Override
                 public void run() {
-                    rec.close();
+                    rec.close(GeckoApp.this);
                 }
             });
         }
 
         Favicons.close();
 
         super.onDestroy();
 
--- a/mobile/android/base/background/healthreport/ProfileInformationCache.java
+++ b/mobile/android/base/background/healthreport/ProfileInformationCache.java
@@ -29,18 +29,19 @@ public class ProfileInformationCache imp
   private static final String CACHE_FILE = "profile_info_cache.json";
 
   /*
    * FORMAT_VERSION history:
    *   -: No version number; implicit v1.
    *   1: Add versioning (Bug 878670).
    *   2: Bump to regenerate add-on set after landing Bug 900694 (Bug 901622).
    *   3: Add distribution, osLocale, appLocale.
+   *   4: Add experiments as add-ons.
    */
-  public static final int FORMAT_VERSION = 3;
+  public static final int FORMAT_VERSION = 4;
 
   protected boolean initialized = false;
   protected boolean needsWrite = false;
 
   protected final File file;
 
   private volatile boolean blocklistEnabled = true;
   private volatile boolean telemetryEnabled = false;
--- a/mobile/android/base/health/BrowserHealthRecorder.java
+++ b/mobile/android/base/health/BrowserHealthRecorder.java
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.health;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.OutputStreamWriter;
+import java.lang.String;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Scanner;
@@ -33,19 +34,25 @@ import org.mozilla.gecko.background.heal
 import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field;
 import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields;
 import org.mozilla.gecko.background.healthreport.ProfileInformationCache;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.distribution.Distribution.DistributionDescriptor;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import com.keepsafe.switchboard.SwitchBoard;
+
+import android.content.BroadcastReceiver;
 import android.content.ContentProviderClient;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.support.v4.content.LocalBroadcastManager;
 import android.util.Log;
 
 /**
  * BrowserHealthRecorder is the browser's interface to the Firefox Health
  * Report storage system. It manages environments (a collection of attributes
  * that are tracked longitudinally) on the browser's behalf, exposing a simpler
  * interface for recording changes.
  *
@@ -54,17 +61,17 @@ import android.util.Log;
  * Tell it when an environment attribute has changed: call {@link
  * #onAppLocaleChanged(String)} followed by {@link
  * #onEnvironmentChanged()}.
  *
  * Use it to record events: {@link #recordSearch(String, String)}.
  *
  * Shut it down when you're done being a browser: {@link #close()}.
  */
-public class BrowserHealthRecorder implements HealthRecorder, GeckoEventListener {
+public class BrowserHealthRecorder extends BroadcastReceiver implements HealthRecorder, GeckoEventListener {
     private static final String LOG_TAG = "GeckoHealthRec";
     private static final String PREF_ACCEPT_LANG = "intl.accept_languages";
     private static final String PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
     private static final String EVENT_SNAPSHOT = "HealthReport:Snapshot";
     private static final String EVENT_ADDONS_CHANGE = "Addons:Change";
     private static final String EVENT_ADDONS_UNINSTALLING = "Addons:Uninstalling";
     private static final String EVENT_PREF_CHANGE = "Pref:Change";
 
@@ -172,30 +179,33 @@ public class BrowserHealthRecorder imple
         return true;
     }
 
     /**
      * Shut down database connections, unregister event listeners, and perform
      * provider-specific uninitialization.
      */
     @Override
-    public synchronized void close() {
+    public synchronized void close(final Context context) {
         switch (this.state) {
             case CLOSED:
                 Log.w(LOG_TAG, "Ignoring attempt to double-close closed BrowserHealthRecorder.");
                 return;
             case INITIALIZED:
                 Log.i(LOG_TAG, "Closing Health Report client.");
                 break;
             default:
                 Log.i(LOG_TAG, "Closing incompletely initialized BrowserHealthRecorder.");
         }
 
         this.state = State.CLOSED;
         this.unregisterEventListeners();
+        if (AppConstants.MOZ_SWITCHBOARD) {
+            LocalBroadcastManager.getInstance(context).unregisterReceiver(this);
+        }
 
         // Add any necessary provider uninitialization here.
         this.storage = null;
         if (this.client != null) {
             this.client.release();
             this.client = null;
         }
     }
@@ -518,16 +528,22 @@ public class BrowserHealthRecorder imple
         Log.d(LOG_TAG, "Initializing profile cache.");
         this.state = State.INITIALIZING;
 
         // If we can restore state from last time, great.
         if (this.profileCache.restoreUnlessInitialized()) {
             this.profileCache.updateLocales(osLocale, appLocale);
             this.profileCache.completeInitialization();
 
+            // Listen for experiment changes.
+            if (AppConstants.MOZ_SWITCHBOARD) {
+                IntentFilter intentFilter = new IntentFilter(SwitchBoard.ACTION_CONFIG_FETCHED);
+                LocalBroadcastManager.getInstance(context).registerReceiver(this, intentFilter);
+            }
+
             Log.d(LOG_TAG, "Successfully restored state. Initializing storage.");
             initializeStorage();
             return;
         }
 
         // Otherwise, let's initialize it from scratch.
         this.profileCache.beginInitialization();
         this.profileCache.setProfileCreationTime(getAndPersistProfileInitTime(context, profilePath));
@@ -621,16 +637,33 @@ public class BrowserHealthRecorder imple
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
             if (EVENT_SNAPSHOT.equals(event)) {
                 Log.d(LOG_TAG, "Got all add-ons and prefs.");
                 try {
                     JSONObject json = message.getJSONObject("json");
                     JSONObject addons = json.getJSONObject("addons");
+
+                    // Treat active experiments as add-ons
+                    if (AppConstants.MOZ_SWITCHBOARD) {
+                        List<String> experiments = SwitchBoard.getActiveExperiments(GeckoAppShell.getContext());
+                        for (String experiment : experiments) {
+                            // Create a fake add-on name
+                            String fakeName = experiment + "@experiments.mozilla.org";
+                            try {
+                                // Create a dummy JSON object for the experiment.
+                                JSONObject fakeAddon = new JSONObject();
+                                fakeAddon.put("type", "experiment");
+                                addons.put(fakeName, fakeAddon);
+                            } catch (JSONException je) {
+                            }
+                        }
+                    }
+
                     Log.i(LOG_TAG, "Persisting " + addons.length() + " add-ons.");
                     profileCache.setJSONForAddons(addons);
 
                     JSONObject prefs = json.getJSONObject("prefs");
                     Log.i(LOG_TAG, "Persisting prefs.");
                     Iterator<?> keys = prefs.keys();
                     while (keys.hasNext()) {
                         String pref = (String) keys.next();
@@ -689,16 +722,81 @@ public class BrowserHealthRecorder imple
                 recordSearch(message.optString("identifier", null), message.getString("location"));
                 return;
             }
         } catch (Exception e) {
             Log.e(LOG_TAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        switch (action) {
+            case SwitchBoard.ACTION_CONFIG_FETCHED:
+                Log.d(LOG_TAG, "Handle the new experiments.");
+                // Get the list of active experiments.
+                List<String> experiments = SwitchBoard.getActiveExperiments(context);
+
+                // We need to figure out which ones are new and which ones were removed. Convert
+                // the active experiments to the fake add-on names for easier lookup.
+                ArrayList<String> addToProfile = new ArrayList<String>();
+                for (String experiment : experiments) {
+                    addToProfile.add(experiment + "@experiments.mozilla.org");
+                }
+
+                // Create a list of add-ons(experiments) we need to remove.
+                ArrayList<String> removeFromProfile = new ArrayList<String>();
+
+                // Loop over the current profile set of add-ons, and figure out
+                // which add-ons (experiments) are new and which need to be removed.
+                JSONObject addons = this.profileCache.getAddonsJSON();
+                Iterator<?> keys = addons.keys();
+                while (keys.hasNext()) {
+                    String addon = (String) keys.next();
+                    if (addon.endsWith("@experiments.mozilla.org")) {
+                        if (addToProfile.contains(addon)) {
+                            // This experiment is already in the profile. We don't need to add it again.
+                            addToProfile.remove(addon);
+                        } else {
+                            // The active set of experiments does not include this fake add-on.
+                            removeFromProfile.add(addon);
+                        }
+                    }
+                }
+
+                // If we don't have any changes, exit early.
+                if (addToProfile.isEmpty() && removeFromProfile.isEmpty()) {
+                    return;
+                }
+
+                // Add the newly active experiments into the profile.
+                for (String fakeName : addToProfile) {
+                    try {
+                        // Create a dummy JSON object for the experiment.
+                        JSONObject fakeAddon = new JSONObject();
+                        fakeAddon.put("type", "experiment");
+                        this.onAddonChanged(fakeName, fakeAddon);
+                        Log.d(LOG_TAG, "Add this experiment: " + fakeName);
+                    } catch (JSONException je) {
+                    }
+                }
+
+                // Remove experiments that are no longer active from the profile.
+                for (String fakeName : removeFromProfile) {
+                    this.onAddonUninstalling(fakeName);
+                    Log.d(LOG_TAG, "Remove this experiment: " + fakeName);
+                }
+
+                // Something changed, so update the environment.
+                this.onEnvironmentChanged();
+                break;
+        }
+    }
+
     /*
      * Searches.
      */
 
     public static final String MEASUREMENT_NAME_SEARCH_COUNTS = "org.mozilla.searches.counts";
     public static final int MEASUREMENT_VERSION_SEARCH_COUNTS = 6;
 
     public static final Set<String> SEARCH_LOCATIONS = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(new String[] {
--- a/mobile/android/base/health/HealthRecorder.java
+++ b/mobile/android/base/health/HealthRecorder.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.health;
 
+import android.content.Context;
 import android.content.SharedPreferences;
 
 import org.json.JSONObject;
 
 /**
  * HealthRecorder is an interface into the Firefox Health Report storage system.
  */
 public interface HealthRecorder {
@@ -28,12 +29,12 @@ public interface HealthRecorder {
     public void recordSessionEnd(String reason, SharedPreferences.Editor editor, final int environment);
 
     public void onAppLocaleChanged(String to);
     public void onAddonChanged(String id, JSONObject json);
     public void onAddonUninstalling(String id);
     public void onEnvironmentChanged();
     public void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason);
 
-    public void close();
+    public void close(final Context context);
 
     public void processDelayed();
 }
--- a/mobile/android/base/health/StubbedHealthRecorder.java
+++ b/mobile/android/base/health/StubbedHealthRecorder.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.health;
 
+import android.content.Context;
 import android.content.SharedPreferences;
 
 import org.json.JSONObject;
 
 /**
  * StubbedHealthRecorder is an implementation of HealthRecorder that does (you guessed it!)
  * nothing.
  */
@@ -40,13 +41,13 @@ public class StubbedHealthRecorder imple
     @Override
     public void onAddonUninstalling(String id) { }
     @Override
     public void onEnvironmentChanged() { }
     @Override
     public void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason) { }
 
     @Override
-    public void close() { }
+    public void close(final Context context) { }
 
     @Override
     public void processDelayed() { }
 }
--- a/mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java
+++ b/mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java
@@ -17,30 +17,34 @@ package com.keepsafe.switchboard;
 
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.ProtocolException;
 import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Locale;
 import java.util.MissingResourceException;
 import java.util.UUID;
 import java.util.zip.CRC32;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Build;
+import android.support.v4.content.LocalBroadcastManager;
 import android.util.Log;
 
-
 /**
  * SwitchBoard is the core class of the KeepSafe Switchboard mobile A/B testing framework.
  * This class provides a bunch of static methods that can be used in your app to run A/B tests. 
  * 
  * The SwitchBoard supports production and staging environment. 
  * 
  * For usage <code>initDefaultServerUrls</code> for first time usage. Server URLs can be updates from
  * a remote location with <code>initConfigServerUrl</code>.
@@ -61,17 +65,18 @@ public class SwitchBoard {
 	public static boolean DEBUG = true;
 	
 	/** Production server to update the remote server URLs. http://staging.domain/path_to/SwitchboardURLs.php */
 	private static String DYNAMIC_CONFIG_SERVER_URL_UPDATE;
 	
 	/** Production server for getting the actual config file. http://staging.domain/path_to/SwitchboardDriver.php */
 	private static String DYNAMIC_CONFIG_SERVER_DEFAULT_URL;
 	
-	
+	public static final String ACTION_CONFIG_FETCHED = ".SwitchBoard.CONFIG_FETCHED";
+
 	private static final String kUpdateServerUrl = "updateServerUrl";
 	private static final String kConfigServerUrl = "configServerUrl";
 	
 	private static final String IS_EXPERIMENT_ACTIVE = "isActive";
 	private static final String EXPERIMENT_VALUES = "values";
 	
 	
 	/**
@@ -224,16 +229,20 @@ public class SwitchBoard {
 				//store experiments in shared prefs (one variable)
 				if(serverConfig != null)
 					Preferences.setDynamicConfigJson(c, serverConfig);
 			}
 			
 		} catch (NullPointerException e) {
 			e.printStackTrace();
 		}
+
+		//notify listeners that the config fetch has completed
+		Intent i = new Intent(ACTION_CONFIG_FETCHED);
+		LocalBroadcastManager.getInstance(c).sendBroadcast(i);
 	}
 
 	public static boolean isInBucket(Context c, int low, int high) {
 		int userBucket = getUserBucket(c);
 		if (userBucket >= low && userBucket < high)
 			return true;
 		else
 			return false;
@@ -284,16 +293,47 @@ public class SwitchBoard {
 		
 			//return false when JSON fails
 			return defaultReturnVal;
 		}
 		
 	}
 	
 	/**
+	 * @returns a list of all active experiments.
+	 */
+	public static List<String> getActiveExperiments(Context c) {
+		ArrayList<String> returnList = new ArrayList<String>();
+
+		// lookup experiment in config
+		String config = Preferences.getDynamicConfigJson(c);
+
+		// if it does not exist
+		if (config == null) {
+			return returnList;
+		}
+
+		try {
+			JSONObject experiments = new JSONObject(config);
+			Iterator<?> iter = experiments.keys();
+			while (iter.hasNext()) {
+				String key = (String)iter.next();
+				JSONObject experiment = experiments.getJSONObject(key);
+				if (experiment.getBoolean(IS_EXPERIMENT_ACTIVE)) {
+					returnList.add(key);
+				}
+			}
+		} catch (JSONException e) {
+			// Something went wrong!
+		}
+
+		return returnList;
+	}
+
+	/**
 	 * Checks if a certain experiment exists. 
 	 * @param c ApplicationContext
 	 * @param experimentName Name of the experiment
 	 * @return true when experiment exists
 	 */
 	public static boolean hasExperimentValues(Context c, String experimentName) {
 		if(getExperimentValueFromJson(c, experimentName) == null)
 			return false;