Merge m-c to inbound a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 05 Jan 2015 17:19:38 -0800
changeset 222164 337faa3baf86d32dece801d3feb57c4649c9cb00
parent 222163 fb1b266828a6d30281d866874b1989a6f111a253 (current diff)
parent 222089 2a193b7f395c8e6f3c21e83777ce2f540e4c04fe (diff)
child 222165 c469aa0c565016a9d47fd55eb7bbc5e94b5abe7f
push id28059
push userryanvm@gmail.com
push dateTue, 06 Jan 2015 15:53:01 +0000
treeherdermozilla-central@4d91c33b351c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound a=merge
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e0c735ec89df011ea7dd435087a9045ecff9ff9e">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="4ceeff19086b2a2955f044ad923dcfa63a293de3"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="b77e0d56d197e0ee02d801a25c784130d888c9db"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5ddb92ff090d40d2d5339e5f07b3a0ce10885462"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f59d8671fff36da22236a4b270b93195889b7ff1"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="83760d213fb3bec7b4117d266fcfbf6fe2ba14ab"/>
   <project name="device/common" path="device/common" revision="6a2995683de147791e516aae2ccb31fdfbe2ad30"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,23 +14,23 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="4ceeff19086b2a2955f044ad923dcfa63a293de3"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="b77e0d56d197e0ee02d801a25c784130d888c9db"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="6fa7a4936414ceb4055fd27f7a30e76790f834fb"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5ddb92ff090d40d2d5339e5f07b3a0ce10885462"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f59d8671fff36da22236a4b270b93195889b7ff1"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="4ceeff19086b2a2955f044ad923dcfa63a293de3"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="b77e0d56d197e0ee02d801a25c784130d888c9db"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5ddb92ff090d40d2d5339e5f07b3a0ce10885462"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f59d8671fff36da22236a4b270b93195889b7ff1"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e0c735ec89df011ea7dd435087a9045ecff9ff9e">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="4ceeff19086b2a2955f044ad923dcfa63a293de3"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="b77e0d56d197e0ee02d801a25c784130d888c9db"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5ddb92ff090d40d2d5339e5f07b3a0ce10885462"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f59d8671fff36da22236a4b270b93195889b7ff1"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="ac6eb97a37035c09fb5ede0852f0881e9aadf9ad"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="737f591c5f95477148d26602c7be56cbea0cdeb9"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="51da9b1981be481b92a59a826d4d78dc73d0989a"/>
   <project name="device/common" path="device/common" revision="798a3664597e6041985feab9aef42e98d458bc3d"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,23 +14,23 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="4ceeff19086b2a2955f044ad923dcfa63a293de3"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="b77e0d56d197e0ee02d801a25c784130d888c9db"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="6fa7a4936414ceb4055fd27f7a30e76790f834fb"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5ddb92ff090d40d2d5339e5f07b3a0ce10885462"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f59d8671fff36da22236a4b270b93195889b7ff1"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e0c735ec89df011ea7dd435087a9045ecff9ff9e">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="4ceeff19086b2a2955f044ad923dcfa63a293de3"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="b77e0d56d197e0ee02d801a25c784130d888c9db"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5ddb92ff090d40d2d5339e5f07b3a0ce10885462"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f59d8671fff36da22236a4b270b93195889b7ff1"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="a32003194f707f66a2d8cdb913ed1869f1926c5d"/>
   <project name="device/common" path="device/common" revision="96d4d2006c4fcb2f19a3fa47ab10cb409faa017b"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="4ceeff19086b2a2955f044ad923dcfa63a293de3"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="b77e0d56d197e0ee02d801a25c784130d888c9db"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5ddb92ff090d40d2d5339e5f07b3a0ce10885462"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f59d8671fff36da22236a4b270b93195889b7ff1"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "ff1669229bb0dae8647a88dfe82a683e88f8c32b", 
+    "revision": "c175a29477e68e70142687587844423a75dcf4cc", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,21 +12,21 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="4ceeff19086b2a2955f044ad923dcfa63a293de3"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="b77e0d56d197e0ee02d801a25c784130d888c9db"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5ddb92ff090d40d2d5339e5f07b3a0ce10885462"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f59d8671fff36da22236a4b270b93195889b7ff1"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
   <project name="platform/development" path="development" revision="2460485184bc8535440bb63876d4e63ec1b4770c"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="4ceeff19086b2a2955f044ad923dcfa63a293de3"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="b77e0d56d197e0ee02d801a25c784130d888c9db"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="4ceeff19086b2a2955f044ad923dcfa63a293de3"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="b77e0d56d197e0ee02d801a25c784130d888c9db"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5ddb92ff090d40d2d5339e5f07b3a0ce10885462"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f59d8671fff36da22236a4b270b93195889b7ff1"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,22 +12,22 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="4ceeff19086b2a2955f044ad923dcfa63a293de3"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="b77e0d56d197e0ee02d801a25c784130d888c9db"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5ddb92ff090d40d2d5339e5f07b3a0ce10885462"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f59d8671fff36da22236a4b270b93195889b7ff1"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="e0a9ac010df3afaa47ba107192c05ac8b5516435"/>
   <project name="platform/development" path="development" revision="a384622f5fcb1d2bebb9102591ff7ae91fe8ed2d"/>
   <project name="device/common" path="device/common" revision="7c65ea240157763b8ded6154a17d3c033167afb7"/>
   <project name="device/sample" path="device/sample" revision="c328f3d4409db801628861baa8d279fb8855892f"/>
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -11,531 +11,21 @@ var loop = loop || {};
 loop.conversation = (function(mozL10n) {
   "use strict";
 
   var sharedViews = loop.shared.views;
   var sharedMixins = loop.shared.mixins;
   var sharedModels = loop.shared.models;
   var sharedActions = loop.shared.actions;
 
+  var IncomingConversationView = loop.conversationViews.IncomingConversationView;
   var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
   var CallIdentifierView = loop.conversationViews.CallIdentifierView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
-
-  // Matches strings of the form "<nonspaces>@<nonspaces>" or "+<digits>"
-  var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
-
-  var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
-    mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
-
-    propTypes: {
-      model: React.PropTypes.object.isRequired,
-      video: React.PropTypes.bool.isRequired
-    },
-
-    getDefaultProps: function() {
-      return {
-        showMenu: false,
-        video: true
-      };
-    },
-
-    clickHandler: function(e) {
-      var target = e.target;
-      if (!target.classList.contains('btn-chevron')) {
-        this._hideDeclineMenu();
-      }
-    },
-
-    _handleAccept: function(callType) {
-      return function() {
-        this.props.model.set("selectedCallType", callType);
-        this.props.model.trigger("accept");
-      }.bind(this);
-    },
-
-    _handleDecline: function() {
-      this.props.model.trigger("decline");
-    },
-
-    _handleDeclineBlock: function(e) {
-      this.props.model.trigger("declineAndBlock");
-      /* Prevent event propagation
-       * stop the click from reaching parent element */
-      return false;
-    },
-
-    /*
-     * Generate props for <AcceptCallButton> component based on
-     * incoming call type. An incoming video call will render a video
-     * answer button primarily, an audio call will flip them.
-     **/
-    _answerModeProps: function() {
-      var videoButton = {
-        handler: this._handleAccept("audio-video"),
-        className: "fx-embedded-btn-icon-video",
-        tooltip: "incoming_call_accept_audio_video_tooltip"
-      };
-      var audioButton = {
-        handler: this._handleAccept("audio"),
-        className: "fx-embedded-btn-audio-small",
-        tooltip: "incoming_call_accept_audio_only_tooltip"
-      };
-      var props = {};
-      props.primary = videoButton;
-      props.secondary = audioButton;
-
-      // When video is not enabled on this call, we swap the buttons around.
-      if (!this.props.video) {
-        audioButton.className = "fx-embedded-btn-icon-audio";
-        videoButton.className = "fx-embedded-btn-video-small";
-        props.primary = audioButton;
-        props.secondary = videoButton;
-      }
-
-      return props;
-    },
-
-    render: function() {
-      /* jshint ignore:start */
-      var dropdownMenuClassesDecline = React.addons.classSet({
-        "native-dropdown-menu": true,
-        "conversation-window-dropdown": true,
-        "visually-hidden": !this.state.showMenu
-      });
-
-      return (
-        React.DOM.div({className: "call-window"}, 
-          CallIdentifierView({video: this.props.video, 
-            peerIdentifier: this.props.model.getCallIdentifier(), 
-            urlCreationDate: this.props.model.get("urlCreationDate"), 
-            showIcons: true}), 
-
-          React.DOM.div({className: "btn-group call-action-group"}, 
-
-            React.DOM.div({className: "fx-embedded-call-button-spacer"}), 
-
-            React.DOM.div({className: "btn-chevron-menu-group"}, 
-              React.DOM.div({className: "btn-group-chevron"}, 
-                React.DOM.div({className: "btn-group"}, 
-
-                  React.DOM.button({className: "btn btn-decline", 
-                          onClick: this._handleDecline}, 
-                    mozL10n.get("incoming_call_cancel_button")
-                  ), 
-                  React.DOM.div({className: "btn-chevron", onClick: this.toggleDropdownMenu})
-                ), 
-
-                React.DOM.ul({className: dropdownMenuClassesDecline}, 
-                  React.DOM.li({className: "btn-block", onClick: this._handleDeclineBlock}, 
-                    mozL10n.get("incoming_call_cancel_and_block_button")
-                  )
-                )
-
-              )
-            ), 
-
-            React.DOM.div({className: "fx-embedded-call-button-spacer"}), 
-
-            AcceptCallButton({mode: this._answerModeProps()}), 
-
-            React.DOM.div({className: "fx-embedded-call-button-spacer"})
-
-          )
-        )
-      );
-      /* jshint ignore:end */
-    }
-  });
-
-  /**
-   * Incoming call view accept button, renders different primary actions
-   * (answer with video / with audio only) based on the props received
-   **/
-  var AcceptCallButton = React.createClass({displayName: 'AcceptCallButton',
-
-    propTypes: {
-      mode: React.PropTypes.object.isRequired,
-    },
-
-    render: function() {
-      var mode = this.props.mode;
-      return (
-        /* jshint ignore:start */
-        React.DOM.div({className: "btn-chevron-menu-group"}, 
-          React.DOM.div({className: "btn-group"}, 
-            React.DOM.button({className: "btn btn-accept", 
-                    onClick: mode.primary.handler, 
-                    title: mozL10n.get(mode.primary.tooltip)}, 
-              React.DOM.span({className: "fx-embedded-answer-btn-text"}, 
-                mozL10n.get("incoming_call_accept_button")
-              ), 
-              React.DOM.span({className: mode.primary.className})
-            ), 
-            React.DOM.div({className: mode.secondary.className, 
-                 onClick: mode.secondary.handler, 
-                 title: mozL10n.get(mode.secondary.tooltip)}
-            )
-          )
-        )
-        /* jshint ignore:end */
-      );
-    }
-  });
-
-  /**
-   * Something went wrong view. Displayed when there's a big problem.
-   *
-   * XXX Based on CallFailedView, but built specially until we flux-ify the
-   * incoming call views (bug 1088672).
-   */
-  var GenericFailureView = React.createClass({displayName: 'GenericFailureView',
-    mixins: [sharedMixins.AudioMixin],
-
-    propTypes: {
-      cancelCall: React.PropTypes.func.isRequired
-    },
-
-    componentDidMount: function() {
-      this.play("failure");
-    },
-
-    render: function() {
-      document.title = mozL10n.get("generic_failure_title");
-
-      return (
-        React.DOM.div({className: "call-window"}, 
-          React.DOM.h2(null, mozL10n.get("generic_failure_title")), 
-
-          React.DOM.div({className: "btn-group call-action-group"}, 
-            React.DOM.button({className: "btn btn-cancel", 
-                    onClick: this.props.cancelCall}, 
-              mozL10n.get("cancel_button")
-            )
-          )
-        )
-      );
-    }
-  });
-
-  /**
-   * This view manages the incoming conversation views - from
-   * call initiation through to the actual conversation and call end.
-   *
-   * At the moment, it does more than that, these parts need refactoring out.
-   */
-  var IncomingConversationView = React.createClass({displayName: 'IncomingConversationView',
-    mixins: [sharedMixins.AudioMixin, sharedMixins.WindowCloseMixin],
-
-    propTypes: {
-      client: React.PropTypes.instanceOf(loop.Client).isRequired,
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
-                         .isRequired,
-      sdk: React.PropTypes.object.isRequired,
-      conversationAppStore: React.PropTypes.instanceOf(
-        loop.store.ConversationAppStore).isRequired,
-      feedbackStore:
-        React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired
-    },
-
-    getInitialState: function() {
-      return {
-        callFailed: false, // XXX this should be removed when bug 1047410 lands.
-        callStatus: "start"
-      };
-    },
-
-    componentDidMount: function() {
-      this.props.conversation.on("accept", this.accept, this);
-      this.props.conversation.on("decline", this.decline, this);
-      this.props.conversation.on("declineAndBlock", this.declineAndBlock, this);
-      this.props.conversation.on("call:accepted", this.accepted, this);
-      this.props.conversation.on("change:publishedStream", this._checkConnected, this);
-      this.props.conversation.on("change:subscribedStream", this._checkConnected, this);
-      this.props.conversation.on("session:ended", this.endCall, this);
-      this.props.conversation.on("session:peer-hungup", this._onPeerHungup, this);
-      this.props.conversation.on("session:network-disconnected", this._onNetworkDisconnected, this);
-      this.props.conversation.on("session:connection-error", this._notifyError, this);
-
-      this.setupIncomingCall();
-    },
-
-    componentDidUnmount: function() {
-      this.props.conversation.off(null, null, this);
-    },
-
-    render: function() {
-      switch (this.state.callStatus) {
-        case "start": {
-          document.title = mozL10n.get("incoming_call_title2");
-
-          // XXX Don't render anything initially, though this should probably
-          // be some sort of pending view, whilst we connect the websocket.
-          return null;
-        }
-        case "incoming": {
-          document.title = mozL10n.get("incoming_call_title2");
-
-          return (
-            IncomingCallView({
-              model: this.props.conversation, 
-              video: this.props.conversation.hasVideoStream("incoming")}
-            )
-          );
-        }
-        case "connected": {
-          document.title = this.props.conversation.getCallIdentifier();
-
-          var callType = this.props.conversation.get("selectedCallType");
-
-          return (
-            sharedViews.ConversationView({
-              initiate: true, 
-              sdk: this.props.sdk, 
-              model: this.props.conversation, 
-              video: {enabled: callType !== "audio"}}
-            )
-          );
-        }
-        case "end": {
-          // XXX To be handled with the "failed" view state when bug 1047410 lands
-          if (this.state.callFailed) {
-            return GenericFailureView({
-              cancelCall: this.closeWindow.bind(this)}
-            );
-          }
-
-          document.title = mozL10n.get("conversation_has_ended");
-
-          this.play("terminated");
-
-          return (
-            sharedViews.FeedbackView({
-              feedbackStore: this.props.feedbackStore, 
-              onAfterFeedbackReceived: this.closeWindow.bind(this)}
-            )
-          );
-        }
-        case "close": {
-          this.closeWindow();
-          return (React.DOM.div(null));
-        }
-      }
-    },
-
-    /**
-     * Notify the user that the connection was not possible
-     * @param {{code: number, message: string}} error
-     */
-    _notifyError: function(error) {
-      // XXX Not the ideal response, but bug 1047410 will be replacing
-      // this by better "call failed" UI.
-      console.error(error);
-      this.setState({callFailed: true, callStatus: "end"});
-    },
-
-    /**
-     * Peer hung up. Notifies the user and ends the call.
-     *
-     * Event properties:
-     * - {String} connectionId: OT session id
-     */
-    _onPeerHungup: function() {
-      this.setState({callFailed: false, callStatus: "end"});
-    },
-
-    /**
-     * Network disconnected. Notifies the user and ends the call.
-     */
-    _onNetworkDisconnected: function() {
-      // XXX Not the ideal response, but bug 1047410 will be replacing
-      // this by better "call failed" UI.
-      this.setState({callFailed: true, callStatus: "end"});
-    },
-
-    /**
-     * Incoming call route.
-     */
-    setupIncomingCall: function() {
-      navigator.mozLoop.startAlerting();
-
-      // XXX This is a hack until we rework for the flux model in bug 1088672.
-      var callData = this.props.conversationAppStore.getStoreState().windowData;
-
-      this.props.conversation.setIncomingSessionData(callData);
-      this._setupWebSocket();
-    },
-
-    /**
-     * Starts the actual conversation
-     */
-    accepted: function() {
-      this.setState({callStatus: "connected"});
-    },
-
-    /**
-     * Moves the call to the end state
-     */
-    endCall: function() {
-      navigator.mozLoop.calls.clearCallInProgress(
-        this.props.conversation.get("windowId"));
-      this.setState({callStatus: "end"});
-    },
-
-    /**
-     * Used to set up the web socket connection and navigate to the
-     * call view if appropriate.
-     */
-    _setupWebSocket: function() {
-      this._websocket = new loop.CallConnectionWebSocket({
-        url: this.props.conversation.get("progressURL"),
-        websocketToken: this.props.conversation.get("websocketToken"),
-        callId: this.props.conversation.get("callId"),
-      });
-      this._websocket.promiseConnect().then(function(progressStatus) {
-        this.setState({
-          callStatus: progressStatus === "terminated" ? "close" : "incoming"
-        });
-      }.bind(this), function() {
-        this._handleSessionError();
-        return;
-      }.bind(this));
-
-      this._websocket.on("progress", this._handleWebSocketProgress, this);
-    },
-
-    /**
-     * Checks if the streams have been connected, and notifies the
-     * websocket that the media is now connected.
-     */
-    _checkConnected: function() {
-      // Check we've had both local and remote streams connected before
-      // sending the media up message.
-      if (this.props.conversation.streamsConnected()) {
-        this._websocket.mediaUp();
-      }
-    },
-
-    /**
-     * Used to receive websocket progress and to determine how to handle
-     * it if appropraite.
-     * If we add more cases here, then we should refactor this function.
-     *
-     * @param {Object} progressData The progress data from the websocket.
-     * @param {String} previousState The previous state from the websocket.
-     */
-    _handleWebSocketProgress: function(progressData, previousState) {
-      // We only care about the terminated state at the moment.
-      if (progressData.state !== "terminated")
-        return;
-
-      // XXX This would be nicer in the _abortIncomingCall function, but we need to stop
-      // it here for now due to server-side issues that are being fixed in bug 1088351.
-      // This is before the abort call to ensure that it happens before the window is
-      // closed.
-      navigator.mozLoop.stopAlerting();
-
-      // If we hit any of the termination reasons, and the user hasn't accepted
-      // then it seems reasonable to close the window/abort the incoming call.
-      //
-      // If the user has accepted the call, and something's happened, display
-      // the call failed view.
-      //
-      // https://wiki.mozilla.org/Loop/Architecture/MVP#Termination_Reasons
-      if (previousState === "init" || previousState === "alerting") {
-        this._abortIncomingCall();
-      } else {
-        this.setState({callFailed: true, callStatus: "end"});
-      }
-
-    },
-
-    /**
-     * Silently aborts an incoming call - stops the alerting, and
-     * closes the websocket.
-     */
-    _abortIncomingCall: function() {
-      this._websocket.close();
-      // Having a timeout here lets the logging for the websocket complete and be
-      // displayed on the console if both are on.
-      setTimeout(this.closeWindow, 0);
-    },
-
-    /**
-     * Accepts an incoming call.
-     */
-    accept: function() {
-      navigator.mozLoop.stopAlerting();
-      this._websocket.accept();
-      this.props.conversation.accepted();
-    },
-
-    /**
-     * Declines a call and handles closing of the window.
-     */
-    _declineCall: function() {
-      this._websocket.decline();
-      navigator.mozLoop.calls.clearCallInProgress(
-        this.props.conversation.get("windowId"));
-      this._websocket.close();
-      // Having a timeout here lets the logging for the websocket complete and be
-      // displayed on the console if both are on.
-      setTimeout(this.closeWindow, 0);
-    },
-
-    /**
-     * Declines an incoming call.
-     */
-    decline: function() {
-      navigator.mozLoop.stopAlerting();
-      this._declineCall();
-    },
-
-    /**
-     * Decline and block an incoming call
-     * @note:
-     * - loopToken is the callUrl identifier. It gets set in the panel
-     *   after a callUrl is received
-     */
-    declineAndBlock: function() {
-      navigator.mozLoop.stopAlerting();
-      var token = this.props.conversation.get("callToken");
-      var callerId = this.props.conversation.get("callerId");
-
-      // If this is a direct call, we'll need to block the caller directly.
-      if (callerId && EMAIL_OR_PHONE_RE.test(callerId)) {
-        navigator.mozLoop.calls.blockDirectCaller(callerId, function(err) {
-          // XXX The conversation window will be closed when this cb is triggered
-          // figure out if there is a better way to report the error to the user
-          // (bug 1103150).
-          console.log(err.fileName + ":" + err.lineNumber + ": " + err.message);
-        });
-      } else {
-        this.props.client.deleteCallUrl(token,
-          this.props.conversation.get("sessionType"),
-          function(error) {
-            // XXX The conversation window will be closed when this cb is triggered
-            // figure out if there is a better way to report the error to the user
-            // (bug 1048909).
-            console.log(error);
-          });
-      }
-
-      this._declineCall();
-    },
-
-    /**
-     * Handles a error starting the session
-     */
-    _handleSessionError: function() {
-      // XXX Not the ideal response, but bug 1047410 will be replacing
-      // this by better "call failed" UI.
-      console.error("Failed initiating the call session.");
-    },
-  });
+  var GenericFailureView = loop.conversationViews.GenericFailureView;
 
   /**
    * Master controller view for handling if incoming or outgoing calls are
    * in progress, and hence, which view to display.
    */
   var AppControllerView = React.createClass({displayName: 'AppControllerView',
     mixins: [Backbone.Events, sharedMixins.WindowCloseMixin],
 
@@ -706,16 +196,13 @@ loop.conversation = (function(mozL10n) {
 
     dispatcher.dispatch(new sharedActions.GetWindowData({
       windowId: windowId
     }));
   }
 
   return {
     AppControllerView: AppControllerView,
-    IncomingConversationView: IncomingConversationView,
-    IncomingCallView: IncomingCallView,
-    GenericFailureView: GenericFailureView,
     init: init
   };
 })(document.mozL10n);
 
 document.addEventListener('DOMContentLoaded', loop.conversation.init);
--- a/browser/components/loop/content/js/conversation.jsx
+++ b/browser/components/loop/content/js/conversation.jsx
@@ -11,531 +11,21 @@ var loop = loop || {};
 loop.conversation = (function(mozL10n) {
   "use strict";
 
   var sharedViews = loop.shared.views;
   var sharedMixins = loop.shared.mixins;
   var sharedModels = loop.shared.models;
   var sharedActions = loop.shared.actions;
 
+  var IncomingConversationView = loop.conversationViews.IncomingConversationView;
   var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
   var CallIdentifierView = loop.conversationViews.CallIdentifierView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
-
-  // Matches strings of the form "<nonspaces>@<nonspaces>" or "+<digits>"
-  var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
-
-  var IncomingCallView = React.createClass({
-    mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
-
-    propTypes: {
-      model: React.PropTypes.object.isRequired,
-      video: React.PropTypes.bool.isRequired
-    },
-
-    getDefaultProps: function() {
-      return {
-        showMenu: false,
-        video: true
-      };
-    },
-
-    clickHandler: function(e) {
-      var target = e.target;
-      if (!target.classList.contains('btn-chevron')) {
-        this._hideDeclineMenu();
-      }
-    },
-
-    _handleAccept: function(callType) {
-      return function() {
-        this.props.model.set("selectedCallType", callType);
-        this.props.model.trigger("accept");
-      }.bind(this);
-    },
-
-    _handleDecline: function() {
-      this.props.model.trigger("decline");
-    },
-
-    _handleDeclineBlock: function(e) {
-      this.props.model.trigger("declineAndBlock");
-      /* Prevent event propagation
-       * stop the click from reaching parent element */
-      return false;
-    },
-
-    /*
-     * Generate props for <AcceptCallButton> component based on
-     * incoming call type. An incoming video call will render a video
-     * answer button primarily, an audio call will flip them.
-     **/
-    _answerModeProps: function() {
-      var videoButton = {
-        handler: this._handleAccept("audio-video"),
-        className: "fx-embedded-btn-icon-video",
-        tooltip: "incoming_call_accept_audio_video_tooltip"
-      };
-      var audioButton = {
-        handler: this._handleAccept("audio"),
-        className: "fx-embedded-btn-audio-small",
-        tooltip: "incoming_call_accept_audio_only_tooltip"
-      };
-      var props = {};
-      props.primary = videoButton;
-      props.secondary = audioButton;
-
-      // When video is not enabled on this call, we swap the buttons around.
-      if (!this.props.video) {
-        audioButton.className = "fx-embedded-btn-icon-audio";
-        videoButton.className = "fx-embedded-btn-video-small";
-        props.primary = audioButton;
-        props.secondary = videoButton;
-      }
-
-      return props;
-    },
-
-    render: function() {
-      /* jshint ignore:start */
-      var dropdownMenuClassesDecline = React.addons.classSet({
-        "native-dropdown-menu": true,
-        "conversation-window-dropdown": true,
-        "visually-hidden": !this.state.showMenu
-      });
-
-      return (
-        <div className="call-window">
-          <CallIdentifierView video={this.props.video}
-            peerIdentifier={this.props.model.getCallIdentifier()}
-            urlCreationDate={this.props.model.get("urlCreationDate")}
-            showIcons={true} />
-
-          <div className="btn-group call-action-group">
-
-            <div className="fx-embedded-call-button-spacer"></div>
-
-            <div className="btn-chevron-menu-group">
-              <div className="btn-group-chevron">
-                <div className="btn-group">
-
-                  <button className="btn btn-decline"
-                          onClick={this._handleDecline}>
-                    {mozL10n.get("incoming_call_cancel_button")}
-                  </button>
-                  <div className="btn-chevron" onClick={this.toggleDropdownMenu} />
-                </div>
-
-                <ul className={dropdownMenuClassesDecline}>
-                  <li className="btn-block" onClick={this._handleDeclineBlock}>
-                    {mozL10n.get("incoming_call_cancel_and_block_button")}
-                  </li>
-                </ul>
-
-              </div>
-            </div>
-
-            <div className="fx-embedded-call-button-spacer"></div>
-
-            <AcceptCallButton mode={this._answerModeProps()} />
-
-            <div className="fx-embedded-call-button-spacer"></div>
-
-          </div>
-        </div>
-      );
-      /* jshint ignore:end */
-    }
-  });
-
-  /**
-   * Incoming call view accept button, renders different primary actions
-   * (answer with video / with audio only) based on the props received
-   **/
-  var AcceptCallButton = React.createClass({
-
-    propTypes: {
-      mode: React.PropTypes.object.isRequired,
-    },
-
-    render: function() {
-      var mode = this.props.mode;
-      return (
-        /* jshint ignore:start */
-        <div className="btn-chevron-menu-group">
-          <div className="btn-group">
-            <button className="btn btn-accept"
-                    onClick={mode.primary.handler}
-                    title={mozL10n.get(mode.primary.tooltip)}>
-              <span className="fx-embedded-answer-btn-text">
-                {mozL10n.get("incoming_call_accept_button")}
-              </span>
-              <span className={mode.primary.className}></span>
-            </button>
-            <div className={mode.secondary.className}
-                 onClick={mode.secondary.handler}
-                 title={mozL10n.get(mode.secondary.tooltip)}>
-            </div>
-          </div>
-        </div>
-        /* jshint ignore:end */
-      );
-    }
-  });
-
-  /**
-   * Something went wrong view. Displayed when there's a big problem.
-   *
-   * XXX Based on CallFailedView, but built specially until we flux-ify the
-   * incoming call views (bug 1088672).
-   */
-  var GenericFailureView = React.createClass({
-    mixins: [sharedMixins.AudioMixin],
-
-    propTypes: {
-      cancelCall: React.PropTypes.func.isRequired
-    },
-
-    componentDidMount: function() {
-      this.play("failure");
-    },
-
-    render: function() {
-      document.title = mozL10n.get("generic_failure_title");
-
-      return (
-        <div className="call-window">
-          <h2>{mozL10n.get("generic_failure_title")}</h2>
-
-          <div className="btn-group call-action-group">
-            <button className="btn btn-cancel"
-                    onClick={this.props.cancelCall}>
-              {mozL10n.get("cancel_button")}
-            </button>
-          </div>
-        </div>
-      );
-    }
-  });
-
-  /**
-   * This view manages the incoming conversation views - from
-   * call initiation through to the actual conversation and call end.
-   *
-   * At the moment, it does more than that, these parts need refactoring out.
-   */
-  var IncomingConversationView = React.createClass({
-    mixins: [sharedMixins.AudioMixin, sharedMixins.WindowCloseMixin],
-
-    propTypes: {
-      client: React.PropTypes.instanceOf(loop.Client).isRequired,
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
-                         .isRequired,
-      sdk: React.PropTypes.object.isRequired,
-      conversationAppStore: React.PropTypes.instanceOf(
-        loop.store.ConversationAppStore).isRequired,
-      feedbackStore:
-        React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired
-    },
-
-    getInitialState: function() {
-      return {
-        callFailed: false, // XXX this should be removed when bug 1047410 lands.
-        callStatus: "start"
-      };
-    },
-
-    componentDidMount: function() {
-      this.props.conversation.on("accept", this.accept, this);
-      this.props.conversation.on("decline", this.decline, this);
-      this.props.conversation.on("declineAndBlock", this.declineAndBlock, this);
-      this.props.conversation.on("call:accepted", this.accepted, this);
-      this.props.conversation.on("change:publishedStream", this._checkConnected, this);
-      this.props.conversation.on("change:subscribedStream", this._checkConnected, this);
-      this.props.conversation.on("session:ended", this.endCall, this);
-      this.props.conversation.on("session:peer-hungup", this._onPeerHungup, this);
-      this.props.conversation.on("session:network-disconnected", this._onNetworkDisconnected, this);
-      this.props.conversation.on("session:connection-error", this._notifyError, this);
-
-      this.setupIncomingCall();
-    },
-
-    componentDidUnmount: function() {
-      this.props.conversation.off(null, null, this);
-    },
-
-    render: function() {
-      switch (this.state.callStatus) {
-        case "start": {
-          document.title = mozL10n.get("incoming_call_title2");
-
-          // XXX Don't render anything initially, though this should probably
-          // be some sort of pending view, whilst we connect the websocket.
-          return null;
-        }
-        case "incoming": {
-          document.title = mozL10n.get("incoming_call_title2");
-
-          return (
-            <IncomingCallView
-              model={this.props.conversation}
-              video={this.props.conversation.hasVideoStream("incoming")}
-            />
-          );
-        }
-        case "connected": {
-          document.title = this.props.conversation.getCallIdentifier();
-
-          var callType = this.props.conversation.get("selectedCallType");
-
-          return (
-            <sharedViews.ConversationView
-              initiate={true}
-              sdk={this.props.sdk}
-              model={this.props.conversation}
-              video={{enabled: callType !== "audio"}}
-            />
-          );
-        }
-        case "end": {
-          // XXX To be handled with the "failed" view state when bug 1047410 lands
-          if (this.state.callFailed) {
-            return <GenericFailureView
-              cancelCall={this.closeWindow.bind(this)}
-            />;
-          }
-
-          document.title = mozL10n.get("conversation_has_ended");
-
-          this.play("terminated");
-
-          return (
-            <sharedViews.FeedbackView
-              feedbackStore={this.props.feedbackStore}
-              onAfterFeedbackReceived={this.closeWindow.bind(this)}
-            />
-          );
-        }
-        case "close": {
-          this.closeWindow();
-          return (<div/>);
-        }
-      }
-    },
-
-    /**
-     * Notify the user that the connection was not possible
-     * @param {{code: number, message: string}} error
-     */
-    _notifyError: function(error) {
-      // XXX Not the ideal response, but bug 1047410 will be replacing
-      // this by better "call failed" UI.
-      console.error(error);
-      this.setState({callFailed: true, callStatus: "end"});
-    },
-
-    /**
-     * Peer hung up. Notifies the user and ends the call.
-     *
-     * Event properties:
-     * - {String} connectionId: OT session id
-     */
-    _onPeerHungup: function() {
-      this.setState({callFailed: false, callStatus: "end"});
-    },
-
-    /**
-     * Network disconnected. Notifies the user and ends the call.
-     */
-    _onNetworkDisconnected: function() {
-      // XXX Not the ideal response, but bug 1047410 will be replacing
-      // this by better "call failed" UI.
-      this.setState({callFailed: true, callStatus: "end"});
-    },
-
-    /**
-     * Incoming call route.
-     */
-    setupIncomingCall: function() {
-      navigator.mozLoop.startAlerting();
-
-      // XXX This is a hack until we rework for the flux model in bug 1088672.
-      var callData = this.props.conversationAppStore.getStoreState().windowData;
-
-      this.props.conversation.setIncomingSessionData(callData);
-      this._setupWebSocket();
-    },
-
-    /**
-     * Starts the actual conversation
-     */
-    accepted: function() {
-      this.setState({callStatus: "connected"});
-    },
-
-    /**
-     * Moves the call to the end state
-     */
-    endCall: function() {
-      navigator.mozLoop.calls.clearCallInProgress(
-        this.props.conversation.get("windowId"));
-      this.setState({callStatus: "end"});
-    },
-
-    /**
-     * Used to set up the web socket connection and navigate to the
-     * call view if appropriate.
-     */
-    _setupWebSocket: function() {
-      this._websocket = new loop.CallConnectionWebSocket({
-        url: this.props.conversation.get("progressURL"),
-        websocketToken: this.props.conversation.get("websocketToken"),
-        callId: this.props.conversation.get("callId"),
-      });
-      this._websocket.promiseConnect().then(function(progressStatus) {
-        this.setState({
-          callStatus: progressStatus === "terminated" ? "close" : "incoming"
-        });
-      }.bind(this), function() {
-        this._handleSessionError();
-        return;
-      }.bind(this));
-
-      this._websocket.on("progress", this._handleWebSocketProgress, this);
-    },
-
-    /**
-     * Checks if the streams have been connected, and notifies the
-     * websocket that the media is now connected.
-     */
-    _checkConnected: function() {
-      // Check we've had both local and remote streams connected before
-      // sending the media up message.
-      if (this.props.conversation.streamsConnected()) {
-        this._websocket.mediaUp();
-      }
-    },
-
-    /**
-     * Used to receive websocket progress and to determine how to handle
-     * it if appropraite.
-     * If we add more cases here, then we should refactor this function.
-     *
-     * @param {Object} progressData The progress data from the websocket.
-     * @param {String} previousState The previous state from the websocket.
-     */
-    _handleWebSocketProgress: function(progressData, previousState) {
-      // We only care about the terminated state at the moment.
-      if (progressData.state !== "terminated")
-        return;
-
-      // XXX This would be nicer in the _abortIncomingCall function, but we need to stop
-      // it here for now due to server-side issues that are being fixed in bug 1088351.
-      // This is before the abort call to ensure that it happens before the window is
-      // closed.
-      navigator.mozLoop.stopAlerting();
-
-      // If we hit any of the termination reasons, and the user hasn't accepted
-      // then it seems reasonable to close the window/abort the incoming call.
-      //
-      // If the user has accepted the call, and something's happened, display
-      // the call failed view.
-      //
-      // https://wiki.mozilla.org/Loop/Architecture/MVP#Termination_Reasons
-      if (previousState === "init" || previousState === "alerting") {
-        this._abortIncomingCall();
-      } else {
-        this.setState({callFailed: true, callStatus: "end"});
-      }
-
-    },
-
-    /**
-     * Silently aborts an incoming call - stops the alerting, and
-     * closes the websocket.
-     */
-    _abortIncomingCall: function() {
-      this._websocket.close();
-      // Having a timeout here lets the logging for the websocket complete and be
-      // displayed on the console if both are on.
-      setTimeout(this.closeWindow, 0);
-    },
-
-    /**
-     * Accepts an incoming call.
-     */
-    accept: function() {
-      navigator.mozLoop.stopAlerting();
-      this._websocket.accept();
-      this.props.conversation.accepted();
-    },
-
-    /**
-     * Declines a call and handles closing of the window.
-     */
-    _declineCall: function() {
-      this._websocket.decline();
-      navigator.mozLoop.calls.clearCallInProgress(
-        this.props.conversation.get("windowId"));
-      this._websocket.close();
-      // Having a timeout here lets the logging for the websocket complete and be
-      // displayed on the console if both are on.
-      setTimeout(this.closeWindow, 0);
-    },
-
-    /**
-     * Declines an incoming call.
-     */
-    decline: function() {
-      navigator.mozLoop.stopAlerting();
-      this._declineCall();
-    },
-
-    /**
-     * Decline and block an incoming call
-     * @note:
-     * - loopToken is the callUrl identifier. It gets set in the panel
-     *   after a callUrl is received
-     */
-    declineAndBlock: function() {
-      navigator.mozLoop.stopAlerting();
-      var token = this.props.conversation.get("callToken");
-      var callerId = this.props.conversation.get("callerId");
-
-      // If this is a direct call, we'll need to block the caller directly.
-      if (callerId && EMAIL_OR_PHONE_RE.test(callerId)) {
-        navigator.mozLoop.calls.blockDirectCaller(callerId, function(err) {
-          // XXX The conversation window will be closed when this cb is triggered
-          // figure out if there is a better way to report the error to the user
-          // (bug 1103150).
-          console.log(err.fileName + ":" + err.lineNumber + ": " + err.message);
-        });
-      } else {
-        this.props.client.deleteCallUrl(token,
-          this.props.conversation.get("sessionType"),
-          function(error) {
-            // XXX The conversation window will be closed when this cb is triggered
-            // figure out if there is a better way to report the error to the user
-            // (bug 1048909).
-            console.log(error);
-          });
-      }
-
-      this._declineCall();
-    },
-
-    /**
-     * Handles a error starting the session
-     */
-    _handleSessionError: function() {
-      // XXX Not the ideal response, but bug 1047410 will be replacing
-      // this by better "call failed" UI.
-      console.error("Failed initiating the call session.");
-    },
-  });
+  var GenericFailureView = loop.conversationViews.GenericFailureView;
 
   /**
    * Master controller view for handling if incoming or outgoing calls are
    * in progress, and hence, which view to display.
    */
   var AppControllerView = React.createClass({
     mixins: [Backbone.Events, sharedMixins.WindowCloseMixin],
 
@@ -706,16 +196,13 @@ loop.conversation = (function(mozL10n) {
 
     dispatcher.dispatch(new sharedActions.GetWindowData({
       windowId: windowId
     }));
   }
 
   return {
     AppControllerView: AppControllerView,
-    IncomingConversationView: IncomingConversationView,
-    IncomingCallView: IncomingCallView,
-    GenericFailureView: GenericFailureView,
     init: init
   };
 })(document.mozL10n);
 
 document.addEventListener('DOMContentLoaded', loop.conversation.init);
--- a/browser/components/loop/content/js/conversationViews.js
+++ b/browser/components/loop/content/js/conversationViews.js
@@ -10,16 +10,17 @@ var loop = loop || {};
 loop.conversationViews = (function(mozL10n) {
 
   var CALL_STATES = loop.store.CALL_STATES;
   var CALL_TYPES = loop.shared.utils.CALL_TYPES;
   var sharedActions = loop.shared.actions;
   var sharedUtils = loop.shared.utils;
   var sharedViews = loop.shared.views;
   var sharedMixins = loop.shared.mixins;
+  var sharedModels = loop.shared.models;
 
   // This duplicates a similar function in contacts.jsx that isn't used in the
   // conversation window. If we get too many of these, we might want to consider
   // finding a logical place for them to be shared.
   function _getPreferredEmail(contact) {
     // A contact may not contain email addresses, but only a phone number.
     if (!contact.email || contact.email.length === 0) {
       return { value: "" };
@@ -124,16 +125,528 @@ loop.conversationViews = (function(mozL1
             peerIdentifier: contactName, 
             showIcons: false}), 
           React.DOM.div(null, this.props.children)
         )
       );
     }
   });
 
+  // Matches strings of the form "<nonspaces>@<nonspaces>" or "+<digits>"
+  var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
+
+  var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
+    mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
+
+    propTypes: {
+      model: React.PropTypes.object.isRequired,
+      video: React.PropTypes.bool.isRequired
+    },
+
+    getDefaultProps: function() {
+      return {
+        showMenu: false,
+        video: true
+      };
+    },
+
+    clickHandler: function(e) {
+      var target = e.target;
+      if (!target.classList.contains('btn-chevron')) {
+        this._hideDeclineMenu();
+      }
+    },
+
+    _handleAccept: function(callType) {
+      return function() {
+        this.props.model.set("selectedCallType", callType);
+        this.props.model.trigger("accept");
+      }.bind(this);
+    },
+
+    _handleDecline: function() {
+      this.props.model.trigger("decline");
+    },
+
+    _handleDeclineBlock: function(e) {
+      this.props.model.trigger("declineAndBlock");
+      /* Prevent event propagation
+       * stop the click from reaching parent element */
+      return false;
+    },
+
+    /*
+     * Generate props for <AcceptCallButton> component based on
+     * incoming call type. An incoming video call will render a video
+     * answer button primarily, an audio call will flip them.
+     **/
+    _answerModeProps: function() {
+      var videoButton = {
+        handler: this._handleAccept("audio-video"),
+        className: "fx-embedded-btn-icon-video",
+        tooltip: "incoming_call_accept_audio_video_tooltip"
+      };
+      var audioButton = {
+        handler: this._handleAccept("audio"),
+        className: "fx-embedded-btn-audio-small",
+        tooltip: "incoming_call_accept_audio_only_tooltip"
+      };
+      var props = {};
+      props.primary = videoButton;
+      props.secondary = audioButton;
+
+      // When video is not enabled on this call, we swap the buttons around.
+      if (!this.props.video) {
+        audioButton.className = "fx-embedded-btn-icon-audio";
+        videoButton.className = "fx-embedded-btn-video-small";
+        props.primary = audioButton;
+        props.secondary = videoButton;
+      }
+
+      return props;
+    },
+
+    render: function() {
+      /* jshint ignore:start */
+      var dropdownMenuClassesDecline = React.addons.classSet({
+        "native-dropdown-menu": true,
+        "conversation-window-dropdown": true,
+        "visually-hidden": !this.state.showMenu
+      });
+
+      return (
+        React.DOM.div({className: "call-window"}, 
+          CallIdentifierView({video: this.props.video, 
+            peerIdentifier: this.props.model.getCallIdentifier(), 
+            urlCreationDate: this.props.model.get("urlCreationDate"), 
+            showIcons: true}), 
+
+          React.DOM.div({className: "btn-group call-action-group"}, 
+
+            React.DOM.div({className: "fx-embedded-call-button-spacer"}), 
+
+            React.DOM.div({className: "btn-chevron-menu-group"}, 
+              React.DOM.div({className: "btn-group-chevron"}, 
+                React.DOM.div({className: "btn-group"}, 
+
+                  React.DOM.button({className: "btn btn-decline", 
+                          onClick: this._handleDecline}, 
+                    mozL10n.get("incoming_call_cancel_button")
+                  ), 
+                  React.DOM.div({className: "btn-chevron", onClick: this.toggleDropdownMenu})
+                ), 
+
+                React.DOM.ul({className: dropdownMenuClassesDecline}, 
+                  React.DOM.li({className: "btn-block", onClick: this._handleDeclineBlock}, 
+                    mozL10n.get("incoming_call_cancel_and_block_button")
+                  )
+                )
+
+              )
+            ), 
+
+            React.DOM.div({className: "fx-embedded-call-button-spacer"}), 
+
+            AcceptCallButton({mode: this._answerModeProps()}), 
+
+            React.DOM.div({className: "fx-embedded-call-button-spacer"})
+
+          )
+        )
+      );
+      /* jshint ignore:end */
+    }
+  });
+
+  /**
+   * Incoming call view accept button, renders different primary actions
+   * (answer with video / with audio only) based on the props received
+   **/
+  var AcceptCallButton = React.createClass({displayName: 'AcceptCallButton',
+
+    propTypes: {
+      mode: React.PropTypes.object.isRequired,
+    },
+
+    render: function() {
+      var mode = this.props.mode;
+      return (
+        /* jshint ignore:start */
+        React.DOM.div({className: "btn-chevron-menu-group"}, 
+          React.DOM.div({className: "btn-group"}, 
+            React.DOM.button({className: "btn btn-accept", 
+                    onClick: mode.primary.handler, 
+                    title: mozL10n.get(mode.primary.tooltip)}, 
+              React.DOM.span({className: "fx-embedded-answer-btn-text"}, 
+                mozL10n.get("incoming_call_accept_button")
+              ), 
+              React.DOM.span({className: mode.primary.className})
+            ), 
+            React.DOM.div({className: mode.secondary.className, 
+                 onClick: mode.secondary.handler, 
+                 title: mozL10n.get(mode.secondary.tooltip)}
+            )
+          )
+        )
+        /* jshint ignore:end */
+      );
+    }
+  });
+
+  /**
+   * Something went wrong view. Displayed when there's a big problem.
+   *
+   * XXX Based on CallFailedView, but built specially until we flux-ify the
+   * incoming call views (bug 1088672).
+   */
+  var GenericFailureView = React.createClass({displayName: 'GenericFailureView',
+    mixins: [sharedMixins.AudioMixin],
+
+    propTypes: {
+      cancelCall: React.PropTypes.func.isRequired
+    },
+
+    componentDidMount: function() {
+      this.play("failure");
+    },
+
+    render: function() {
+      document.title = mozL10n.get("generic_failure_title");
+
+      return (
+        React.DOM.div({className: "call-window"}, 
+          React.DOM.h2(null, mozL10n.get("generic_failure_title")), 
+
+          React.DOM.div({className: "btn-group call-action-group"}, 
+            React.DOM.button({className: "btn btn-cancel", 
+                    onClick: this.props.cancelCall}, 
+              mozL10n.get("cancel_button")
+            )
+          )
+        )
+      );
+    }
+  });
+
+  /**
+   * This view manages the incoming conversation views - from
+   * call initiation through to the actual conversation and call end.
+   *
+   * At the moment, it does more than that, these parts need refactoring out.
+   */
+  var IncomingConversationView = React.createClass({displayName: 'IncomingConversationView',
+    mixins: [sharedMixins.AudioMixin, sharedMixins.WindowCloseMixin],
+
+    propTypes: {
+      client: React.PropTypes.instanceOf(loop.Client).isRequired,
+      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
+                         .isRequired,
+      sdk: React.PropTypes.object.isRequired,
+      conversationAppStore: React.PropTypes.instanceOf(
+        loop.store.ConversationAppStore).isRequired,
+      feedbackStore:
+        React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired
+    },
+
+    getInitialState: function() {
+      return {
+        callFailed: false, // XXX this should be removed when bug 1047410 lands.
+        callStatus: "start"
+      };
+    },
+
+    componentDidMount: function() {
+      this.props.conversation.on("accept", this.accept, this);
+      this.props.conversation.on("decline", this.decline, this);
+      this.props.conversation.on("declineAndBlock", this.declineAndBlock, this);
+      this.props.conversation.on("call:accepted", this.accepted, this);
+      this.props.conversation.on("change:publishedStream", this._checkConnected, this);
+      this.props.conversation.on("change:subscribedStream", this._checkConnected, this);
+      this.props.conversation.on("session:ended", this.endCall, this);
+      this.props.conversation.on("session:peer-hungup", this._onPeerHungup, this);
+      this.props.conversation.on("session:network-disconnected", this._onNetworkDisconnected, this);
+      this.props.conversation.on("session:connection-error", this._notifyError, this);
+
+      this.setupIncomingCall();
+    },
+
+    componentDidUnmount: function() {
+      this.props.conversation.off(null, null, this);
+    },
+
+    render: function() {
+      switch (this.state.callStatus) {
+        case "start": {
+          document.title = mozL10n.get("incoming_call_title2");
+
+          // XXX Don't render anything initially, though this should probably
+          // be some sort of pending view, whilst we connect the websocket.
+          return null;
+        }
+        case "incoming": {
+          document.title = mozL10n.get("incoming_call_title2");
+
+          return (
+            IncomingCallView({
+              model: this.props.conversation, 
+              video: this.props.conversation.hasVideoStream("incoming")}
+            )
+          );
+        }
+        case "connected": {
+          document.title = this.props.conversation.getCallIdentifier();
+
+          var callType = this.props.conversation.get("selectedCallType");
+
+          return (
+            sharedViews.ConversationView({
+              initiate: true, 
+              sdk: this.props.sdk, 
+              model: this.props.conversation, 
+              video: {enabled: callType !== "audio"}}
+            )
+          );
+        }
+        case "end": {
+          // XXX To be handled with the "failed" view state when bug 1047410 lands
+          if (this.state.callFailed) {
+            return GenericFailureView({
+              cancelCall: this.closeWindow.bind(this)}
+            );
+          }
+
+          document.title = mozL10n.get("conversation_has_ended");
+
+          this.play("terminated");
+
+          return (
+            sharedViews.FeedbackView({
+              feedbackStore: this.props.feedbackStore, 
+              onAfterFeedbackReceived: this.closeWindow.bind(this)}
+            )
+          );
+        }
+        case "close": {
+          this.closeWindow();
+          return (React.DOM.div(null));
+        }
+      }
+    },
+
+    /**
+     * Notify the user that the connection was not possible
+     * @param {{code: number, message: string}} error
+     */
+    _notifyError: function(error) {
+      // XXX Not the ideal response, but bug 1047410 will be replacing
+      // this by better "call failed" UI.
+      console.error(error);
+      this.setState({callFailed: true, callStatus: "end"});
+    },
+
+    /**
+     * Peer hung up. Notifies the user and ends the call.
+     *
+     * Event properties:
+     * - {String} connectionId: OT session id
+     */
+    _onPeerHungup: function() {
+      this.setState({callFailed: false, callStatus: "end"});
+    },
+
+    /**
+     * Network disconnected. Notifies the user and ends the call.
+     */
+    _onNetworkDisconnected: function() {
+      // XXX Not the ideal response, but bug 1047410 will be replacing
+      // this by better "call failed" UI.
+      this.setState({callFailed: true, callStatus: "end"});
+    },
+
+    /**
+     * Incoming call route.
+     */
+    setupIncomingCall: function() {
+      navigator.mozLoop.startAlerting();
+
+      // XXX This is a hack until we rework for the flux model in bug 1088672.
+      var callData = this.props.conversationAppStore.getStoreState().windowData;
+
+      this.props.conversation.setIncomingSessionData(callData);
+      this._setupWebSocket();
+    },
+
+    /**
+     * Starts the actual conversation
+     */
+    accepted: function() {
+      this.setState({callStatus: "connected"});
+    },
+
+    /**
+     * Moves the call to the end state
+     */
+    endCall: function() {
+      navigator.mozLoop.calls.clearCallInProgress(
+        this.props.conversation.get("windowId"));
+      this.setState({callStatus: "end"});
+    },
+
+    /**
+     * Used to set up the web socket connection and navigate to the
+     * call view if appropriate.
+     */
+    _setupWebSocket: function() {
+      this._websocket = new loop.CallConnectionWebSocket({
+        url: this.props.conversation.get("progressURL"),
+        websocketToken: this.props.conversation.get("websocketToken"),
+        callId: this.props.conversation.get("callId"),
+      });
+      this._websocket.promiseConnect().then(function(progressStatus) {
+        this.setState({
+          callStatus: progressStatus === "terminated" ? "close" : "incoming"
+        });
+      }.bind(this), function() {
+        this._handleSessionError();
+        return;
+      }.bind(this));
+
+      this._websocket.on("progress", this._handleWebSocketProgress, this);
+    },
+
+    /**
+     * Checks if the streams have been connected, and notifies the
+     * websocket that the media is now connected.
+     */
+    _checkConnected: function() {
+      // Check we've had both local and remote streams connected before
+      // sending the media up message.
+      if (this.props.conversation.streamsConnected()) {
+        this._websocket.mediaUp();
+      }
+    },
+
+    /**
+     * Used to receive websocket progress and to determine how to handle
+     * it if appropraite.
+     * If we add more cases here, then we should refactor this function.
+     *
+     * @param {Object} progressData The progress data from the websocket.
+     * @param {String} previousState The previous state from the websocket.
+     */
+    _handleWebSocketProgress: function(progressData, previousState) {
+      // We only care about the terminated state at the moment.
+      if (progressData.state !== "terminated")
+        return;
+
+      // XXX This would be nicer in the _abortIncomingCall function, but we need to stop
+      // it here for now due to server-side issues that are being fixed in bug 1088351.
+      // This is before the abort call to ensure that it happens before the window is
+      // closed.
+      navigator.mozLoop.stopAlerting();
+
+      // If we hit any of the termination reasons, and the user hasn't accepted
+      // then it seems reasonable to close the window/abort the incoming call.
+      //
+      // If the user has accepted the call, and something's happened, display
+      // the call failed view.
+      //
+      // https://wiki.mozilla.org/Loop/Architecture/MVP#Termination_Reasons
+      if (previousState === "init" || previousState === "alerting") {
+        this._abortIncomingCall();
+      } else {
+        this.setState({callFailed: true, callStatus: "end"});
+      }
+
+    },
+
+    /**
+     * Silently aborts an incoming call - stops the alerting, and
+     * closes the websocket.
+     */
+    _abortIncomingCall: function() {
+      this._websocket.close();
+      // Having a timeout here lets the logging for the websocket complete and be
+      // displayed on the console if both are on.
+      setTimeout(this.closeWindow, 0);
+    },
+
+    /**
+     * Accepts an incoming call.
+     */
+    accept: function() {
+      navigator.mozLoop.stopAlerting();
+      this._websocket.accept();
+      this.props.conversation.accepted();
+    },
+
+    /**
+     * Declines a call and handles closing of the window.
+     */
+    _declineCall: function() {
+      this._websocket.decline();
+      navigator.mozLoop.calls.clearCallInProgress(
+        this.props.conversation.get("windowId"));
+      this._websocket.close();
+      // Having a timeout here lets the logging for the websocket complete and be
+      // displayed on the console if both are on.
+      setTimeout(this.closeWindow, 0);
+    },
+
+    /**
+     * Declines an incoming call.
+     */
+    decline: function() {
+      navigator.mozLoop.stopAlerting();
+      this._declineCall();
+    },
+
+    /**
+     * Decline and block an incoming call
+     * @note:
+     * - loopToken is the callUrl identifier. It gets set in the panel
+     *   after a callUrl is received
+     */
+    declineAndBlock: function() {
+      navigator.mozLoop.stopAlerting();
+      var token = this.props.conversation.get("callToken");
+      var callerId = this.props.conversation.get("callerId");
+
+      // If this is a direct call, we'll need to block the caller directly.
+      if (callerId && EMAIL_OR_PHONE_RE.test(callerId)) {
+        navigator.mozLoop.calls.blockDirectCaller(callerId, function(err) {
+          // XXX The conversation window will be closed when this cb is triggered
+          // figure out if there is a better way to report the error to the user
+          // (bug 1103150).
+          console.log(err.fileName + ":" + err.lineNumber + ": " + err.message);
+        });
+      } else {
+        this.props.client.deleteCallUrl(token,
+          this.props.conversation.get("sessionType"),
+          function(error) {
+            // XXX The conversation window will be closed when this cb is triggered
+            // figure out if there is a better way to report the error to the user
+            // (bug 1048909).
+            console.log(error);
+          });
+      }
+
+      this._declineCall();
+    },
+
+    /**
+     * Handles a error starting the session
+     */
+    _handleSessionError: function() {
+      // XXX Not the ideal response, but bug 1047410 will be replacing
+      // this by better "call failed" UI.
+      console.error("Failed initiating the call session.");
+    },
+  });
+
   /**
    * View for pending conversations. Displays a cancel button and appropriate
    * pending/ringing strings.
    */
   var PendingConversationView = React.createClass({displayName: 'PendingConversationView',
     mixins: [sharedMixins.AudioMixin],
 
     propTypes: {
@@ -530,13 +1043,16 @@ loop.conversationViews = (function(mozL1
     },
   });
 
   return {
     PendingConversationView: PendingConversationView,
     CallIdentifierView: CallIdentifierView,
     ConversationDetailView: ConversationDetailView,
     CallFailedView: CallFailedView,
+    GenericFailureView: GenericFailureView,
+    IncomingCallView: IncomingCallView,
+    IncomingConversationView: IncomingConversationView,
     OngoingConversationView: OngoingConversationView,
     OutgoingConversationView: OutgoingConversationView
   };
 
 })(document.mozL10n || navigator.mozL10n);
--- a/browser/components/loop/content/js/conversationViews.jsx
+++ b/browser/components/loop/content/js/conversationViews.jsx
@@ -10,16 +10,17 @@ var loop = loop || {};
 loop.conversationViews = (function(mozL10n) {
 
   var CALL_STATES = loop.store.CALL_STATES;
   var CALL_TYPES = loop.shared.utils.CALL_TYPES;
   var sharedActions = loop.shared.actions;
   var sharedUtils = loop.shared.utils;
   var sharedViews = loop.shared.views;
   var sharedMixins = loop.shared.mixins;
+  var sharedModels = loop.shared.models;
 
   // This duplicates a similar function in contacts.jsx that isn't used in the
   // conversation window. If we get too many of these, we might want to consider
   // finding a logical place for them to be shared.
   function _getPreferredEmail(contact) {
     // A contact may not contain email addresses, but only a phone number.
     if (!contact.email || contact.email.length === 0) {
       return { value: "" };
@@ -124,16 +125,528 @@ loop.conversationViews = (function(mozL1
             peerIdentifier={contactName}
             showIcons={false} />
           <div>{this.props.children}</div>
         </div>
       );
     }
   });
 
+  // Matches strings of the form "<nonspaces>@<nonspaces>" or "+<digits>"
+  var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
+
+  var IncomingCallView = React.createClass({
+    mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
+
+    propTypes: {
+      model: React.PropTypes.object.isRequired,
+      video: React.PropTypes.bool.isRequired
+    },
+
+    getDefaultProps: function() {
+      return {
+        showMenu: false,
+        video: true
+      };
+    },
+
+    clickHandler: function(e) {
+      var target = e.target;
+      if (!target.classList.contains('btn-chevron')) {
+        this._hideDeclineMenu();
+      }
+    },
+
+    _handleAccept: function(callType) {
+      return function() {
+        this.props.model.set("selectedCallType", callType);
+        this.props.model.trigger("accept");
+      }.bind(this);
+    },
+
+    _handleDecline: function() {
+      this.props.model.trigger("decline");
+    },
+
+    _handleDeclineBlock: function(e) {
+      this.props.model.trigger("declineAndBlock");
+      /* Prevent event propagation
+       * stop the click from reaching parent element */
+      return false;
+    },
+
+    /*
+     * Generate props for <AcceptCallButton> component based on
+     * incoming call type. An incoming video call will render a video
+     * answer button primarily, an audio call will flip them.
+     **/
+    _answerModeProps: function() {
+      var videoButton = {
+        handler: this._handleAccept("audio-video"),
+        className: "fx-embedded-btn-icon-video",
+        tooltip: "incoming_call_accept_audio_video_tooltip"
+      };
+      var audioButton = {
+        handler: this._handleAccept("audio"),
+        className: "fx-embedded-btn-audio-small",
+        tooltip: "incoming_call_accept_audio_only_tooltip"
+      };
+      var props = {};
+      props.primary = videoButton;
+      props.secondary = audioButton;
+
+      // When video is not enabled on this call, we swap the buttons around.
+      if (!this.props.video) {
+        audioButton.className = "fx-embedded-btn-icon-audio";
+        videoButton.className = "fx-embedded-btn-video-small";
+        props.primary = audioButton;
+        props.secondary = videoButton;
+      }
+
+      return props;
+    },
+
+    render: function() {
+      /* jshint ignore:start */
+      var dropdownMenuClassesDecline = React.addons.classSet({
+        "native-dropdown-menu": true,
+        "conversation-window-dropdown": true,
+        "visually-hidden": !this.state.showMenu
+      });
+
+      return (
+        <div className="call-window">
+          <CallIdentifierView video={this.props.video}
+            peerIdentifier={this.props.model.getCallIdentifier()}
+            urlCreationDate={this.props.model.get("urlCreationDate")}
+            showIcons={true} />
+
+          <div className="btn-group call-action-group">
+
+            <div className="fx-embedded-call-button-spacer"></div>
+
+            <div className="btn-chevron-menu-group">
+              <div className="btn-group-chevron">
+                <div className="btn-group">
+
+                  <button className="btn btn-decline"
+                          onClick={this._handleDecline}>
+                    {mozL10n.get("incoming_call_cancel_button")}
+                  </button>
+                  <div className="btn-chevron" onClick={this.toggleDropdownMenu} />
+                </div>
+
+                <ul className={dropdownMenuClassesDecline}>
+                  <li className="btn-block" onClick={this._handleDeclineBlock}>
+                    {mozL10n.get("incoming_call_cancel_and_block_button")}
+                  </li>
+                </ul>
+
+              </div>
+            </div>
+
+            <div className="fx-embedded-call-button-spacer"></div>
+
+            <AcceptCallButton mode={this._answerModeProps()} />
+
+            <div className="fx-embedded-call-button-spacer"></div>
+
+          </div>
+        </div>
+      );
+      /* jshint ignore:end */
+    }
+  });
+
+  /**
+   * Incoming call view accept button, renders different primary actions
+   * (answer with video / with audio only) based on the props received
+   **/
+  var AcceptCallButton = React.createClass({
+
+    propTypes: {
+      mode: React.PropTypes.object.isRequired,
+    },
+
+    render: function() {
+      var mode = this.props.mode;
+      return (
+        /* jshint ignore:start */
+        <div className="btn-chevron-menu-group">
+          <div className="btn-group">
+            <button className="btn btn-accept"
+                    onClick={mode.primary.handler}
+                    title={mozL10n.get(mode.primary.tooltip)}>
+              <span className="fx-embedded-answer-btn-text">
+                {mozL10n.get("incoming_call_accept_button")}
+              </span>
+              <span className={mode.primary.className}></span>
+            </button>
+            <div className={mode.secondary.className}
+                 onClick={mode.secondary.handler}
+                 title={mozL10n.get(mode.secondary.tooltip)}>
+            </div>
+          </div>
+        </div>
+        /* jshint ignore:end */
+      );
+    }
+  });
+
+  /**
+   * Something went wrong view. Displayed when there's a big problem.
+   *
+   * XXX Based on CallFailedView, but built specially until we flux-ify the
+   * incoming call views (bug 1088672).
+   */
+  var GenericFailureView = React.createClass({
+    mixins: [sharedMixins.AudioMixin],
+
+    propTypes: {
+      cancelCall: React.PropTypes.func.isRequired
+    },
+
+    componentDidMount: function() {
+      this.play("failure");
+    },
+
+    render: function() {
+      document.title = mozL10n.get("generic_failure_title");
+
+      return (
+        <div className="call-window">
+          <h2>{mozL10n.get("generic_failure_title")}</h2>
+
+          <div className="btn-group call-action-group">
+            <button className="btn btn-cancel"
+                    onClick={this.props.cancelCall}>
+              {mozL10n.get("cancel_button")}
+            </button>
+          </div>
+        </div>
+      );
+    }
+  });
+
+  /**
+   * This view manages the incoming conversation views - from
+   * call initiation through to the actual conversation and call end.
+   *
+   * At the moment, it does more than that, these parts need refactoring out.
+   */
+  var IncomingConversationView = React.createClass({
+    mixins: [sharedMixins.AudioMixin, sharedMixins.WindowCloseMixin],
+
+    propTypes: {
+      client: React.PropTypes.instanceOf(loop.Client).isRequired,
+      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
+                         .isRequired,
+      sdk: React.PropTypes.object.isRequired,
+      conversationAppStore: React.PropTypes.instanceOf(
+        loop.store.ConversationAppStore).isRequired,
+      feedbackStore:
+        React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired
+    },
+
+    getInitialState: function() {
+      return {
+        callFailed: false, // XXX this should be removed when bug 1047410 lands.
+        callStatus: "start"
+      };
+    },
+
+    componentDidMount: function() {
+      this.props.conversation.on("accept", this.accept, this);
+      this.props.conversation.on("decline", this.decline, this);
+      this.props.conversation.on("declineAndBlock", this.declineAndBlock, this);
+      this.props.conversation.on("call:accepted", this.accepted, this);
+      this.props.conversation.on("change:publishedStream", this._checkConnected, this);
+      this.props.conversation.on("change:subscribedStream", this._checkConnected, this);
+      this.props.conversation.on("session:ended", this.endCall, this);
+      this.props.conversation.on("session:peer-hungup", this._onPeerHungup, this);
+      this.props.conversation.on("session:network-disconnected", this._onNetworkDisconnected, this);
+      this.props.conversation.on("session:connection-error", this._notifyError, this);
+
+      this.setupIncomingCall();
+    },
+
+    componentDidUnmount: function() {
+      this.props.conversation.off(null, null, this);
+    },
+
+    render: function() {
+      switch (this.state.callStatus) {
+        case "start": {
+          document.title = mozL10n.get("incoming_call_title2");
+
+          // XXX Don't render anything initially, though this should probably
+          // be some sort of pending view, whilst we connect the websocket.
+          return null;
+        }
+        case "incoming": {
+          document.title = mozL10n.get("incoming_call_title2");
+
+          return (
+            <IncomingCallView
+              model={this.props.conversation}
+              video={this.props.conversation.hasVideoStream("incoming")}
+            />
+          );
+        }
+        case "connected": {
+          document.title = this.props.conversation.getCallIdentifier();
+
+          var callType = this.props.conversation.get("selectedCallType");
+
+          return (
+            <sharedViews.ConversationView
+              initiate={true}
+              sdk={this.props.sdk}
+              model={this.props.conversation}
+              video={{enabled: callType !== "audio"}}
+            />
+          );
+        }
+        case "end": {
+          // XXX To be handled with the "failed" view state when bug 1047410 lands
+          if (this.state.callFailed) {
+            return <GenericFailureView
+              cancelCall={this.closeWindow.bind(this)}
+            />;
+          }
+
+          document.title = mozL10n.get("conversation_has_ended");
+
+          this.play("terminated");
+
+          return (
+            <sharedViews.FeedbackView
+              feedbackStore={this.props.feedbackStore}
+              onAfterFeedbackReceived={this.closeWindow.bind(this)}
+            />
+          );
+        }
+        case "close": {
+          this.closeWindow();
+          return (<div/>);
+        }
+      }
+    },
+
+    /**
+     * Notify the user that the connection was not possible
+     * @param {{code: number, message: string}} error
+     */
+    _notifyError: function(error) {
+      // XXX Not the ideal response, but bug 1047410 will be replacing
+      // this by better "call failed" UI.
+      console.error(error);
+      this.setState({callFailed: true, callStatus: "end"});
+    },
+
+    /**
+     * Peer hung up. Notifies the user and ends the call.
+     *
+     * Event properties:
+     * - {String} connectionId: OT session id
+     */
+    _onPeerHungup: function() {
+      this.setState({callFailed: false, callStatus: "end"});
+    },
+
+    /**
+     * Network disconnected. Notifies the user and ends the call.
+     */
+    _onNetworkDisconnected: function() {
+      // XXX Not the ideal response, but bug 1047410 will be replacing
+      // this by better "call failed" UI.
+      this.setState({callFailed: true, callStatus: "end"});
+    },
+
+    /**
+     * Incoming call route.
+     */
+    setupIncomingCall: function() {
+      navigator.mozLoop.startAlerting();
+
+      // XXX This is a hack until we rework for the flux model in bug 1088672.
+      var callData = this.props.conversationAppStore.getStoreState().windowData;
+
+      this.props.conversation.setIncomingSessionData(callData);
+      this._setupWebSocket();
+    },
+
+    /**
+     * Starts the actual conversation
+     */
+    accepted: function() {
+      this.setState({callStatus: "connected"});
+    },
+
+    /**
+     * Moves the call to the end state
+     */
+    endCall: function() {
+      navigator.mozLoop.calls.clearCallInProgress(
+        this.props.conversation.get("windowId"));
+      this.setState({callStatus: "end"});
+    },
+
+    /**
+     * Used to set up the web socket connection and navigate to the
+     * call view if appropriate.
+     */
+    _setupWebSocket: function() {
+      this._websocket = new loop.CallConnectionWebSocket({
+        url: this.props.conversation.get("progressURL"),
+        websocketToken: this.props.conversation.get("websocketToken"),
+        callId: this.props.conversation.get("callId"),
+      });
+      this._websocket.promiseConnect().then(function(progressStatus) {
+        this.setState({
+          callStatus: progressStatus === "terminated" ? "close" : "incoming"
+        });
+      }.bind(this), function() {
+        this._handleSessionError();
+        return;
+      }.bind(this));
+
+      this._websocket.on("progress", this._handleWebSocketProgress, this);
+    },
+
+    /**
+     * Checks if the streams have been connected, and notifies the
+     * websocket that the media is now connected.
+     */
+    _checkConnected: function() {
+      // Check we've had both local and remote streams connected before
+      // sending the media up message.
+      if (this.props.conversation.streamsConnected()) {
+        this._websocket.mediaUp();
+      }
+    },
+
+    /**
+     * Used to receive websocket progress and to determine how to handle
+     * it if appropraite.
+     * If we add more cases here, then we should refactor this function.
+     *
+     * @param {Object} progressData The progress data from the websocket.
+     * @param {String} previousState The previous state from the websocket.
+     */
+    _handleWebSocketProgress: function(progressData, previousState) {
+      // We only care about the terminated state at the moment.
+      if (progressData.state !== "terminated")
+        return;
+
+      // XXX This would be nicer in the _abortIncomingCall function, but we need to stop
+      // it here for now due to server-side issues that are being fixed in bug 1088351.
+      // This is before the abort call to ensure that it happens before the window is
+      // closed.
+      navigator.mozLoop.stopAlerting();
+
+      // If we hit any of the termination reasons, and the user hasn't accepted
+      // then it seems reasonable to close the window/abort the incoming call.
+      //
+      // If the user has accepted the call, and something's happened, display
+      // the call failed view.
+      //
+      // https://wiki.mozilla.org/Loop/Architecture/MVP#Termination_Reasons
+      if (previousState === "init" || previousState === "alerting") {
+        this._abortIncomingCall();
+      } else {
+        this.setState({callFailed: true, callStatus: "end"});
+      }
+
+    },
+
+    /**
+     * Silently aborts an incoming call - stops the alerting, and
+     * closes the websocket.
+     */
+    _abortIncomingCall: function() {
+      this._websocket.close();
+      // Having a timeout here lets the logging for the websocket complete and be
+      // displayed on the console if both are on.
+      setTimeout(this.closeWindow, 0);
+    },
+
+    /**
+     * Accepts an incoming call.
+     */
+    accept: function() {
+      navigator.mozLoop.stopAlerting();
+      this._websocket.accept();
+      this.props.conversation.accepted();
+    },
+
+    /**
+     * Declines a call and handles closing of the window.
+     */
+    _declineCall: function() {
+      this._websocket.decline();
+      navigator.mozLoop.calls.clearCallInProgress(
+        this.props.conversation.get("windowId"));
+      this._websocket.close();
+      // Having a timeout here lets the logging for the websocket complete and be
+      // displayed on the console if both are on.
+      setTimeout(this.closeWindow, 0);
+    },
+
+    /**
+     * Declines an incoming call.
+     */
+    decline: function() {
+      navigator.mozLoop.stopAlerting();
+      this._declineCall();
+    },
+
+    /**
+     * Decline and block an incoming call
+     * @note:
+     * - loopToken is the callUrl identifier. It gets set in the panel
+     *   after a callUrl is received
+     */
+    declineAndBlock: function() {
+      navigator.mozLoop.stopAlerting();
+      var token = this.props.conversation.get("callToken");
+      var callerId = this.props.conversation.get("callerId");
+
+      // If this is a direct call, we'll need to block the caller directly.
+      if (callerId && EMAIL_OR_PHONE_RE.test(callerId)) {
+        navigator.mozLoop.calls.blockDirectCaller(callerId, function(err) {
+          // XXX The conversation window will be closed when this cb is triggered
+          // figure out if there is a better way to report the error to the user
+          // (bug 1103150).
+          console.log(err.fileName + ":" + err.lineNumber + ": " + err.message);
+        });
+      } else {
+        this.props.client.deleteCallUrl(token,
+          this.props.conversation.get("sessionType"),
+          function(error) {
+            // XXX The conversation window will be closed when this cb is triggered
+            // figure out if there is a better way to report the error to the user
+            // (bug 1048909).
+            console.log(error);
+          });
+      }
+
+      this._declineCall();
+    },
+
+    /**
+     * Handles a error starting the session
+     */
+    _handleSessionError: function() {
+      // XXX Not the ideal response, but bug 1047410 will be replacing
+      // this by better "call failed" UI.
+      console.error("Failed initiating the call session.");
+    },
+  });
+
   /**
    * View for pending conversations. Displays a cancel button and appropriate
    * pending/ringing strings.
    */
   var PendingConversationView = React.createClass({
     mixins: [sharedMixins.AudioMixin],
 
     propTypes: {
@@ -530,13 +1043,16 @@ loop.conversationViews = (function(mozL1
     },
   });
 
   return {
     PendingConversationView: PendingConversationView,
     CallIdentifierView: CallIdentifierView,
     ConversationDetailView: ConversationDetailView,
     CallFailedView: CallFailedView,
+    GenericFailureView: GenericFailureView,
+    IncomingCallView: IncomingCallView,
+    IncomingConversationView: IncomingConversationView,
     OngoingConversationView: OngoingConversationView,
     OutgoingConversationView: OutgoingConversationView
   };
 
 })(document.mozL10n || navigator.mozL10n);
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -290,17 +290,17 @@ loop.roomViews = (function(mozL10n) {
         "room-preview": this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS
       });
 
       switch(this.state.roomState) {
         case ROOM_STATES.FAILED:
         case ROOM_STATES.FULL: {
           // Note: While rooms are set to hold a maximum of 2 participants, the
           //       FULL case should never happen on desktop.
-          return loop.conversation.GenericFailureView({
+          return loop.conversationViews.GenericFailureView({
             cancelCall: this.closeWindow}
           );
         }
         case ROOM_STATES.ENDED: {
           if (this.state.used)
             return sharedViews.FeedbackView({
               feedbackStore: this.props.feedbackStore, 
               onAfterFeedbackReceived: this.closeWindow}
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -290,17 +290,17 @@ loop.roomViews = (function(mozL10n) {
         "room-preview": this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS
       });
 
       switch(this.state.roomState) {
         case ROOM_STATES.FAILED:
         case ROOM_STATES.FULL: {
           // Note: While rooms are set to hold a maximum of 2 participants, the
           //       FULL case should never happen on desktop.
-          return <loop.conversation.GenericFailureView
+          return <loop.conversationViews.GenericFailureView
             cancelCall={this.closeWindow}
           />;
         }
         case ROOM_STATES.ENDED: {
           if (this.state.used)
             return <sharedViews.FeedbackView
               feedbackStore={this.props.feedbackStore}
               onAfterFeedbackReceived={this.closeWindow}
--- a/browser/components/loop/test/desktop-local/conversationViews_test.js
+++ b/browser/components/loop/test/desktop-local/conversationViews_test.js
@@ -2,23 +2,38 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var expect = chai.expect;
 
 describe("loop.conversationViews", function () {
   "use strict";
 
   var sharedUtils = loop.shared.utils;
+  var sharedView = loop.shared.views;
   var sandbox, oldTitle, view, dispatcher, contact, fakeAudioXHR;
   var fakeMozLoop, fakeWindow;
 
   var CALL_STATES = loop.store.CALL_STATES;
 
+  // XXX refactor to Just Work with "sandbox.stubComponent" or else
+  // just pass in the sandbox and put somewhere generally usable
+
+  function stubComponent(obj, component, mockTagName){
+    var reactClass = React.createClass({
+      render: function() {
+        var mockTagName = mockTagName || "div";
+        return React.DOM[mockTagName](null, this.props.children);
+      }
+    });
+    return sandbox.stub(obj, component, reactClass);
+  }
+
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
+    sandbox.useFakeTimers();
 
     oldTitle = document.title;
     sandbox.stub(document.mozL10n, "get", function(x) {
       return x;
     });
 
     dispatcher = new loop.Dispatcher();
     sandbox.stub(dispatcher, "dispatch");
@@ -40,32 +55,45 @@ describe("loop.conversationViews", funct
           return "audio/ogg";
       },
       responseType: null,
       response: new ArrayBuffer(10),
       onload: null
     };
 
     fakeMozLoop = navigator.mozLoop = {
-      getLoopPref: sinon.stub().returns("http://fakeurl"),
+      // Dummy function, stubbed below.
+      getLoopPref: function() {},
+      calls: {
+        clearCallInProgress: sinon.stub()
+      },
       composeEmail: sinon.spy(),
       get appVersionInfo() {
         return {
           version: "42",
           channel: "test",
           platform: "test"
         };
       },
       getAudioBlob: sinon.spy(function(name, callback) {
         callback(null, new Blob([new ArrayBuffer(10)], {type: "audio/ogg"}));
       }),
+      startAlerting: sinon.stub(),
+      stopAlerting: sinon.stub(),
       userProfile: {
         email: "bob@invalid.tld"
       }
     };
+    sinon.stub(fakeMozLoop, "getLoopPref", function(pref) {
+        if (pref === "fake") {
+          return"http://fakeurl";
+        }
+
+        return false;
+    });
 
     fakeWindow = {
       navigator: { mozLoop: fakeMozLoop },
       close: sandbox.stub(),
     };
     loop.shared.mixins.setRootObject(fakeWindow);
 
   });
@@ -573,9 +601,726 @@ describe("loop.conversationViews", funct
           loop.conversationViews.PendingConversationView);
 
         store.setStoreState({callState: CALL_STATES.TERMINATED});
 
         TestUtils.findRenderedComponentWithType(view,
           loop.conversationViews.CallFailedView);
     });
   });
+
+  describe("IncomingConversationView", function() {
+    var conversationAppStore, conversation, client, icView, oldTitle,
+        feedbackStore;
+
+    function mountTestComponent() {
+      return TestUtils.renderIntoDocument(
+        loop.conversationViews.IncomingConversationView({
+          client: client,
+          conversation: conversation,
+          sdk: {},
+          conversationAppStore: conversationAppStore,
+          feedbackStore: feedbackStore
+        }));
+    }
+
+    beforeEach(function() {
+      oldTitle = document.title;
+      client = new loop.Client();
+      conversation = new loop.shared.models.ConversationModel({}, {
+        sdk: {}
+      });
+      conversation.set({windowId: 42});
+      var dispatcher = new loop.Dispatcher();
+      conversationAppStore = new loop.store.ConversationAppStore({
+        dispatcher: dispatcher,
+        mozLoop: navigator.mozLoop
+      });
+      feedbackStore = new loop.store.FeedbackStore(dispatcher, {
+        feedbackClient: {}
+      });
+      sandbox.stub(conversation, "setOutgoingSessionData");
+    });
+
+    afterEach(function() {
+      icView = undefined;
+      document.title = oldTitle;
+    });
+
+    describe("start", function() {
+      it("should set the title to incoming_call_title2", function() {
+        conversationAppStore.setStoreState({
+          windowData: {
+            progressURL:    "fake",
+            websocketToken: "fake",
+            callId: 42
+          }
+        });
+
+        icView = mountTestComponent();
+
+        expect(document.title).eql("incoming_call_title2");
+      });
+    });
+
+    describe("componentDidMount", function() {
+      var fakeSessionData, promise, resolveWebSocketConnect;
+      var rejectWebSocketConnect;
+
+      beforeEach(function() {
+        fakeSessionData  = {
+          sessionId:      "sessionId",
+          sessionToken:   "sessionToken",
+          apiKey:         "apiKey",
+          callType:       "callType",
+          callId:         "Hello",
+          progressURL:    "http://progress.example.com",
+          websocketToken: "7b"
+        };
+
+        conversationAppStore.setStoreState({
+          windowData: fakeSessionData
+        });
+
+        stubComponent(loop.conversationViews, "IncomingCallView");
+        stubComponent(sharedView, "ConversationView");
+      });
+
+      it("should start alerting", function() {
+        icView = mountTestComponent();
+
+        sinon.assert.calledOnce(navigator.mozLoop.startAlerting);
+      });
+
+      describe("Session Data setup", function() {
+        beforeEach(function() {
+          sandbox.stub(loop, "CallConnectionWebSocket").returns({
+            promiseConnect: function () {
+              promise = new Promise(function(resolve, reject) {
+                resolveWebSocketConnect = resolve;
+                rejectWebSocketConnect = reject;
+              });
+              return promise;
+            },
+            on: sinon.stub()
+          });
+        });
+
+        it("should store the session data", function() {
+          sandbox.stub(conversation, "setIncomingSessionData");
+
+          icView = mountTestComponent();
+
+          sinon.assert.calledOnce(conversation.setIncomingSessionData);
+          sinon.assert.calledWithExactly(conversation.setIncomingSessionData,
+                                         fakeSessionData);
+        });
+
+        it("should setup the websocket connection", function() {
+          icView = mountTestComponent();
+
+          sinon.assert.calledOnce(loop.CallConnectionWebSocket);
+          sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, {
+            callId: "Hello",
+            url: "http://progress.example.com",
+            websocketToken: "7b"
+          });
+        });
+      });
+
+      describe("WebSocket Handling", function() {
+        beforeEach(function() {
+          promise = new Promise(function(resolve, reject) {
+            resolveWebSocketConnect = resolve;
+            rejectWebSocketConnect = reject;
+          });
+
+          sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
+        });
+
+        it("should set the state to incoming on success", function(done) {
+          icView = mountTestComponent();
+          resolveWebSocketConnect("incoming");
+
+          promise.then(function () {
+            expect(icView.state.callStatus).eql("incoming");
+            done();
+          });
+        });
+
+        it("should set the state to close on success if the progress " +
+          "state is terminated", function(done) {
+            icView = mountTestComponent();
+            resolveWebSocketConnect("terminated");
+
+            promise.then(function () {
+              expect(icView.state.callStatus).eql("close");
+              done();
+            });
+          });
+
+        // XXX implement me as part of bug 1047410
+        // see https://hg.mozilla.org/integration/fx-team/rev/5d2c69ebb321#l18.259
+        it.skip("should should switch view state to failed", function(done) {
+          icView = mountTestComponent();
+          rejectWebSocketConnect();
+
+          promise.then(function() {}, function() {
+            done();
+          });
+        });
+      });
+
+      describe("WebSocket Events", function() {
+        describe("Call cancelled or timed out before acceptance", function() {
+          beforeEach(function() {
+            // Mounting the test component automatically calls the required
+            // setup functions
+            icView = mountTestComponent();
+            promise = new Promise(function(resolve, reject) {
+              resolve();
+            });
+
+            sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
+            sandbox.stub(loop.CallConnectionWebSocket.prototype, "close");
+          });
+
+          describe("progress - terminated (previousState = alerting)", function() {
+            it("should stop alerting", function(done) {
+              promise.then(function() {
+                icView._websocket.trigger("progress", {
+                  state: "terminated",
+                  reason: "timeout"
+                }, "alerting");
+
+                sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
+                done();
+              });
+            });
+
+            it("should close the websocket", function(done) {
+              promise.then(function() {
+                icView._websocket.trigger("progress", {
+                  state: "terminated",
+                  reason: "closed"
+                }, "alerting");
+
+                sinon.assert.calledOnce(icView._websocket.close);
+                done();
+              });
+            });
+
+            it("should close the window", function(done) {
+              promise.then(function() {
+                icView._websocket.trigger("progress", {
+                  state: "terminated",
+                  reason: "answered-elsewhere"
+                }, "alerting");
+
+                sandbox.clock.tick(1);
+
+                sinon.assert.calledOnce(fakeWindow.close);
+                done();
+              });
+            });
+          });
+
+
+          describe("progress - terminated (previousState not init" +
+                   " nor alerting)",
+            function() {
+              it("should set the state to end", function(done) {
+                promise.then(function() {
+                  icView._websocket.trigger("progress", {
+                    state: "terminated",
+                    reason: "media-fail"
+                  }, "connecting");
+
+                  expect(icView.state.callStatus).eql("end");
+                  done();
+                });
+              });
+
+              it("should stop alerting", function(done) {
+                promise.then(function() {
+                  icView._websocket.trigger("progress", {
+                    state: "terminated",
+                    reason: "media-fail"
+                  }, "connecting");
+
+                  sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
+                  done();
+                });
+              });
+            });
+        });
+      });
+
+      describe("#accept", function() {
+        beforeEach(function() {
+          icView = mountTestComponent();
+          conversation.setIncomingSessionData({
+            sessionId:      "sessionId",
+            sessionToken:   "sessionToken",
+            apiKey:         "apiKey",
+            callType:       "callType",
+            callId:         "Hello",
+            progressURL:    "http://progress.example.com",
+            websocketToken: 123
+          });
+
+          sandbox.stub(icView._websocket, "accept");
+          sandbox.stub(icView.props.conversation, "accepted");
+        });
+
+        it("should initiate the conversation", function() {
+          icView.accept();
+
+          sinon.assert.calledOnce(icView.props.conversation.accepted);
+        });
+
+        it("should notify the websocket of the user acceptance", function() {
+          icView.accept();
+
+          sinon.assert.calledOnce(icView._websocket.accept);
+        });
+
+        it("should stop alerting", function() {
+          icView.accept();
+
+          sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
+        });
+      });
+
+      describe("#decline", function() {
+        beforeEach(function() {
+          icView = mountTestComponent();
+
+          icView._websocket = {
+            decline: sinon.stub(),
+            close: sinon.stub()
+          };
+          conversation.set({
+            windowId: "8699"
+          });
+          conversation.setIncomingSessionData({
+            websocketToken: 123
+          });
+        });
+
+        it("should close the window", function() {
+          icView.decline();
+
+          sandbox.clock.tick(1);
+
+          sinon.assert.calledOnce(fakeWindow.close);
+        });
+
+        it("should stop alerting", function() {
+          icView.decline();
+
+          sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
+        });
+
+        it("should release callData", function() {
+          icView.decline();
+
+          sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
+          sinon.assert.calledWithExactly(
+            navigator.mozLoop.calls.clearCallInProgress, "8699");
+        });
+      });
+
+      describe("#blocked", function() {
+        var mozLoop, deleteCallUrlStub;
+
+        beforeEach(function() {
+          icView = mountTestComponent();
+
+          icView._websocket = {
+            decline: sinon.spy(),
+            close: sinon.stub()
+          };
+
+          mozLoop = {
+            LOOP_SESSION_TYPE: {
+              GUEST: 1,
+              FXA: 2
+            }
+          };
+
+          deleteCallUrlStub = sandbox.stub(loop.Client.prototype,
+                                           "deleteCallUrl");
+        });
+
+        it("should call mozLoop.stopAlerting", function() {
+          icView.declineAndBlock();
+
+          sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
+        });
+
+        it("should call delete call", function() {
+          sandbox.stub(conversation, "get").withArgs("callToken")
+                                           .returns("fakeToken")
+                                           .withArgs("sessionType")
+                                           .returns(mozLoop.LOOP_SESSION_TYPE.FXA);
+
+          icView.declineAndBlock();
+
+          sinon.assert.calledOnce(deleteCallUrlStub);
+          sinon.assert.calledWithExactly(deleteCallUrlStub,
+            "fakeToken", mozLoop.LOOP_SESSION_TYPE.FXA, sinon.match.func);
+        });
+
+        it("should get callToken from conversation model", function() {
+          sandbox.stub(conversation, "get");
+          icView.declineAndBlock();
+
+          sinon.assert.called(conversation.get);
+          sinon.assert.calledWithExactly(conversation.get, "callToken");
+          sinon.assert.calledWithExactly(conversation.get, "windowId");
+        });
+
+        it("should trigger error handling in case of error", function() {
+          // XXX just logging to console for now
+          var log = sandbox.stub(console, "log");
+          var fakeError = {
+            error: true
+          };
+          deleteCallUrlStub.callsArgWith(2, fakeError);
+          icView.declineAndBlock();
+
+          sinon.assert.calledOnce(log);
+          sinon.assert.calledWithExactly(log, fakeError);
+        });
+
+        it("should close the window", function() {
+          icView.declineAndBlock();
+
+          sandbox.clock.tick(1);
+
+          sinon.assert.calledOnce(fakeWindow.close);
+        });
+      });
+    });
+
+    describe("Events", function() {
+      var fakeSessionData;
+
+      beforeEach(function() {
+
+        fakeSessionData = {
+          sessionId:    "sessionId",
+          sessionToken: "sessionToken",
+          apiKey:       "apiKey"
+        };
+
+        conversationAppStore.setStoreState({
+          windowData: fakeSessionData
+        });
+
+        sandbox.stub(conversation, "setIncomingSessionData");
+        sandbox.stub(loop, "CallConnectionWebSocket").returns({
+          promiseConnect: function() {
+            return new Promise(function() {});
+          },
+          on: sandbox.spy()
+        });
+
+        icView = mountTestComponent();
+
+        conversation.set("loopToken", "fakeToken");
+        stubComponent(sharedView, "ConversationView");
+      });
+
+      describe("call:accepted", function() {
+        it("should display the ConversationView",
+          function() {
+            conversation.accepted();
+
+            TestUtils.findRenderedComponentWithType(icView,
+              sharedView.ConversationView);
+          });
+
+        it("should set the title to the call identifier", function() {
+          sandbox.stub(conversation, "getCallIdentifier").returns("fakeId");
+
+          conversation.accepted();
+
+          expect(document.title).eql("fakeId");
+        });
+      });
+
+      describe("session:ended", function() {
+        it("should display the feedback view when the call session ends",
+          function() {
+            conversation.trigger("session:ended");
+
+            TestUtils.findRenderedComponentWithType(icView,
+              sharedView.FeedbackView);
+          });
+      });
+
+      describe("session:peer-hungup", function() {
+        it("should display the feedback view when the peer hangs up",
+          function() {
+            conversation.trigger("session:peer-hungup");
+
+              TestUtils.findRenderedComponentWithType(icView,
+                sharedView.FeedbackView);
+          });
+      });
+
+      describe("session:network-disconnected", function() {
+        it("should navigate to call failed when network disconnects",
+          function() {
+            conversation.trigger("session:network-disconnected");
+
+            TestUtils.findRenderedComponentWithType(icView,
+              loop.conversationViews.GenericFailureView);
+          });
+
+        it("should update the conversation window toolbar title",
+          function() {
+            conversation.trigger("session:network-disconnected");
+
+            expect(document.title).eql("generic_failure_title");
+          });
+      });
+
+      describe("Published and Subscribed Streams", function() {
+        beforeEach(function() {
+          icView._websocket = {
+            mediaUp: sinon.spy()
+          };
+        });
+
+        describe("publishStream", function() {
+          it("should not notify the websocket if only one stream is up",
+            function() {
+              conversation.set("publishedStream", true);
+
+              sinon.assert.notCalled(icView._websocket.mediaUp);
+            });
+
+          it("should notify the websocket that media is up if both streams" +
+             "are connected", function() {
+              conversation.set("subscribedStream", true);
+              conversation.set("publishedStream", true);
+
+              sinon.assert.calledOnce(icView._websocket.mediaUp);
+            });
+        });
+
+        describe("subscribedStream", function() {
+          it("should not notify the websocket if only one stream is up",
+            function() {
+              conversation.set("subscribedStream", true);
+
+              sinon.assert.notCalled(icView._websocket.mediaUp);
+            });
+
+          it("should notify the websocket that media is up if both streams" +
+             "are connected", function() {
+              conversation.set("publishedStream", true);
+              conversation.set("subscribedStream", true);
+
+              sinon.assert.calledOnce(icView._websocket.mediaUp);
+            });
+        });
+      });
+    });
+  });
+
+  describe("IncomingCallView", function() {
+    var view, model, fakeAudio;
+
+    beforeEach(function() {
+      var Model = Backbone.Model.extend({
+        getCallIdentifier: function() {return "fakeId";}
+      });
+      model = new Model();
+      sandbox.spy(model, "trigger");
+      sandbox.stub(model, "set");
+
+      fakeAudio = {
+        play: sinon.spy(),
+        pause: sinon.spy(),
+        removeAttribute: sinon.spy()
+      };
+      sandbox.stub(window, "Audio").returns(fakeAudio);
+
+      view = TestUtils.renderIntoDocument(
+        loop.conversationViews.IncomingCallView({
+          model: model,
+          video: true
+        }));
+    });
+
+    describe("default answer mode", function() {
+      it("should display video as primary answer mode", function() {
+        view = TestUtils.renderIntoDocument(
+          loop.conversationViews.IncomingCallView({
+            model: model,
+            video: true
+          }));
+        var primaryBtn = view.getDOMNode()
+                                  .querySelector('.fx-embedded-btn-icon-video');
+
+        expect(primaryBtn).not.to.eql(null);
+      });
+
+      it("should display audio as primary answer mode", function() {
+        view = TestUtils.renderIntoDocument(
+          loop.conversationViews.IncomingCallView({
+            model: model,
+            video: false
+          }));
+        var primaryBtn = view.getDOMNode()
+                                  .querySelector('.fx-embedded-btn-icon-audio');
+
+        expect(primaryBtn).not.to.eql(null);
+      });
+
+      it("should accept call with video", function() {
+        view = TestUtils.renderIntoDocument(
+          loop.conversationViews.IncomingCallView({
+            model: model,
+            video: true
+          }));
+        var primaryBtn = view.getDOMNode()
+                                  .querySelector('.fx-embedded-btn-icon-video');
+
+        React.addons.TestUtils.Simulate.click(primaryBtn);
+
+        sinon.assert.calledOnce(model.set);
+        sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio-video");
+        sinon.assert.calledOnce(model.trigger);
+        sinon.assert.calledWithExactly(model.trigger, "accept");
+      });
+
+      it("should accept call with audio", function() {
+        view = TestUtils.renderIntoDocument(
+          loop.conversationViews.IncomingCallView({
+            model: model,
+            video: false
+          }));
+        var primaryBtn = view.getDOMNode()
+                                  .querySelector('.fx-embedded-btn-icon-audio');
+
+        React.addons.TestUtils.Simulate.click(primaryBtn);
+
+        sinon.assert.calledOnce(model.set);
+        sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio");
+        sinon.assert.calledOnce(model.trigger);
+        sinon.assert.calledWithExactly(model.trigger, "accept");
+      });
+
+      it("should accept call with video when clicking on secondary btn",
+         function() {
+           view = TestUtils.renderIntoDocument(
+             loop.conversationViews.IncomingCallView({
+               model: model,
+               video: false
+             }));
+           var secondaryBtn = view.getDOMNode()
+           .querySelector('.fx-embedded-btn-video-small');
+
+           React.addons.TestUtils.Simulate.click(secondaryBtn);
+
+           sinon.assert.calledOnce(model.set);
+           sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio-video");
+           sinon.assert.calledOnce(model.trigger);
+           sinon.assert.calledWithExactly(model.trigger, "accept");
+         });
+
+      it("should accept call with audio when clicking on secondary btn",
+         function() {
+           view = TestUtils.renderIntoDocument(
+             loop.conversationViews.IncomingCallView({
+               model: model,
+               video: true
+             }));
+           var secondaryBtn = view.getDOMNode()
+           .querySelector('.fx-embedded-btn-audio-small');
+
+           React.addons.TestUtils.Simulate.click(secondaryBtn);
+
+           sinon.assert.calledOnce(model.set);
+           sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio");
+           sinon.assert.calledOnce(model.trigger);
+           sinon.assert.calledWithExactly(model.trigger, "accept");
+         });
+    });
+
+    describe("click event on .btn-accept", function() {
+      it("should trigger an 'accept' conversation model event", function () {
+        var buttonAccept = view.getDOMNode().querySelector(".btn-accept");
+        model.trigger.withArgs("accept");
+        TestUtils.Simulate.click(buttonAccept);
+
+        /* Setting a model property triggers 2 events */
+        sinon.assert.calledOnce(model.trigger.withArgs("accept"));
+      });
+
+      it("should set selectedCallType to audio-video", function () {
+        var buttonAccept = view.getDOMNode().querySelector(".btn-accept");
+
+        TestUtils.Simulate.click(buttonAccept);
+
+        sinon.assert.calledOnce(model.set);
+        sinon.assert.calledWithExactly(model.set, "selectedCallType",
+          "audio-video");
+      });
+    });
+
+    describe("click event on .btn-decline", function() {
+      it("should trigger an 'decline' conversation model event", function() {
+        var buttonDecline = view.getDOMNode().querySelector(".btn-decline");
+
+        TestUtils.Simulate.click(buttonDecline);
+
+        sinon.assert.calledOnce(model.trigger);
+        sinon.assert.calledWith(model.trigger, "decline");
+        });
+    });
+
+    describe("click event on .btn-block", function() {
+      it("should trigger a 'block' conversation model event", function() {
+        var buttonBlock = view.getDOMNode().querySelector(".btn-block");
+
+        TestUtils.Simulate.click(buttonBlock);
+
+        sinon.assert.calledOnce(model.trigger);
+        sinon.assert.calledWith(model.trigger, "declineAndBlock");
+      });
+    });
+  });
+
+  describe("GenericFailureView", function() {
+    var view, fakeAudio;
+
+    beforeEach(function() {
+      fakeAudio = {
+        play: sinon.spy(),
+        pause: sinon.spy(),
+        removeAttribute: sinon.spy()
+      };
+      navigator.mozLoop.doNotDisturb = false;
+      sandbox.stub(window, "Audio").returns(fakeAudio);
+
+      view = TestUtils.renderIntoDocument(
+        loop.conversationViews.GenericFailureView({
+          cancelCall: function() {}
+        })
+      );
+    });
+
+    it("should play a failure sound, once", function() {
+      sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
+      sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
+                                     "failure", sinon.match.func);
+      sinon.assert.calledOnce(fakeAudio.play);
+      expect(fakeAudio.loop).to.equal(false);
+    });
+
+  });
 });
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -5,36 +5,21 @@
 /* global loop, sinon, React, TestUtils */
 
 var expect = chai.expect;
 
 describe("loop.conversation", function() {
   "use strict";
 
   var sharedModels = loop.shared.models,
-      sharedView = loop.shared.views,
       fakeWindow,
       sandbox;
 
-  // XXX refactor to Just Work with "sandbox.stubComponent" or else
-  // just pass in the sandbox and put somewhere generally usable
-
-  function stubComponent(obj, component, mockTagName){
-    var reactClass = React.createClass({
-      render: function() {
-        var mockTagName = mockTagName || "div";
-        return React.DOM[mockTagName](null, this.props.children);
-      }
-    });
-    return sandbox.stub(obj, component, reactClass);
-  }
-
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
-    sandbox.useFakeTimers();
 
     navigator.mozLoop = {
       doNotDisturb: true,
       getStrings: function() {
         return JSON.stringify({textContent: "fakeText"});
       },
       get locale() {
         return "en-US";
@@ -42,19 +27,16 @@ describe("loop.conversation", function()
       setLoopPref: sinon.stub(),
       getLoopPref: function(prefName) {
         if (prefName == "debug.sdk") {
           return false;
         }
 
         return "http://fake";
       },
-      calls: {
-        clearCallInProgress: sinon.stub()
-      },
       LOOP_SESSION_TYPE: {
         GUEST: 1,
         FXA: 2
       },
       startAlerting: sinon.stub(),
       stopAlerting: sinon.stub(),
       ensureRegistered: sinon.stub(),
       get appVersionInfo() {
@@ -215,17 +197,17 @@ describe("loop.conversation", function()
         },
         on: sandbox.spy()
       });
       conversationAppStore.setStoreState({windowType: "incoming"});
 
       ccView = mountTestComponent();
 
       TestUtils.findRenderedComponentWithType(ccView,
-        loop.conversation.IncomingConversationView);
+        loop.conversationViews.IncomingConversationView);
     });
 
     it("should display the RoomView for rooms", function() {
       conversationAppStore.setStoreState({windowType: "room"});
 
       ccView = mountTestComponent();
 
       TestUtils.findRenderedComponentWithType(ccView,
@@ -233,722 +215,12 @@ describe("loop.conversation", function()
     });
 
     it("should display the GenericFailureView for failures", function() {
       conversationAppStore.setStoreState({windowType: "failed"});
 
       ccView = mountTestComponent();
 
       TestUtils.findRenderedComponentWithType(ccView,
-        loop.conversation.GenericFailureView);
+        loop.conversationViews.GenericFailureView);
     });
   });
-
-  describe("IncomingConversationView", function() {
-    var conversationAppStore, conversation, client, icView, oldTitle,
-        feedbackStore;
-
-    function mountTestComponent() {
-      return TestUtils.renderIntoDocument(
-        loop.conversation.IncomingConversationView({
-          client: client,
-          conversation: conversation,
-          sdk: {},
-          conversationAppStore: conversationAppStore,
-          feedbackStore: feedbackStore
-        }));
-    }
-
-    beforeEach(function() {
-      oldTitle = document.title;
-      client = new loop.Client();
-      conversation = new loop.shared.models.ConversationModel({}, {
-        sdk: {}
-      });
-      conversation.set({windowId: 42});
-      var dispatcher = new loop.Dispatcher();
-      conversationAppStore = new loop.store.ConversationAppStore({
-        dispatcher: dispatcher,
-        mozLoop: navigator.mozLoop
-      });
-      feedbackStore = new loop.store.FeedbackStore(dispatcher, {
-        feedbackClient: {}
-      });
-      sandbox.stub(conversation, "setOutgoingSessionData");
-    });
-
-    afterEach(function() {
-      icView = undefined;
-      document.title = oldTitle;
-    });
-
-    describe("start", function() {
-      it("should set the title to incoming_call_title2", function() {
-        conversationAppStore.setStoreState({
-          windowData: {
-            progressURL:    "fake",
-            websocketToken: "fake",
-            callId: 42
-          }
-        });
-
-        icView = mountTestComponent();
-
-        expect(document.title).eql("incoming_call_title2");
-      });
-    });
-
-    describe("componentDidMount", function() {
-      var fakeSessionData, promise, resolveWebSocketConnect;
-      var rejectWebSocketConnect;
-
-      beforeEach(function() {
-        fakeSessionData  = {
-          sessionId:      "sessionId",
-          sessionToken:   "sessionToken",
-          apiKey:         "apiKey",
-          callType:       "callType",
-          callId:         "Hello",
-          progressURL:    "http://progress.example.com",
-          websocketToken: "7b"
-        };
-
-        conversationAppStore.setStoreState({
-          windowData: fakeSessionData
-        });
-
-        stubComponent(loop.conversation, "IncomingCallView");
-        stubComponent(sharedView, "ConversationView");
-      });
-
-      it("should start alerting", function() {
-        icView = mountTestComponent();
-
-        sinon.assert.calledOnce(navigator.mozLoop.startAlerting);
-      });
-
-      describe("Session Data setup", function() {
-        beforeEach(function() {
-          sandbox.stub(loop, "CallConnectionWebSocket").returns({
-            promiseConnect: function () {
-              promise = new Promise(function(resolve, reject) {
-                resolveWebSocketConnect = resolve;
-                rejectWebSocketConnect = reject;
-              });
-              return promise;
-            },
-            on: sinon.stub()
-          });
-        });
-
-        it("should store the session data", function() {
-          sandbox.stub(conversation, "setIncomingSessionData");
-
-          icView = mountTestComponent();
-
-          sinon.assert.calledOnce(conversation.setIncomingSessionData);
-          sinon.assert.calledWithExactly(conversation.setIncomingSessionData,
-                                         fakeSessionData);
-        });
-
-        it("should setup the websocket connection", function() {
-          icView = mountTestComponent();
-
-          sinon.assert.calledOnce(loop.CallConnectionWebSocket);
-          sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, {
-            callId: "Hello",
-            url: "http://progress.example.com",
-            websocketToken: "7b"
-          });
-        });
-      });
-
-      describe("WebSocket Handling", function() {
-        beforeEach(function() {
-          promise = new Promise(function(resolve, reject) {
-            resolveWebSocketConnect = resolve;
-            rejectWebSocketConnect = reject;
-          });
-
-          sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
-        });
-
-        it("should set the state to incoming on success", function(done) {
-          icView = mountTestComponent();
-          resolveWebSocketConnect("incoming");
-
-          promise.then(function () {
-            expect(icView.state.callStatus).eql("incoming");
-            done();
-          });
-        });
-
-        it("should set the state to close on success if the progress " +
-          "state is terminated", function(done) {
-            icView = mountTestComponent();
-            resolveWebSocketConnect("terminated");
-
-            promise.then(function () {
-              expect(icView.state.callStatus).eql("close");
-              done();
-            });
-          });
-
-        // XXX implement me as part of bug 1047410
-        // see https://hg.mozilla.org/integration/fx-team/rev/5d2c69ebb321#l18.259
-        it.skip("should should switch view state to failed", function(done) {
-          icView = mountTestComponent();
-          rejectWebSocketConnect();
-
-          promise.then(function() {}, function() {
-            done();
-          });
-        });
-      });
-
-      describe("WebSocket Events", function() {
-        describe("Call cancelled or timed out before acceptance", function() {
-          beforeEach(function() {
-            // Mounting the test component automatically calls the required
-            // setup functions
-            icView = mountTestComponent();
-            promise = new Promise(function(resolve, reject) {
-              resolve();
-            });
-
-            sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
-            sandbox.stub(loop.CallConnectionWebSocket.prototype, "close");
-          });
-
-          describe("progress - terminated (previousState = alerting)", function() {
-            it("should stop alerting", function(done) {
-              promise.then(function() {
-                icView._websocket.trigger("progress", {
-                  state: "terminated",
-                  reason: "timeout"
-                }, "alerting");
-
-                sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
-                done();
-              });
-            });
-
-            it("should close the websocket", function(done) {
-              promise.then(function() {
-                icView._websocket.trigger("progress", {
-                  state: "terminated",
-                  reason: "closed"
-                }, "alerting");
-
-                sinon.assert.calledOnce(icView._websocket.close);
-                done();
-              });
-            });
-
-            it("should close the window", function(done) {
-              promise.then(function() {
-                icView._websocket.trigger("progress", {
-                  state: "terminated",
-                  reason: "answered-elsewhere"
-                }, "alerting");
-
-                sandbox.clock.tick(1);
-
-                sinon.assert.calledOnce(fakeWindow.close);
-                done();
-              });
-            });
-          });
-
-
-          describe("progress - terminated (previousState not init" +
-                   " nor alerting)",
-            function() {
-              it("should set the state to end", function(done) {
-                promise.then(function() {
-                  icView._websocket.trigger("progress", {
-                    state: "terminated",
-                    reason: "media-fail"
-                  }, "connecting");
-
-                  expect(icView.state.callStatus).eql("end");
-                  done();
-                });
-              });
-
-              it("should stop alerting", function(done) {
-                promise.then(function() {
-                  icView._websocket.trigger("progress", {
-                    state: "terminated",
-                    reason: "media-fail"
-                  }, "connecting");
-
-                  sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
-                  done();
-                });
-              });
-            });
-        });
-      });
-
-      describe("#accept", function() {
-        beforeEach(function() {
-          icView = mountTestComponent();
-          conversation.setIncomingSessionData({
-            sessionId:      "sessionId",
-            sessionToken:   "sessionToken",
-            apiKey:         "apiKey",
-            callType:       "callType",
-            callId:         "Hello",
-            progressURL:    "http://progress.example.com",
-            websocketToken: 123
-          });
-
-          sandbox.stub(icView._websocket, "accept");
-          sandbox.stub(icView.props.conversation, "accepted");
-        });
-
-        it("should initiate the conversation", function() {
-          icView.accept();
-
-          sinon.assert.calledOnce(icView.props.conversation.accepted);
-        });
-
-        it("should notify the websocket of the user acceptance", function() {
-          icView.accept();
-
-          sinon.assert.calledOnce(icView._websocket.accept);
-        });
-
-        it("should stop alerting", function() {
-          icView.accept();
-
-          sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
-        });
-      });
-
-      describe("#decline", function() {
-        beforeEach(function() {
-          icView = mountTestComponent();
-
-          icView._websocket = {
-            decline: sinon.stub(),
-            close: sinon.stub()
-          };
-          conversation.set({
-            windowId: "8699"
-          });
-          conversation.setIncomingSessionData({
-            websocketToken: 123
-          });
-        });
-
-        it("should close the window", function() {
-          icView.decline();
-
-          sandbox.clock.tick(1);
-
-          sinon.assert.calledOnce(fakeWindow.close);
-        });
-
-        it("should stop alerting", function() {
-          icView.decline();
-
-          sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
-        });
-
-        it("should release callData", function() {
-          icView.decline();
-
-          sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
-          sinon.assert.calledWithExactly(
-            navigator.mozLoop.calls.clearCallInProgress, "8699");
-        });
-      });
-
-      describe("#blocked", function() {
-        var mozLoop, deleteCallUrlStub;
-
-        beforeEach(function() {
-          icView = mountTestComponent();
-
-          icView._websocket = {
-            decline: sinon.spy(),
-            close: sinon.stub()
-          };
-
-          mozLoop = {
-            LOOP_SESSION_TYPE: {
-              GUEST: 1,
-              FXA: 2
-            }
-          };
-
-          deleteCallUrlStub = sandbox.stub(loop.Client.prototype,
-                                           "deleteCallUrl");
-        });
-
-        it("should call mozLoop.stopAlerting", function() {
-          icView.declineAndBlock();
-
-          sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
-        });
-
-        it("should call delete call", function() {
-          sandbox.stub(conversation, "get").withArgs("callToken")
-                                           .returns("fakeToken")
-                                           .withArgs("sessionType")
-                                           .returns(mozLoop.LOOP_SESSION_TYPE.FXA);
-
-          icView.declineAndBlock();
-
-          sinon.assert.calledOnce(deleteCallUrlStub);
-          sinon.assert.calledWithExactly(deleteCallUrlStub,
-            "fakeToken", mozLoop.LOOP_SESSION_TYPE.FXA, sinon.match.func);
-        });
-
-        it("should get callToken from conversation model", function() {
-          sandbox.stub(conversation, "get");
-          icView.declineAndBlock();
-
-          sinon.assert.called(conversation.get);
-          sinon.assert.calledWithExactly(conversation.get, "callToken");
-          sinon.assert.calledWithExactly(conversation.get, "windowId");
-        });
-
-        it("should trigger error handling in case of error", function() {
-          // XXX just logging to console for now
-          var log = sandbox.stub(console, "log");
-          var fakeError = {
-            error: true
-          };
-          deleteCallUrlStub.callsArgWith(2, fakeError);
-          icView.declineAndBlock();
-
-          sinon.assert.calledOnce(log);
-          sinon.assert.calledWithExactly(log, fakeError);
-        });
-
-        it("should close the window", function() {
-          icView.declineAndBlock();
-
-          sandbox.clock.tick(1);
-
-          sinon.assert.calledOnce(fakeWindow.close);
-        });
-      });
-    });
-
-    describe("Events", function() {
-      var fakeSessionData;
-
-      beforeEach(function() {
-
-        fakeSessionData = {
-          sessionId:    "sessionId",
-          sessionToken: "sessionToken",
-          apiKey:       "apiKey"
-        };
-
-        conversationAppStore.setStoreState({
-          windowData: fakeSessionData
-        });
-
-        sandbox.stub(conversation, "setIncomingSessionData");
-        sandbox.stub(loop, "CallConnectionWebSocket").returns({
-          promiseConnect: function() {
-            return new Promise(function() {});
-          },
-          on: sandbox.spy()
-        });
-
-        icView = mountTestComponent();
-
-        conversation.set("loopToken", "fakeToken");
-        stubComponent(sharedView, "ConversationView");
-      });
-
-      describe("call:accepted", function() {
-        it("should display the ConversationView",
-          function() {
-            conversation.accepted();
-
-            TestUtils.findRenderedComponentWithType(icView,
-              sharedView.ConversationView);
-          });
-
-        it("should set the title to the call identifier", function() {
-          sandbox.stub(conversation, "getCallIdentifier").returns("fakeId");
-
-          conversation.accepted();
-
-          expect(document.title).eql("fakeId");
-        });
-      });
-
-      describe("session:ended", function() {
-        it("should display the feedback view when the call session ends",
-          function() {
-            conversation.trigger("session:ended");
-
-            TestUtils.findRenderedComponentWithType(icView,
-              sharedView.FeedbackView);
-          });
-      });
-
-      describe("session:peer-hungup", function() {
-        it("should display the feedback view when the peer hangs up",
-          function() {
-            conversation.trigger("session:peer-hungup");
-
-              TestUtils.findRenderedComponentWithType(icView,
-                sharedView.FeedbackView);
-          });
-      });
-
-      describe("session:network-disconnected", function() {
-        it("should navigate to call failed when network disconnects",
-          function() {
-            conversation.trigger("session:network-disconnected");
-
-            TestUtils.findRenderedComponentWithType(icView,
-              loop.conversation.GenericFailureView);
-          });
-
-        it("should update the conversation window toolbar title",
-          function() {
-            conversation.trigger("session:network-disconnected");
-
-            expect(document.title).eql("generic_failure_title");
-          });
-      });
-
-      describe("Published and Subscribed Streams", function() {
-        beforeEach(function() {
-          icView._websocket = {
-            mediaUp: sinon.spy()
-          };
-        });
-
-        describe("publishStream", function() {
-          it("should not notify the websocket if only one stream is up",
-            function() {
-              conversation.set("publishedStream", true);
-
-              sinon.assert.notCalled(icView._websocket.mediaUp);
-            });
-
-          it("should notify the websocket that media is up if both streams" +
-             "are connected", function() {
-              conversation.set("subscribedStream", true);
-              conversation.set("publishedStream", true);
-
-              sinon.assert.calledOnce(icView._websocket.mediaUp);
-            });
-        });
-
-        describe("subscribedStream", function() {
-          it("should not notify the websocket if only one stream is up",
-            function() {
-              conversation.set("subscribedStream", true);
-
-              sinon.assert.notCalled(icView._websocket.mediaUp);
-            });
-
-          it("should notify the websocket that media is up if both streams" +
-             "are connected", function() {
-              conversation.set("publishedStream", true);
-              conversation.set("subscribedStream", true);
-
-              sinon.assert.calledOnce(icView._websocket.mediaUp);
-            });
-        });
-      });
-    });
-  });
-
-  describe("IncomingCallView", function() {
-    var view, model, fakeAudio;
-
-    beforeEach(function() {
-      var Model = Backbone.Model.extend({
-        getCallIdentifier: function() {return "fakeId";}
-      });
-      model = new Model();
-      sandbox.spy(model, "trigger");
-      sandbox.stub(model, "set");
-
-      fakeAudio = {
-        play: sinon.spy(),
-        pause: sinon.spy(),
-        removeAttribute: sinon.spy()
-      };
-      sandbox.stub(window, "Audio").returns(fakeAudio);
-
-      view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
-        model: model,
-        video: true
-      }));
-    });
-
-    describe("default answer mode", function() {
-      it("should display video as primary answer mode", function() {
-        view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
-          model: model,
-          video: true
-        }));
-        var primaryBtn = view.getDOMNode()
-                                  .querySelector('.fx-embedded-btn-icon-video');
-
-        expect(primaryBtn).not.to.eql(null);
-      });
-
-      it("should display audio as primary answer mode", function() {
-        view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
-          model: model,
-          video: false
-        }));
-        var primaryBtn = view.getDOMNode()
-                                  .querySelector('.fx-embedded-btn-icon-audio');
-
-        expect(primaryBtn).not.to.eql(null);
-      });
-
-      it("should accept call with video", function() {
-        view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
-          model: model,
-          video: true
-        }));
-        var primaryBtn = view.getDOMNode()
-                                  .querySelector('.fx-embedded-btn-icon-video');
-
-        React.addons.TestUtils.Simulate.click(primaryBtn);
-
-        sinon.assert.calledOnce(model.set);
-        sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio-video");
-        sinon.assert.calledOnce(model.trigger);
-        sinon.assert.calledWithExactly(model.trigger, "accept");
-      });
-
-      it("should accept call with audio", function() {
-        view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
-          model: model,
-          video: false
-        }));
-        var primaryBtn = view.getDOMNode()
-                                  .querySelector('.fx-embedded-btn-icon-audio');
-
-        React.addons.TestUtils.Simulate.click(primaryBtn);
-
-        sinon.assert.calledOnce(model.set);
-        sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio");
-        sinon.assert.calledOnce(model.trigger);
-        sinon.assert.calledWithExactly(model.trigger, "accept");
-      });
-
-      it("should accept call with video when clicking on secondary btn",
-         function() {
-           view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
-             model: model,
-             video: false
-           }));
-           var secondaryBtn = view.getDOMNode()
-           .querySelector('.fx-embedded-btn-video-small');
-
-           React.addons.TestUtils.Simulate.click(secondaryBtn);
-
-           sinon.assert.calledOnce(model.set);
-           sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio-video");
-           sinon.assert.calledOnce(model.trigger);
-           sinon.assert.calledWithExactly(model.trigger, "accept");
-         });
-
-      it("should accept call with audio when clicking on secondary btn",
-         function() {
-           view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
-             model: model,
-             video: true
-           }));
-           var secondaryBtn = view.getDOMNode()
-           .querySelector('.fx-embedded-btn-audio-small');
-
-           React.addons.TestUtils.Simulate.click(secondaryBtn);
-
-           sinon.assert.calledOnce(model.set);
-           sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio");
-           sinon.assert.calledOnce(model.trigger);
-           sinon.assert.calledWithExactly(model.trigger, "accept");
-         });
-    });
-
-    describe("click event on .btn-accept", function() {
-      it("should trigger an 'accept' conversation model event", function () {
-        var buttonAccept = view.getDOMNode().querySelector(".btn-accept");
-        model.trigger.withArgs("accept");
-        TestUtils.Simulate.click(buttonAccept);
-
-        /* Setting a model property triggers 2 events */
-        sinon.assert.calledOnce(model.trigger.withArgs("accept"));
-      });
-
-      it("should set selectedCallType to audio-video", function () {
-        var buttonAccept = view.getDOMNode().querySelector(".btn-accept");
-
-        TestUtils.Simulate.click(buttonAccept);
-
-        sinon.assert.calledOnce(model.set);
-        sinon.assert.calledWithExactly(model.set, "selectedCallType",
-          "audio-video");
-      });
-    });
-
-    describe("click event on .btn-decline", function() {
-      it("should trigger an 'decline' conversation model event", function() {
-        var buttonDecline = view.getDOMNode().querySelector(".btn-decline");
-
-        TestUtils.Simulate.click(buttonDecline);
-
-        sinon.assert.calledOnce(model.trigger);
-        sinon.assert.calledWith(model.trigger, "decline");
-        });
-    });
-
-    describe("click event on .btn-block", function() {
-      it("should trigger a 'block' conversation model event", function() {
-        var buttonBlock = view.getDOMNode().querySelector(".btn-block");
-
-        TestUtils.Simulate.click(buttonBlock);
-
-        sinon.assert.calledOnce(model.trigger);
-        sinon.assert.calledWith(model.trigger, "declineAndBlock");
-      });
-    });
-  });
-
-  describe("GenericFailureView", function() {
-    var view, fakeAudio;
-
-    beforeEach(function() {
-      fakeAudio = {
-        play: sinon.spy(),
-        pause: sinon.spy(),
-        removeAttribute: sinon.spy()
-      };
-      navigator.mozLoop.doNotDisturb = false;
-      sandbox.stub(window, "Audio").returns(fakeAudio);
-
-      view = TestUtils.renderIntoDocument(
-        loop.conversation.GenericFailureView({
-          cancelCall: function() {}
-        })
-      );
-    });
-
-    it("should play a failure sound, once", function() {
-      sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
-      sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
-                                     "failure", sinon.match.func);
-      sinon.assert.calledOnce(fakeAudio.play);
-      expect(fakeAudio.loop).to.equal(false);
-    });
-
-  });
 });
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -316,27 +316,27 @@ describe("loop.roomViews", function () {
 
       it("should render the GenericFailureView if the roomState is `FAILED`",
         function() {
           activeRoomStore.setStoreState({roomState: ROOM_STATES.FAILED});
 
           view = mountTestComponent();
 
           TestUtils.findRenderedComponentWithType(view,
-            loop.conversation.GenericFailureView);
+            loop.conversationViews.GenericFailureView);
         });
 
       it("should render the GenericFailureView if the roomState is `FULL`",
         function() {
           activeRoomStore.setStoreState({roomState: ROOM_STATES.FULL});
 
           view = mountTestComponent();
 
           TestUtils.findRenderedComponentWithType(view,
-            loop.conversation.GenericFailureView);
+            loop.conversationViews.GenericFailureView);
         });
 
       it("should render the DesktopRoomInvitationView if roomState is `JOINED`",
         function() {
           activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
 
           view = mountTestComponent();
 
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -13,17 +13,17 @@
   // Stop the default init functions running to avoid conflicts.
   document.removeEventListener('DOMContentLoaded', loop.panel.init);
   document.removeEventListener('DOMContentLoaded', loop.conversation.init);
 
   // 1. Desktop components
   // 1.1 Panel
   var PanelView = loop.panel.PanelView;
   // 1.2. Conversation Window
-  var IncomingCallView = loop.conversation.IncomingCallView;
+  var IncomingCallView = loop.conversationViews.IncomingCallView;
   var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
   var CallFailedView = loop.conversationViews.CallFailedView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
 
   // 2. Standalone webapp
   var HomeView = loop.webapp.HomeView;
   var UnsupportedBrowserView  = loop.webapp.UnsupportedBrowserView;
   var UnsupportedDeviceView   = loop.webapp.UnsupportedDeviceView;
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -13,17 +13,17 @@
   // Stop the default init functions running to avoid conflicts.
   document.removeEventListener('DOMContentLoaded', loop.panel.init);
   document.removeEventListener('DOMContentLoaded', loop.conversation.init);
 
   // 1. Desktop components
   // 1.1 Panel
   var PanelView = loop.panel.PanelView;
   // 1.2. Conversation Window
-  var IncomingCallView = loop.conversation.IncomingCallView;
+  var IncomingCallView = loop.conversationViews.IncomingCallView;
   var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
   var CallFailedView = loop.conversationViews.CallFailedView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
 
   // 2. Standalone webapp
   var HomeView = loop.webapp.HomeView;
   var UnsupportedBrowserView  = loop.webapp.UnsupportedBrowserView;
   var UnsupportedDeviceView   = loop.webapp.UnsupportedDeviceView;
--- a/browser/devtools/fontinspector/font-inspector.js
+++ b/browser/devtools/fontinspector/font-inspector.js
@@ -80,17 +80,17 @@ FontInspector.prototype = {
    */
   undim: function FI_undim() {
     this.chromeDoc.body.classList.remove("dim");
   },
 
  /**
   * Retrieve all the font info for the selected node and display it.
   */
-  update: Task.async(function*() {
+  update: Task.async(function*(showAllFonts) {
     let node = this.inspector.selection.nodeFront;
 
     if (!node ||
         !this.isActive() ||
         !this.inspector.selection.isConnected() ||
         !this.inspector.selection.isElementNode() ||
         this.chromeDoc.body.classList.contains("dim")) {
       return;
@@ -99,20 +99,26 @@ FontInspector.prototype = {
     this.chromeDoc.querySelector("#all-fonts").innerHTML = "";
 
     let fillStyle = (Services.prefs.getCharPref("devtools.theme") == "light") ?
         "black" : "white";
     let options = {
       includePreviews: true,
       previewFillStyle: fillStyle
     }
-
-    let fonts = yield this.pageStyle.getUsedFontFaces(node, options)
+    let fonts = [];
+    if (showAllFonts){
+      fonts = yield this.pageStyle.getAllUsedFontFaces(options)
                       .then(null, console.error);
-    if (!fonts) {
+    }
+    else{
+      fonts = yield this.pageStyle.getUsedFontFaces(node, options)
+                      .then(null, console.error);
+    }
+    if (!fonts || !fonts.length) {
       return;
     }
 
     for (let font of fonts) {
       font.previewUrl = yield font.preview.data.string();
     }
 
     // in case we've been destroyed in the meantime
@@ -164,31 +170,20 @@ FontInspector.prototype = {
     }
     let preview = s.querySelector(".font-preview");
     preview.src = font.previewUrl;
 
     this.chromeDoc.querySelector("#all-fonts").appendChild(s);
   },
 
   /**
-   * Select the <body> to show all the fonts included in the document.
+   * Show all fonts for the document (including iframes)
    */
   showAll: function FI_showAll() {
-    if (!this.isActive() ||
-        !this.inspector.selection.isConnected() ||
-        !this.inspector.selection.isElementNode()) {
-      return;
-    }
-
-    // Select the body node to show all fonts
-    let walker = this.inspector.walker;
-
-    walker.getRootNode().then(root => walker.querySelector(root, "body")).then(body => {
-      this.inspector.selection.setNodeFront(body, "fontinspector");
-    });
+    this.update(true);
   },
 }
 
 window.setPanel = function(panel) {
   window.fontInspector = new FontInspector(panel, window);
 }
 
 window.onunload = function() {
--- a/browser/devtools/fontinspector/test/browser.ini
+++ b/browser/devtools/fontinspector/test/browser.ini
@@ -1,9 +1,10 @@
 [DEFAULT]
 subsuite = devtools
 support-files =
   browser_fontinspector.html
+  test_iframe.html
   ostrich-black.ttf
   ostrich-regular.ttf
   head.js
 
 [browser_fontinspector.js]
--- a/browser/devtools/fontinspector/test/browser_fontinspector.html
+++ b/browser/devtools/fontinspector/test/browser_fontinspector.html
@@ -40,12 +40,13 @@
     font-family: bar;
     font-weight: 800;
   }
 </style>
 
 <body>
   BODY
   <div>DIV</div>
+  <iframe src="test_iframe.html"></iframe>
   <div class="normal-text">NORMAL DIV</div>
   <div class="bold-text">BOLD DIV</div>
   <div class="black-text">800 DIV</div>
 </body>
--- a/browser/devtools/fontinspector/test/browser_fontinspector.js
+++ b/browser/devtools/fontinspector/test/browser_fontinspector.js
@@ -102,12 +102,13 @@ function* testDivFonts(inspector) {
 
 function* testShowAllFonts(inspector) {
   info("testing showing all fonts");
 
   let updated = inspector.once("fontinspector-updated");
   viewDoc.querySelector("#showall").click();
   yield updated;
 
-  is(inspector.selection.nodeFront.nodeName, "BODY", "Show all fonts selected the body node");
+  // shouldn't change the node selection
+  is(inspector.selection.nodeFront.nodeName, "DIV", "Show all fonts selected");
   let sections = viewDoc.querySelectorAll("#all-fonts > section");
-  is(sections.length, 5, "And font-inspector still shows 5 fonts for body");
+  is(sections.length, 6, "Font inspector shows 6 fonts (1 from iframe)");
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/fontinspector/test/test_iframe.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<style>
+  div{
+    font-family: "Times New Roman";
+  }
+</style>
+
+<body>
+  <div>Hello world</div>
+</body>
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,4 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.0.978
+Current extension version is: 1.0.1040
 
--- a/browser/extensions/pdfjs/content/PdfJs.jsm
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -1,56 +1,64 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+/* jshint esnext:true */
+/* globals Components, Services, XPCOMUtils, PdfjsChromeUtils, PdfRedirector,
+           PdfjsContentUtils, DEFAULT_PREFERENCES, PdfStreamConverter */
 
-var EXPORTED_SYMBOLS = ["PdfJs"];
+'use strict';
+
+var EXPORTED_SYMBOLS = ['PdfJs'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cm = Components.manager;
 const Cu = Components.utils;
 
 const PREF_PREFIX = 'pdfjs';
 const PREF_DISABLED = PREF_PREFIX + '.disabled';
 const PREF_MIGRATION_VERSION = PREF_PREFIX + '.migrationVersion';
 const PREF_PREVIOUS_ACTION = PREF_PREFIX + '.previousHandler.preferredAction';
-const PREF_PREVIOUS_ASK = PREF_PREFIX + '.previousHandler.alwaysAskBeforeHandling';
+const PREF_PREVIOUS_ASK = PREF_PREFIX +
+                          '.previousHandler.alwaysAskBeforeHandling';
 const PREF_DISABLED_PLUGIN_TYPES = 'plugin.disable_full_page_plugin_for_types';
 const TOPIC_PDFJS_HANDLER_CHANGED = 'pdfjs:handlerChanged';
-const TOPIC_PLUGINS_LIST_UPDATED = "plugins-list-updated";
-const TOPIC_PLUGIN_INFO_UPDATED = "plugin-info-updated";
+const TOPIC_PLUGINS_LIST_UPDATED = 'plugins-list-updated';
+const TOPIC_PLUGIN_INFO_UPDATED = 'plugin-info-updated';
 const PDF_CONTENT_TYPE = 'application/pdf';
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 
 let Svc = {};
 XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
                                    '@mozilla.org/mime;1',
                                    'nsIMIMEService');
 XPCOMUtils.defineLazyServiceGetter(Svc, 'pluginHost',
                                    '@mozilla.org/plugin/host;1',
                                    'nsIPluginHost');
-XPCOMUtils.defineLazyModuleGetter(this, "PdfjsChromeUtils",
-                                  "resource://pdf.js/PdfjsChromeUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PdfjsContentUtils",
-                                  "resource://pdf.js/PdfjsContentUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, 'PdfjsChromeUtils',
+                                  'resource://pdf.js/PdfjsChromeUtils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'PdfjsContentUtils',
+                                  'resource://pdf.js/PdfjsContentUtils.jsm');
 
 function getBoolPref(aPref, aDefaultValue) {
   try {
     return Services.prefs.getBoolPref(aPref);
   } catch (ex) {
     return aDefaultValue;
   }
 }
@@ -59,17 +67,17 @@ function getIntPref(aPref, aDefaultValue
   try {
     return Services.prefs.getIntPref(aPref);
   } catch (ex) {
     return aDefaultValue;
   }
 }
 
 function isDefaultHandler() {
- if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+ if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
    return PdfjsContentUtils.isDefaultHandlerApp();
  }
  return PdfjsChromeUtils.isDefaultHandlerApp();
 }
 
 function initializeDefaultPreferences() {
 
 var DEFAULT_PREFERENCES = {
@@ -129,18 +137,20 @@ Factory.prototype = {
 };
 
 let PdfJs = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
   _registered: false,
   _initialized: false,
 
   init: function init(remote) {
-    if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
-      throw new Error("PdfJs.init should only get called in the parent process.");
+    if (Services.appinfo.processType !==
+        Services.appinfo.PROCESS_TYPE_DEFAULT) {
+      throw new Error('PdfJs.init should only get called ' +
+                      'in the parent process.');
     }
     PdfjsChromeUtils.init();
     if (!remote) {
       PdfjsContentUtils.init();
     }
     this.initPrefs();
     this.updateRegistration();
   },
@@ -234,28 +244,29 @@ let PdfJs = {
     }
 
     if (types.indexOf(PDF_CONTENT_TYPE) === -1) {
       types.push(PDF_CONTENT_TYPE);
     }
     prefs.setCharPref(PREF_DISABLED_PLUGIN_TYPES, types.join(','));
 
     // Update the category manager in case the plugins are already loaded.
-    let categoryManager = Cc["@mozilla.org/categorymanager;1"];
+    let categoryManager = Cc['@mozilla.org/categorymanager;1'];
     categoryManager.getService(Ci.nsICategoryManager).
-                    deleteCategoryEntry("Gecko-Content-Viewers",
+                    deleteCategoryEntry('Gecko-Content-Viewers',
                                         PDF_CONTENT_TYPE,
                                         false);
   },
 
   // nsIObserver
   observe: function observe(aSubject, aTopic, aData) {
     this.updateRegistration();
-    if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
-      let jsm = "resource://pdf.js/PdfjsChromeUtils.jsm";
+    if (Services.appinfo.processType ===
+        Services.appinfo.PROCESS_TYPE_DEFAULT) {
+      let jsm = 'resource://pdf.js/PdfjsChromeUtils.jsm';
       let PdfjsChromeUtils = Components.utils.import(jsm, {}).PdfjsChromeUtils;
       PdfjsChromeUtils.notifyChildOfSettingsChange();
     }
   },
   
   /**
    * pdf.js is only enabled if it is both selected as the pdf viewer and if the 
    * global switch enabling it is true.
@@ -277,19 +288,19 @@ let PdfJs = {
       let disabledPluginTypes =
         Services.prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES).split(',');
       if (disabledPluginTypes.indexOf(PDF_CONTENT_TYPE) >= 0) {
         return true;
       }
     }
 
     // Check if there is an enabled pdf plugin.
-    // Note: this check is performed last because getPluginTags() triggers costly
-    // plugin list initialization (bug 881575)
-    let tags = Cc["@mozilla.org/plugin/host;1"].
+    // Note: this check is performed last because getPluginTags() triggers
+    // costly plugin list initialization (bug 881575)
+    let tags = Cc['@mozilla.org/plugin/host;1'].
                   getService(Ci.nsIPluginHost).
                   getPluginTags();
     let enabledPluginFound = tags.some(function(tag) {
       if (tag.disabled) {
         return false;
       }
       let mimeTypes = tag.getMimeTypes();
       return mimeTypes.some(function(mimeType) {
@@ -297,37 +308,37 @@ let PdfJs = {
       });
     });
 
     // Use pdf.js if pdf plugin is not present or disabled
     return !enabledPluginFound;
   },
 
   _ensureRegistered: function _ensureRegistered() {
-    if (this._registered)
+    if (this._registered) {
       return;
-
+    }
     this._pdfStreamConverterFactory = new Factory();
     Cu.import('resource://pdf.js/PdfStreamConverter.jsm');
     this._pdfStreamConverterFactory.register(PdfStreamConverter);
 
     this._pdfRedirectorFactory = new Factory();
     Cu.import('resource://pdf.js/PdfRedirector.jsm');
     this._pdfRedirectorFactory.register(PdfRedirector);
 
     Svc.pluginHost.registerPlayPreviewMimeType(PDF_CONTENT_TYPE, true,
       'data:application/x-moz-playpreview-pdfjs;,');
 
     this._registered = true;
   },
 
   _ensureUnregistered: function _ensureUnregistered() {
-    if (!this._registered)
+    if (!this._registered) {
       return;
-
+    }
     this._pdfStreamConverterFactory.unregister();
     Cu.unload('resource://pdf.js/PdfStreamConverter.jsm');
     delete this._pdfStreamConverterFactory;
 
     this._pdfRedirectorFactory.unregister();
     Cu.unload('resource://pdf.js/PdfRedirector.jsm');
     delete this._pdfRedirectorFactory;
 
--- a/browser/extensions/pdfjs/content/PdfJsTelemetry.jsm
+++ b/browser/extensions/pdfjs/content/PdfJsTelemetry.jsm
@@ -9,63 +9,64 @@
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* jshint esnext:true */
+/* jshint esnext:true, maxlen: 100 */
+/* globals Components, Services */
 
 'use strict';
 
 this.EXPORTED_SYMBOLS = ['PdfJsTelemetry'];
 
 const Cu = Components.utils;
 Cu.import('resource://gre/modules/Services.jsm');
 
 this.PdfJsTelemetry = {
   onViewerIsUsed: function () {
-    let histogram = Services.telemetry.getHistogramById("PDF_VIEWER_USED");
+    let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_USED');
     histogram.add(true);
   },
   onFallback: function () {
-    let histogram = Services.telemetry.getHistogramById("PDF_VIEWER_FALLBACK_SHOWN");
+    let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_FALLBACK_SHOWN');
     histogram.add(true);
   },
   onDocumentSize: function (size) {
-    let histogram = Services.telemetry.getHistogramById("PDF_VIEWER_DOCUMENT_SIZE_KB");
+    let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_DOCUMENT_SIZE_KB');
     histogram.add(size / 1024);
   },
   onDocumentVersion: function (versionId) {
-    let histogram = Services.telemetry.getHistogramById("PDF_VIEWER_DOCUMENT_VERSION");
+    let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_DOCUMENT_VERSION');
     histogram.add(versionId);
   },
   onDocumentGenerator: function (generatorId) {
-    let histogram = Services.telemetry.getHistogramById("PDF_VIEWER_DOCUMENT_GENERATOR");
+    let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_DOCUMENT_GENERATOR');
     histogram.add(generatorId);
   },
   onEmbed: function (isObject) {
-    let histogram = Services.telemetry.getHistogramById("PDF_VIEWER_EMBED");
+    let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_EMBED');
     histogram.add(isObject);
   },
   onFontType: function (fontTypeId) {
-    let histogram = Services.telemetry.getHistogramById("PDF_VIEWER_FONT_TYPES");
+    let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_FONT_TYPES');
     histogram.add(fontTypeId);
   },
   onForm: function (isAcroform) {
-    let histogram = Services.telemetry.getHistogramById("PDF_VIEWER_FORM");
+    let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_FORM');
     histogram.add(isAcroform);
   },
   onPrint: function () {
-    let histogram = Services.telemetry.getHistogramById("PDF_VIEWER_PRINT");
+    let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_PRINT');
     histogram.add(true);
   },
   onStreamType: function (streamTypeId) {
-    let histogram = Services.telemetry.getHistogramById("PDF_VIEWER_STREAM_TYPES");
+    let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_STREAM_TYPES');
     histogram.add(streamTypeId);
   },
   onTimeToView: function (ms) {
-    let histogram = Services.telemetry.getHistogramById("PDF_VIEWER_TIME_TO_VIEW_MS");
+    let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_TIME_TO_VIEW_MS');
     histogram.add(ms);
   }
 };
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -11,17 +11,17 @@
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 /* jshint esnext:true */
 /* globals Components, Services, XPCOMUtils, NetUtil, PrivateBrowsingUtils,
-           dump, NetworkManager, PdfJsTelemetry */
+           dump, NetworkManager, PdfJsTelemetry, PdfjsContentUtils */
 
 'use strict';
 
 var EXPORTED_SYMBOLS = ['PdfStreamConverter'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
@@ -138,30 +138,33 @@ function getLocalizedStrings(path) {
   while (enumerator.hasMoreElements()) {
     var string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
     var key = string.key, property = 'textContent';
     var i = key.lastIndexOf('.');
     if (i >= 0) {
       property = key.substring(i + 1);
       key = key.substring(0, i);
     }
-    if (!(key in map))
+    if (!(key in map)) {
       map[key] = {};
+    }
     map[key][property] = string.value;
   }
   return map;
 }
 function getLocalizedString(strings, id, property) {
   property = property || 'textContent';
-  if (id in strings)
+  if (id in strings) {
     return strings[id][property];
+  }
   return id;
 }
 
 function makeContentReadable(obj, window) {
+  /* jshint -W027 */
   return Cu.cloneInto(obj, window);
 }
 
 // PDF data storage
 function PdfDataListener(length) {
   this.length = length; // less than 0, if length is unknown
   this.buffer = null;
   this.loaded = 0;
@@ -237,17 +240,17 @@ ChromeActions.prototype = {
   },
   download: function(data, sendResponse) {
     var self = this;
     var originalUrl = data.originalUrl;
     // The data may not be downloaded so we need just retry getting the pdf with
     // the original url.
     var originalUri = NetUtil.newURI(data.originalUrl);
     var filename = data.filename;
-    if (typeof filename !== 'string' || 
+    if (typeof filename !== 'string' ||
         (!/\.pdf$/i.test(filename) && !data.isAttachment)) {
       filename = 'document.pdf';
     }
     var blobUri = data.blobUrl ? NetUtil.newURI(data.blobUrl) : originalUri;
     var extHelperAppSvc =
           Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
              getService(Ci.nsIExternalHelperAppService);
     var frontWindow = Cc['@mozilla.org/embedcomp/window-watcher;1'].
@@ -256,18 +259,19 @@ ChromeActions.prototype = {
     var docIsPrivate = this.isInPrivateBrowsing();
     var netChannel = NetUtil.newChannel(blobUri);
     if ('nsIPrivateBrowsingChannel' in Ci &&
         netChannel instanceof Ci.nsIPrivateBrowsingChannel) {
       netChannel.setPrivate(docIsPrivate);
     }
     NetUtil.asyncFetch(netChannel, function(aInputStream, aResult) {
       if (!Components.isSuccessCode(aResult)) {
-        if (sendResponse)
+        if (sendResponse) {
           sendResponse(true);
+        }
         return;
       }
       // Create a nsIInputStreamChannel so we can set the url on the channel
       // so the filename will be correct.
       var channel = Cc['@mozilla.org/network/input-stream-channel;1'].
                        createInstance(Ci.nsIInputStreamChannel);
       channel.QueryInterface(Ci.nsIChannel);
       try {
@@ -291,21 +295,23 @@ ChromeActions.prototype = {
         onStartRequest: function(aRequest, aContext) {
           this.extListener = extHelperAppSvc.doContent(
             (data.isAttachment ? 'application/octet-stream' :
                                  'application/pdf'),
             aRequest, frontWindow, false);
           this.extListener.onStartRequest(aRequest, aContext);
         },
         onStopRequest: function(aRequest, aContext, aStatusCode) {
-          if (this.extListener)
+          if (this.extListener) {
             this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
+          }
           // Notify the content code we're done downloading.
-          if (sendResponse)
+          if (sendResponse) {
             sendResponse(false);
+          }
         },
         onDataAvailable: function(aRequest, aContext, aInputStream, aOffset,
                                   aCount) {
           this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
                                            aOffset, aCount);
         }
       };
 
@@ -313,19 +319,19 @@ ChromeActions.prototype = {
     });
   },
   getLocale: function() {
     return getStringPref('general.useragent.locale', 'en-US');
   },
   getStrings: function(data) {
     try {
       // Lazy initialization of localizedStrings
-      if (!('localizedStrings' in this))
+      if (!('localizedStrings' in this)) {
         this.localizedStrings = getLocalizedStrings('viewer.properties');
-
+      }
       var result = this.localizedStrings[data];
       return JSON.stringify(result || null);
     } catch (e) {
       log('Unable to retrive localized strings: ' + e);
       return 'null';
     }
   },
   supportsIntegratedFind: function() {
@@ -368,31 +374,31 @@ ChromeActions.prototype = {
       case 'documentStats':
         // documentStats can be called several times for one documents.
         // if stream/font types are reported, trying not to submit the same
         // enumeration value multiple times.
         var documentStats = probeInfo.stats;
         if (!documentStats || typeof documentStats !== 'object') {
           break;
         }
-        var streamTypes = documentStats.streamTypes;
+        var i, streamTypes = documentStats.streamTypes;
         if (Array.isArray(streamTypes)) {
           var STREAM_TYPE_ID_LIMIT = 20;
-          for (var i = 0; i < STREAM_TYPE_ID_LIMIT; i++) {
+          for (i = 0; i < STREAM_TYPE_ID_LIMIT; i++) {
             if (streamTypes[i] &&
                 !this.telemetryState.streamTypesUsed[i]) {
               PdfJsTelemetry.onStreamType(i);
               this.telemetryState.streamTypesUsed[i] = true;
             }
           }
         }
         var fontTypes = documentStats.fontTypes;
         if (Array.isArray(fontTypes)) {
           var FONT_TYPE_ID_LIMIT = 20;
-          for (var i = 0; i < FONT_TYPE_ID_LIMIT; i++) {
+          for (i = 0; i < FONT_TYPE_ID_LIMIT; i++) {
             if (fontTypes[i] &&
                 !this.telemetryState.fontTypesUsed[i]) {
               PdfJsTelemetry.onFontType(i);
               this.telemetryState.fontTypesUsed[i] = true;
             }
           }
         }
         break;
@@ -415,18 +421,19 @@ ChromeActions.prototype = {
       message = getLocalizedString(strings, 'unsupported_feature');
     }
     PdfJsTelemetry.onFallback();
     PdfjsContentUtils.displayWarning(domWindow, message, sendResponse,
       getLocalizedString(strings, 'open_with_different_viewer'),
       getLocalizedString(strings, 'open_with_different_viewer', 'accessKey'));
   },
   updateFindControlState: function(data) {
-    if (!this.supportsIntegratedFind())
+    if (!this.supportsIntegratedFind()) {
       return;
+    }
     // Verify what we're sending to the findbar.
     var result = data.result;
     var findPrevious = data.findPrevious;
     var findPreviousType = typeof findPrevious;
     if ((typeof result !== 'number' || result < 0 || result > 3) ||
         (findPreviousType !== 'undefined' && findPreviousType !== 'boolean')) {
       return;
     }
@@ -701,29 +708,30 @@ RequestListener.prototype.receive = func
   var action = event.detail.action;
   var data = event.detail.data;
   var sync = event.detail.sync;
   var actions = this.actions;
   if (!(action in actions)) {
     log('Unknown action: ' + action);
     return;
   }
+  var response;
   if (sync) {
-    var response = actions[action].call(this.actions, data);
+    response = actions[action].call(this.actions, data);
     event.detail.response = response;
   } else {
-    var response;
     if (!event.detail.responseExpected) {
       doc.documentElement.removeChild(message);
       response = null;
     } else {
       response = function sendResponse(response) {
         try {
           var listener = doc.createEvent('CustomEvent');
-          let detail = makeContentReadable({response: response}, doc.defaultView);
+          let detail = makeContentReadable({response: response},
+                                           doc.defaultView);
           listener.initCustomEvent('pdf.js.response', true, false, detail);
           return message.dispatchEvent(listener);
         } catch (e) {
           // doc is no longer accessible because the requestor is already
           // gone. unloaded content cannot receive the response anyway.
           return false;
         }
       };
@@ -982,17 +990,18 @@ PdfStreamConverter.prototype = {
 
   // nsIRequestObserver::onStopRequest
   onStopRequest: function(aRequest, aContext, aStatusCode) {
     if (!this.dataListener) {
       // Do nothing
       return;
     }
 
-    if (Components.isSuccessCode(aStatusCode))
+    if (Components.isSuccessCode(aStatusCode)) {
       this.dataListener.finish();
-    else
+    } else {
       this.dataListener.error(aStatusCode);
+    }
     delete this.dataListener;
     delete this.binaryStream;
   }
 };
 
--- a/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
+++ b/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
@@ -9,17 +9,18 @@
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- /*globals DEFAULT_PREFERENCES */
+/* jshint esnext:true */
+/* globals Components, Services, XPCOMUtils, DEFAULT_PREFERENCES */
 
 'use strict';
 
 var EXPORTED_SYMBOLS = ['PdfjsChromeUtils'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
@@ -62,110 +63,113 @@ let PdfjsChromeUtils = {
 
   /*
    * Public API
    */
 
   init: function () {
     if (!this._ppmm) {
       // global parent process message manager (PPMM)
-      this._ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIMessageBroadcaster);
-      this._ppmm.addMessageListener("PDFJS:Parent:clearUserPref", this);
-      this._ppmm.addMessageListener("PDFJS:Parent:setIntPref", this);
-      this._ppmm.addMessageListener("PDFJS:Parent:setBoolPref", this);
-      this._ppmm.addMessageListener("PDFJS:Parent:setCharPref", this);
-      this._ppmm.addMessageListener("PDFJS:Parent:setStringPref", this);
-      this._ppmm.addMessageListener("PDFJS:Parent:isDefaultHandlerApp", this);
+      this._ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1'].
+        getService(Ci.nsIMessageBroadcaster);
+      this._ppmm.addMessageListener('PDFJS:Parent:clearUserPref', this);
+      this._ppmm.addMessageListener('PDFJS:Parent:setIntPref', this);
+      this._ppmm.addMessageListener('PDFJS:Parent:setBoolPref', this);
+      this._ppmm.addMessageListener('PDFJS:Parent:setCharPref', this);
+      this._ppmm.addMessageListener('PDFJS:Parent:setStringPref', this);
+      this._ppmm.addMessageListener('PDFJS:Parent:isDefaultHandlerApp', this);
 
       // global dom message manager (MMg)
-      this._mmg = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
-      this._mmg.addMessageListener("PDFJS:Parent:getChromeWindow", this);
-      this._mmg.addMessageListener("PDFJS:Parent:getFindBar", this);
-      this._mmg.addMessageListener("PDFJS:Parent:displayWarning", this);
+      this._mmg = Cc['@mozilla.org/globalmessagemanager;1'].
+        getService(Ci.nsIMessageListenerManager);
+      this._mmg.addMessageListener('PDFJS:Parent:getChromeWindow', this);
+      this._mmg.addMessageListener('PDFJS:Parent:getFindBar', this);
+      this._mmg.addMessageListener('PDFJS:Parent:displayWarning', this);
 
       // observer to handle shutdown
-      Services.obs.addObserver(this, "quit-application", false);
+      Services.obs.addObserver(this, 'quit-application', false);
     }
   },
 
   uninit: function () {
     if (this._ppmm) {
-      this._ppmm.removeMessageListener("PDFJS:Parent:clearUserPref", this);
-      this._ppmm.removeMessageListener("PDFJS:Parent:setIntPref", this);
-      this._ppmm.removeMessageListener("PDFJS:Parent:setBoolPref", this);
-      this._ppmm.removeMessageListener("PDFJS:Parent:setCharPref", this);
-      this._ppmm.removeMessageListener("PDFJS:Parent:setStringPref", this);
-      this._ppmm.removeMessageListener("PDFJS:Parent:isDefaultHandlerApp", this);
+      this._ppmm.removeMessageListener('PDFJS:Parent:clearUserPref', this);
+      this._ppmm.removeMessageListener('PDFJS:Parent:setIntPref', this);
+      this._ppmm.removeMessageListener('PDFJS:Parent:setBoolPref', this);
+      this._ppmm.removeMessageListener('PDFJS:Parent:setCharPref', this);
+      this._ppmm.removeMessageListener('PDFJS:Parent:setStringPref', this);
+      this._ppmm.removeMessageListener('PDFJS:Parent:isDefaultHandlerApp',
+                                       this);
 
-      this._mmg.removeMessageListener("PDFJS:Parent:getChromeWindow", this);
-      this._mmg.removeMessageListener("PDFJS:Parent:getFindBar", this);
-      this._mmg.removeMessageListener("PDFJS:Parent:displayWarning", this);
+      this._mmg.removeMessageListener('PDFJS:Parent:getChromeWindow', this);
+      this._mmg.removeMessageListener('PDFJS:Parent:getFindBar', this);
+      this._mmg.removeMessageListener('PDFJS:Parent:displayWarning', this);
 
-      Services.obs.removeObserver(this, "quit-application", false);
+      Services.obs.removeObserver(this, 'quit-application', false);
 
       this._mmg = null;
       this._ppmm = null;
     }
   },
 
   /*
    * Called by the main module when preference changes are picked up
    * in the parent process. Observers don't propagate so we need to
    * instruct the child to refresh its configuration and (possibly)
    * the module's registration.
    */
   notifyChildOfSettingsChange: function () {
-    if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT &&
-        this._ppmm) {
+    if (Services.appinfo.processType ===
+        Services.appinfo.PROCESS_TYPE_DEFAULT && this._ppmm) {
       // XXX kinda bad, we want to get the parent process mm associated
       // with the content process. _ppmm is currently the global process
       // manager, which means this is going to fire to every child process
       // we have open. Unfortunately I can't find a way to get at that
       // process specific mm from js.
-      this._ppmm.broadcastAsyncMessage("PDFJS:Child:refreshSettings", {});
+      this._ppmm.broadcastAsyncMessage('PDFJS:Child:refreshSettings', {});
     }
   },
 
   /*
    * Events
    */
 
   observe: function(aSubject, aTopic, aData) {
-    if (aTopic == "quit-application") {
+    if (aTopic === 'quit-application') {
       this.uninit();
     }
   },
 
   receiveMessage: function (aMsg) {
     switch (aMsg.name) {
-      case "PDFJS:Parent:clearUserPref":
+      case 'PDFJS:Parent:clearUserPref':
         this._clearUserPref(aMsg.data.name);
         break;
-      case "PDFJS:Parent:setIntPref":
+      case 'PDFJS:Parent:setIntPref':
         this._setIntPref(aMsg.data.name, aMsg.data.value);
         break;
-      case "PDFJS:Parent:setBoolPref":
+      case 'PDFJS:Parent:setBoolPref':
         this._setBoolPref(aMsg.data.name, aMsg.data.value);
         break;
-      case "PDFJS:Parent:setCharPref":
+      case 'PDFJS:Parent:setCharPref':
         this._setCharPref(aMsg.data.name, aMsg.data.value);
         break;
-      case "PDFJS:Parent:setStringPref":
+      case 'PDFJS:Parent:setStringPref':
         this._setStringPref(aMsg.data.name, aMsg.data.value);
         break;
-      case "PDFJS:Parent:isDefaultHandlerApp":
+      case 'PDFJS:Parent:isDefaultHandlerApp':
         return this.isDefaultHandlerApp();
-      case "PDFJS:Parent:displayWarning":
+      case 'PDFJS:Parent:displayWarning':
         this._displayWarning(aMsg);
         break;
 
       // CPOW getters
-      case "PDFJS:Parent:getChromeWindow":
+      case 'PDFJS:Parent:getChromeWindow':
         return this._getChromeWindow(aMsg);
-      case "PDFJS:Parent:getFindBar":
+      case 'PDFJS:Parent:getFindBar':
         return this._getFindBar(aMsg);
     }
   },
 
   /*
    * Internal
    */
 
@@ -188,18 +192,18 @@ let PdfjsChromeUtils = {
     suitcase.setFindBar(wrapper);
     return true;
   },
 
   _ensurePreferenceAllowed: function (aPrefName) {
     let unPrefixedName = aPrefName.split(PREF_PREFIX + '.');
     if (unPrefixedName[0] !== '' ||
         this._allowedPrefNames.indexOf(unPrefixedName[1]) === -1) {
-      let msg = "'" + aPrefName + "' ";
-      msg += "can't be accessed from content. See PdfjsChromeUtils." 
+      let msg = '"' + aPrefName + '" ' +
+                'can\'t be accessed from content. See PdfjsChromeUtils.';
       throw new Error(msg);
     }
   },
 
   _clearUserPref: function (aPrefName) {
     this._ensurePreferenceAllowed(aPrefName);
     Services.prefs.clearUserPref(aPrefName);
   },
@@ -229,18 +233,18 @@ let PdfjsChromeUtils = {
 
   /*
    * Svc.mime doesn't have profile information in the child, so
    * we bounce this pdfjs enabled configuration check over to the
    * parent.
    */
   isDefaultHandlerApp: function () {
     var handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, 'pdf');
-    return !handlerInfo.alwaysAskBeforeHandling &&
-           handlerInfo.preferredAction == Ci.nsIHandlerInfo.handleInternally;
+    return (!handlerInfo.alwaysAskBeforeHandling &&
+            handlerInfo.preferredAction === Ci.nsIHandlerInfo.handleInternally);
   },
 
   /*
    * Display a notification warning when the renderer isn't sure
    * a pdf displayed correctly.
    */
   _displayWarning: function (aMsg) {
     let json = aMsg.data;
@@ -285,47 +289,47 @@ let PdfjsChromeUtils = {
  * directly on the findbar, so we wrap the findbar in a smaller
  * object here that supports the features pdf.js needs.
  */
 function PdfjsFindbarWrapper(aBrowser) {
   let tabbrowser = aBrowser.getTabBrowser();
   let tab;
   tab = tabbrowser.getTabForBrowser(aBrowser);
   this._findbar = tabbrowser.getFindBar(tab);
-};
+}
 
 PdfjsFindbarWrapper.prototype = {
   __exposedProps__: {
-    addEventListener: "r",
-    removeEventListener: "r",
-    updateControlState: "r",
+    addEventListener: 'r',
+    removeEventListener: 'r',
+    updateControlState: 'r',
   },
   _findbar: null,
 
   updateControlState: function (aResult, aFindPrevious) {
     this._findbar.updateControlState(aResult, aFindPrevious);
   },
 
   addEventListener: function (aType, aListener, aUseCapture, aWantsUntrusted) {
-    this._findbar.addEventListener(aType, aListener, aUseCapture, aWantsUntrusted);
+    this._findbar.addEventListener(aType, aListener, aUseCapture,
+                                   aWantsUntrusted);
   },
 
   removeEventListener: function (aType, aListener, aUseCapture) {
     this._findbar.removeEventListener(aType, aListener, aUseCapture);
   }
 };
 
 function PdfjsWindowWrapper(aBrowser) {
   this._window = aBrowser.ownerDocument.defaultView;
-};
+}
 
 PdfjsWindowWrapper.prototype = {
   __exposedProps__: {
-    valueOf: "r",
+    valueOf: 'r',
   },
   _window: null,
 
   valueOf: function () {
     return this._window.valueOf();
   }
 };
 
-
--- a/browser/extensions/pdfjs/content/PdfjsContentUtils.jsm
+++ b/browser/extensions/pdfjs/content/PdfjsContentUtils.jsm
@@ -1,22 +1,26 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+/* jshint esnext:true */
+/* globals Components, Services, XPCOMUtils */
 
 'use strict';
 
 var EXPORTED_SYMBOLS = ['PdfjsContentUtils'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
@@ -28,120 +32,123 @@ Cu.import('resource://gre/modules/Servic
 let PdfjsContentUtils = {
   _mm: null,
 
   /*
    * Public API
    */
 
   get isRemote() {
-    return Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+    return (Services.appinfo.processType ===
+            Services.appinfo.PROCESS_TYPE_CONTENT);
   },
 
   init: function () {
     // child *process* mm, or when loaded into the parent for in-content
     // support the psuedo child process mm 'child PPMM'.
     if (!this._mm) {
-      this._mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
-      this._mm.addMessageListener("PDFJS:Child:refreshSettings", this);
-      Services.obs.addObserver(this, "quit-application", false);
+      this._mm = Cc['@mozilla.org/childprocessmessagemanager;1'].
+        getService(Ci.nsISyncMessageSender);
+      this._mm.addMessageListener('PDFJS:Child:refreshSettings', this);
+      Services.obs.addObserver(this, 'quit-application', false);
     }
   },
 
   uninit: function () {
     if (this._mm) {
-      this._mm.removeMessageListener("PDFJS:Child:refreshSettings", this);
-      Services.obs.removeObserver(this, "quit-application");
+      this._mm.removeMessageListener('PDFJS:Child:refreshSettings', this);
+      Services.obs.removeObserver(this, 'quit-application');
     }
     this._mm = null;
   },
 
   /*
    * prefs utilities - the child does not have write access to prefs.
    * note, the pref names here are cross-checked against a list of
    * approved pdfjs prefs in chrome utils.
    */
 
   clearUserPref: function (aPrefName) {
-    this._mm.sendSyncMessage("PDFJS:Parent:clearUserPref", {
+    this._mm.sendSyncMessage('PDFJS:Parent:clearUserPref', {
       name: aPrefName
     });
   },
 
   setIntPref: function (aPrefName, aPrefValue) {
-    this._mm.sendSyncMessage("PDFJS:Parent:setIntPref", {
+    this._mm.sendSyncMessage('PDFJS:Parent:setIntPref', {
       name: aPrefName,
       value: aPrefValue
     });
   },
 
   setBoolPref: function (aPrefName, aPrefValue) {
-    this._mm.sendSyncMessage("PDFJS:Parent:setBoolPref", {
+    this._mm.sendSyncMessage('PDFJS:Parent:setBoolPref', {
       name: aPrefName,
       value: aPrefValue
     });
   },
 
   setCharPref: function (aPrefName, aPrefValue) {
-    this._mm.sendSyncMessage("PDFJS:Parent:setCharPref", {
+    this._mm.sendSyncMessage('PDFJS:Parent:setCharPref', {
       name: aPrefName,
       value: aPrefValue
     });
   },
 
   setStringPref: function (aPrefName, aPrefValue) {
-    this._mm.sendSyncMessage("PDFJS:Parent:setStringPref", {
+    this._mm.sendSyncMessage('PDFJS:Parent:setStringPref', {
       name: aPrefName,
       value: aPrefValue
     });
   },
 
   /*
    * Forwards default app query to the parent where we check various
    * handler app settings only available in the parent process.
    */
   isDefaultHandlerApp: function () {
-    return this._mm.sendSyncMessage("PDFJS:Parent:isDefaultHandlerApp")[0];
+    return this._mm.sendSyncMessage('PDFJS:Parent:isDefaultHandlerApp')[0];
   },
 
   /*
    * Request the display of a notification warning in the associated window
    * when the renderer isn't sure a pdf displayed correctly.
    */
   displayWarning: function (aWindow, aMessage, aCallback, aLabel, accessKey) {
     // the child's dom frame mm associated with the window.
     let winmm = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                        .getInterface(Ci.nsIDocShell)
                        .QueryInterface(Ci.nsIInterfaceRequestor)
                        .getInterface(Ci.nsIContentFrameMessageManager);
-    winmm.sendAsyncMessage("PDFJS:Parent:displayWarning", {
+    winmm.sendAsyncMessage('PDFJS:Parent:displayWarning', {
       message: aMessage,
       label: aLabel,
       accessKey: accessKey
     }, {
       callback: aCallback
     });
   },
 
   /*
    * Events
    */
 
   observe: function(aSubject, aTopic, aData) {
-    if (aTopic == "quit-application") {
+    if (aTopic === 'quit-application') {
       this.uninit();
     }
   },
 
   receiveMessage: function (aMsg) {
     switch (aMsg.name) {
-      case "PDFJS:Child:refreshSettings":
+      case 'PDFJS:Child:refreshSettings':
         // Only react to this if we are remote.
-        if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
-          let jsm = "resource://pdf.js/PdfJs.jsm";
+        if (Services.appinfo.processType ===
+            Services.appinfo.PROCESS_TYPE_CONTENT) {
+          let jsm = 'resource://pdf.js/PdfJs.jsm';
           let pdfjs = Components.utils.import(jsm, {}).PdfJs;
           pdfjs.updateRegistration();
         }
         break;
     }
   },
 
   /*
@@ -154,40 +161,44 @@ let PdfjsContentUtils = {
                         .sameTypeRootTreeItem
                         .QueryInterface(Ci.nsIDocShell)
                         .QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIContentFrameMessageManager);
     // Sync calls don't support cpow wrapping of returned results, so we
     // send over a small container for the object we want.
     let suitcase = {
       _window: null,
-      setChromeWindow: function (aObj) { this._window = aObj; }
-    }
-    if (!winmm.sendSyncMessage("PDFJS:Parent:getChromeWindow", {},
+      setChromeWindow: function (aObj) {
+        this._window = aObj;
+      }
+    };
+    if (!winmm.sendSyncMessage('PDFJS:Parent:getChromeWindow', {},
                                { suitcase: suitcase })[0]) {
-      Cu.reportError("A request for a CPOW wrapped chrome window " +
-                     "failed for unknown reasons.");
+      Cu.reportError('A request for a CPOW wrapped chrome window ' +
+                     'failed for unknown reasons.');
       return null;
     }
     return suitcase._window;
   },
 
   getFindBar: function (aWindow) {
     let winmm = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDocShell)
                         .sameTypeRootTreeItem
                         .QueryInterface(Ci.nsIDocShell)
                         .QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIContentFrameMessageManager);
     let suitcase = {
       _findbar: null,
-      setFindBar: function (aObj) { this._findbar = aObj; }
-    }
-    if (!winmm.sendSyncMessage("PDFJS:Parent:getFindBar", {},
+      setFindBar: function (aObj) {
+        this._findbar = aObj;
+      }
+    };
+    if (!winmm.sendSyncMessage('PDFJS:Parent:getFindBar', {},
                                { suitcase: suitcase })[0]) {
-      Cu.reportError("A request for a CPOW wrapped findbar " +
-                     "failed for unknown reasons.");
+      Cu.reportError('A request for a CPOW wrapped findbar ' +
+                     'failed for unknown reasons.');
       return null;
     }
     return suitcase._findbar;
   }
 };
 
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -17,18 +17,18 @@
 /*jshint globalstrict: false */
 /* globals PDFJS */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '1.0.978';
-PDFJS.build = '20bf84a';
+PDFJS.version = '1.0.1040';
+PDFJS.build = '997096f';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -2798,17 +2798,23 @@ var Metadata = PDFJS.Metadata = (functio
 // However, PDF needs a bit more state, which we store here.
 
 // Minimal font size that would be used during canvas fillText operations.
 var MIN_FONT_SIZE = 16;
 // Maximum font size that would be used during canvas fillText operations.
 var MAX_FONT_SIZE = 100;
 var MAX_GROUP_SIZE = 4096;
 
+// Heuristic value used when enforcing minimum line widths.
+var MIN_WIDTH_FACTOR = 0.65;
+
 var COMPILE_TYPE3_GLYPHS = true;
+var MAX_SIZE_TO_COMPILE = 1000;
+
+var FULL_CHUNK_HEIGHT = 16;
 
 function createScratchCanvas(width, height) {
   var canvas = document.createElement('canvas');
   canvas.width = width;
   canvas.height = height;
   return canvas;
 }
 
@@ -2932,17 +2938,17 @@ function addContextCurrentTransform(ctx)
 }
 
 var CachedCanvases = (function CachedCanvasesClosure() {
   var cache = {};
   return {
     getCanvas: function CachedCanvases_getCanvas(id, width, height,
                                                  trackTransform) {
       var canvasEntry;
-      if (id in cache) {
+      if (cache[id] !== undefined) {
         canvasEntry = cache[id];
         canvasEntry.canvas.width = width;
         canvasEntry.canvas.height = height;
         // reset canvas transform for emulated mozCurrentTransform, if needed
         canvasEntry.context.setTransform(1, 0, 0, 1, 0, 0);
       } else {
         var canvas = createScratchCanvas(width, height);
         var ctx = canvas.getContext('2d');
@@ -3146,16 +3152,17 @@ var CanvasExtraState = (function CanvasE
     this.charSpacing = 0;
     this.wordSpacing = 0;
     this.textHScale = 1;
     this.textRenderingMode = TextRenderingMode.FILL;
     this.textRise = 0;
     // Default fore and background colors
     this.fillColor = '#000000';
     this.strokeColor = '#000000';
+    this.patternFill = false;
     // Note: fill alpha applies to all non-stroking operations
     this.fillAlpha = 1;
     this.strokeAlpha = 1;
     this.lineWidth = 1;
     this.activeSMask = null; // nonclonable field (see the save method below)
 
     this.old = old;
   }
@@ -3198,16 +3205,17 @@ var CanvasGraphics = (function CanvasGra
     this.baseTransformStack = [];
     this.groupLevel = 0;
     this.smaskStack = [];
     this.smaskCounter = 0;
     this.tempSMask = null;
     if (canvasCtx) {
       addContextCurrentTransform(canvasCtx);
     }
+    this.cachedGetSinglePixelWidth = null;
   }
 
   function putBinaryImageData(ctx, imgData) {
     if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) {
       ctx.putImageData(imgData, 0, 0);
       return;
     }
 
@@ -3218,23 +3226,21 @@ var CanvasGraphics = (function CanvasGra
     // the data passed to putImageData()). |n| shouldn't be too small, however,
     // because too many putImageData() calls will slow things down.
     //
     // Note: as written, if the last chunk is partial, the putImageData() call
     // will (conceptually) put pixels past the bounds of the canvas.  But
     // that's ok; any such pixels are ignored.
 
     var height = imgData.height, width = imgData.width;
-    var fullChunkHeight = 16;
-    var fracChunks = height / fullChunkHeight;
-    var fullChunks = Math.floor(fracChunks);
-    var totalChunks = Math.ceil(fracChunks);
-    var partialChunkHeight = height - fullChunks * fullChunkHeight;
-
-    var chunkImgData = ctx.createImageData(width, fullChunkHeight);
+    var partialChunkHeight = height % FULL_CHUNK_HEIGHT;
+    var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
+    var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
+
+    var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
     var srcPos = 0, destPos;
     var src = imgData.data;
     var dest = chunkImgData.data;
     var i, j, thisChunkHeight, elemsInThisChunk;
 
     // There are multiple forms in which the pixel data can be passed, and
     // imgData.kind tells us which one this is.
     if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
@@ -3244,17 +3250,17 @@ var CanvasGraphics = (function CanvasGra
         new Uint32ArrayView(dest);
       var dest32DataLength = dest32.length;
       var fullSrcDiff = (width + 7) >> 3;
       var white = 0xFFFFFFFF;
       var black = (PDFJS.isLittleEndian || !PDFJS.hasCanvasTypedArrays) ?
         0xFF000000 : 0x000000FF;
       for (i = 0; i < totalChunks; i++) {
         thisChunkHeight =
-          (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
+          (i < fullChunks) ? FULL_CHUNK_HEIGHT : partialChunkHeight;
         destPos = 0;
         for (j = 0; j < thisChunkHeight; j++) {
           var srcDiff = srcLength - srcPos;
           var k = 0;
           var kEnd = (srcDiff > fullSrcDiff) ? width : srcDiff * 8 - 7;
           var kEndUnrolled = kEnd & ~7;
           var mask = 0;
           var srcByte = 0;
@@ -3279,110 +3285,108 @@ var CanvasGraphics = (function CanvasGra
             mask >>= 1;
           }
         }
         // We ran out of input. Make all remaining pixels transparent.
         while (destPos < dest32DataLength) {
           dest32[destPos++] = 0;
         }
 
-        ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
+        ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
       }
     } else if (imgData.kind === ImageKind.RGBA_32BPP) {
       // RGBA, 32-bits per pixel.
 
       j = 0;
-      elemsInThisChunk = width * fullChunkHeight * 4;
+      elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
       for (i = 0; i < fullChunks; i++) {
         dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
         srcPos += elemsInThisChunk;
 
         ctx.putImageData(chunkImgData, 0, j);
-        j += fullChunkHeight;
+        j += FULL_CHUNK_HEIGHT;
       }
       if (i < totalChunks) {
         elemsInThisChunk = width * partialChunkHeight * 4;
         dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
         ctx.putImageData(chunkImgData, 0, j);
       }
 
     } else if (imgData.kind === ImageKind.RGB_24BPP) {
       // RGB, 24-bits per pixel.
-      thisChunkHeight = fullChunkHeight;
+      thisChunkHeight = FULL_CHUNK_HEIGHT;
       elemsInThisChunk = width * thisChunkHeight;
       for (i = 0; i < totalChunks; i++) {
         if (i >= fullChunks) {
-          thisChunkHeight =partialChunkHeight;
+          thisChunkHeight = partialChunkHeight;
           elemsInThisChunk = width * thisChunkHeight;
         }
 
         destPos = 0;
         for (j = elemsInThisChunk; j--;) {
           dest[destPos++] = src[srcPos++];
           dest[destPos++] = src[srcPos++];
           dest[destPos++] = src[srcPos++];
           dest[destPos++] = 255;
         }
-        ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
+        ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
       }
     } else {
       error('bad image kind: ' + imgData.kind);
     }
   }
 
   function putBinaryImageMask(ctx, imgData) {
     var height = imgData.height, width = imgData.width;
-    var fullChunkHeight = 16;
-    var fracChunks = height / fullChunkHeight;
-    var fullChunks = Math.floor(fracChunks);
-    var totalChunks = Math.ceil(fracChunks);
-    var partialChunkHeight = height - fullChunks * fullChunkHeight;
-
-    var chunkImgData = ctx.createImageData(width, fullChunkHeight);
+    var partialChunkHeight = height % FULL_CHUNK_HEIGHT;
+    var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
+    var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
+
+    var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
     var srcPos = 0;
     var src = imgData.data;
     var dest = chunkImgData.data;
 
     for (var i = 0; i < totalChunks; i++) {
       var thisChunkHeight =
-        (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
+        (i < fullChunks) ? FULL_CHUNK_HEIGHT : partialChunkHeight;
 
       // Expand the mask so it can be used by the canvas.  Any required
       // inversion has already been handled.
       var destPos = 3; // alpha component offset
       for (var j = 0; j < thisChunkHeight; j++) {
         var mask = 0;
         for (var k = 0; k < width; k++) {
           if (!mask) {
             var elem = src[srcPos++];
             mask = 128;
           }
           dest[destPos] = (elem & mask) ? 0 : 255;
           destPos += 4;
           mask >>= 1;
         }
       }
-      ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
+      ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
     }
   }
 
   function copyCtxState(sourceCtx, destCtx) {
     var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha',
                       'lineWidth', 'lineCap', 'lineJoin', 'miterLimit',
                       'globalCompositeOperation', 'font'];
     for (var i = 0, ii = properties.length; i < ii; i++) {
       var property = properties[i];
-      if (property in sourceCtx) {
+      if (sourceCtx[property] !== undefined) {
         destCtx[property] = sourceCtx[property];
       }
     }
-    if ('setLineDash' in sourceCtx) {
+    if (sourceCtx.setLineDash !== undefined) {
       destCtx.setLineDash(sourceCtx.getLineDash());
       destCtx.lineDashOffset =  sourceCtx.lineDashOffset;
-    } else if ('mozDash' in sourceCtx) {
+    } else if (sourceCtx.mozDashOffset !== undefined) {
       destCtx.mozDash = sourceCtx.mozDash;
       destCtx.mozDashOffset = sourceCtx.mozDashOffset;
     }
   }
 
   function composeSMaskBackdrop(bytes, r0, g0, b0) {
     var length = bytes.length;
     for (var i = 3; i < length; i += 4) {
@@ -3599,17 +3603,17 @@ var CanvasGraphics = (function CanvasGra
     setLineJoin: function CanvasGraphics_setLineJoin(style) {
       this.ctx.lineJoin = LINE_JOIN_STYLES[style];
     },
     setMiterLimit: function CanvasGraphics_setMiterLimit(limit) {
       this.ctx.miterLimit = limit;
     },
     setDash: function CanvasGraphics_setDash(dashArray, dashPhase) {
       var ctx = this.ctx;
-      if ('setLineDash' in ctx) {
+      if (ctx.setLineDash !== undefined) {
         ctx.setLineDash(dashArray);
         ctx.lineDashOffset = dashPhase;
       } else {
         ctx.mozDash = dashArray;
         ctx.mozDashOffset = dashPhase;
       }
     },
     setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) {
@@ -3734,20 +3738,24 @@ var CanvasGraphics = (function CanvasGra
     restore: function CanvasGraphics_restore() {
       if (this.stateStack.length !== 0) {
         if (this.current.activeSMask !== null) {
           this.endSMaskGroup();
         }
 
         this.current = this.stateStack.pop();
         this.ctx.restore();
+
+        this.cachedGetSinglePixelWidth = null;
       }
     },
     transform: function CanvasGraphics_transform(a, b, c, d, e, f) {
       this.ctx.transform(a, b, c, d, e, f);
+
+      this.cachedGetSinglePixelWidth = null;
     },
 
     // Path
     constructPath: function CanvasGraphics_constructPath(ops, args) {
       var ctx = this.ctx;
       var current = this.current;
       var x = current.x, y = current.y;
       for (var i = 0, j = 0, ii = ops.length; i < ii; i++) {
@@ -3811,19 +3819,19 @@ var CanvasGraphics = (function CanvasGra
     },
     closePath: function CanvasGraphics_closePath() {
       this.ctx.closePath();
     },
     stroke: function CanvasGraphics_stroke(consumePath) {
       consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
       var ctx = this.ctx;
       var strokeColor = this.current.strokeColor;
-      if (this.current.lineWidth === 0) {
-        ctx.lineWidth = this.getSinglePixelWidth();
-      }
+      // Prevent drawing too thin lines by enforcing a minimum line width.
+      ctx.lineWidth = Math.max(this.getSinglePixelWidth() * MIN_WIDTH_FACTOR,
+                               this.current.lineWidth);
       // For stroke we want to temporarily change the global alpha to the
       // stroking alpha.
       ctx.globalAlpha = this.current.strokeAlpha;
       if (strokeColor && strokeColor.hasOwnProperty('type') &&
           strokeColor.type === 'Pattern') {
         // for patterns, we transform to pattern space, calculate
         // the pattern, call stroke, and restore to user space
         ctx.save();
@@ -3842,20 +3850,20 @@ var CanvasGraphics = (function CanvasGra
     closeStroke: function CanvasGraphics_closeStroke() {
       this.closePath();
       this.stroke();
     },
     fill: function CanvasGraphics_fill(consumePath) {
       consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
       var ctx = this.ctx;
       var fillColor = this.current.fillColor;
+      var isPatternFill = this.current.patternFill;
       var needRestore = false;
 
-      if (fillColor && fillColor.hasOwnProperty('type') &&
-          fillColor.type === 'Pattern') {
+      if (isPatternFill) {
         ctx.save();
         ctx.fillStyle = fillColor.getPattern(ctx, this);
         needRestore = true;
       }
 
       if (this.pendingEOFill) {
         if (ctx.mozFillRule !== undefined) {
           ctx.mozFillRule = 'evenodd';
@@ -4138,17 +4146,23 @@ var CanvasGraphics = (function CanvasGra
         ctx.scale(textHScale, -1);
       } else {
         ctx.scale(textHScale, 1);
       }
 
       var lineWidth = current.lineWidth;
       var scale = current.textMatrixScale;
       if (scale === 0 || lineWidth === 0) {
-        lineWidth = this.getSinglePixelWidth();
+        var fillStrokeMode = current.textRenderingMode &
+          TextRenderingMode.FILL_STROKE_MASK;
+        if (fillStrokeMode === TextRenderingMode.STROKE ||
+            fillStrokeMode === TextRenderingMode.FILL_STROKE) {
+          this.cachedGetSinglePixelWidth = null;
+          lineWidth = this.getSinglePixelWidth() * MIN_WIDTH_FACTOR;
+        }
       } else {
         lineWidth /= scale;
       }
 
       if (fontSizeScale !== 1.0) {
         ctx.scale(fontSizeScale, fontSizeScale);
         lineWidth /= fontSizeScale;
       }
@@ -4319,26 +4333,28 @@ var CanvasGraphics = (function CanvasGra
       }
       return pattern;
     },
     setStrokeColorN: function CanvasGraphics_setStrokeColorN(/*...*/) {
       this.current.strokeColor = this.getColorN_Pattern(arguments);
     },
     setFillColorN: function CanvasGraphics_setFillColorN(/*...*/) {
       this.current.fillColor = this.getColorN_Pattern(arguments);
+      this.current.patternFill = true;
     },
     setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) {
       var color = Util.makeCssRgb(r, g, b);
       this.ctx.strokeStyle = color;
       this.current.strokeColor = color;
     },
     setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) {
       var color = Util.makeCssRgb(r, g, b);
       this.ctx.fillStyle = color;
       this.current.fillColor = color;
+      this.current.patternFill = false;
     },
 
     shadingFill: function CanvasGraphics_shadingFill(patternIR) {
       var ctx = this.ctx;
 
       this.save();
       var pattern = getShadingPatternFromIR(patternIR);
       ctx.fillStyle = pattern.getPattern(ctx, this, true);
@@ -4587,21 +4603,22 @@ var CanvasGraphics = (function CanvasGra
         });
       }
       this.restore();
     },
 
     paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
       var ctx = this.ctx;
       var width = img.width, height = img.height;
+      var fillColor = this.current.fillColor;
+      var isPatternFill = this.current.patternFill;
 
       var glyph = this.processingType3;
 
-      if (COMPILE_TYPE3_GLYPHS && glyph && !('compiled' in glyph)) {
-        var MAX_SIZE_TO_COMPILE = 1000;
+      if (COMPILE_TYPE3_GLYPHS && glyph && glyph.compiled === undefined) {
         if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
           glyph.compiled =
             compileType3Glyph({data: img.data, width: width, height: height});
         } else {
           glyph.compiled = null;
         }
       }
 
@@ -4613,79 +4630,77 @@ var CanvasGraphics = (function CanvasGra
       var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
       var maskCtx = maskCanvas.context;
       maskCtx.save();
 
       putBinaryImageMask(maskCtx, img);
 
       maskCtx.globalCompositeOperation = 'source-in';
 
-      var fillColor = this.current.fillColor;
-      maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
-                          fillColor.type === 'Pattern') ?
+      maskCtx.fillStyle = isPatternFill ?
                           fillColor.getPattern(maskCtx, this) : fillColor;
       maskCtx.fillRect(0, 0, width, height);
 
       maskCtx.restore();
 
       this.paintInlineImageXObject(maskCanvas.canvas);
     },
 
     paintImageMaskXObjectRepeat:
       function CanvasGraphics_paintImageMaskXObjectRepeat(imgData, scaleX,
                                                           scaleY, positions) {
       var width = imgData.width;
       var height = imgData.height;
-      var ctx = this.ctx;
+      var fillColor = this.current.fillColor;
+      var isPatternFill = this.current.patternFill;
 
       var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
       var maskCtx = maskCanvas.context;
       maskCtx.save();
 
       putBinaryImageMask(maskCtx, imgData);
 
       maskCtx.globalCompositeOperation = 'source-in';
 
-      var fillColor = this.current.fillColor;
-      maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
-        fillColor.type === 'Pattern') ?
-        fillColor.getPattern(maskCtx, this) : fillColor;
+      maskCtx.fillStyle = isPatternFill ?
+                          fillColor.getPattern(maskCtx, this) : fillColor;
       maskCtx.fillRect(0, 0, width, height);
 
       maskCtx.restore();
 
+      var ctx = this.ctx;
       for (var i = 0, ii = positions.length; i < ii; i += 2) {
         ctx.save();
         ctx.transform(scaleX, 0, 0, scaleY, positions[i], positions[i + 1]);
         ctx.scale(1, -1);
         ctx.drawImage(maskCanvas.canvas, 0, 0, width, height,
           0, -1, 1, 1);
         ctx.restore();
       }
     },
 
     paintImageMaskXObjectGroup:
       function CanvasGraphics_paintImageMaskXObjectGroup(images) {
       var ctx = this.ctx;
 
+      var fillColor = this.current.fillColor;
+      var isPatternFill = this.current.patternFill;
       for (var i = 0, ii = images.length; i < ii; i++) {
         var image = images[i];
         var width = image.width, height = image.height;
 
         var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
         var maskCtx = maskCanvas.context;
         maskCtx.save();
 
         putBinaryImageMask(maskCtx, image);
 
         maskCtx.globalCompositeOperation = 'source-in';
 
-        var fillColor = this.current.fillColor;
-        maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
-                            fillColor.type === 'Pattern') ?
+        maskCtx.fillStyle = isPatternFill ?
                             fillColor.getPattern(maskCtx, this) : fillColor;
         maskCtx.fillRect(0, 0, width, height);
 
         maskCtx.restore();
 
         ctx.save();
         ctx.transform.apply(ctx, image.transform);
         ctx.scale(1, -1);
@@ -4878,21 +4893,24 @@ var CanvasGraphics = (function CanvasGra
         } else {
           ctx.clip();
         }
         this.pendingClip = null;
       }
       ctx.beginPath();
     },
     getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) {
-      var inverse = this.ctx.mozCurrentTransformInverse;
-      // max of the current horizontal and vertical scale
-      return Math.sqrt(Math.max(
-        (inverse[0] * inverse[0] + inverse[1] * inverse[1]),
-        (inverse[2] * inverse[2] + inverse[3] * inverse[3])));
+      if (this.cachedGetSinglePixelWidth === null) {
+        var inverse = this.ctx.mozCurrentTransformInverse;
+        // max of the current horizontal and vertical scale
+        this.cachedGetSinglePixelWidth = Math.sqrt(Math.max(
+          (inverse[0] * inverse[0] + inverse[1] * inverse[1]),
+          (inverse[2] * inverse[2] + inverse[3] * inverse[3])));
+      }
+      return this.cachedGetSinglePixelWidth;
     },
     getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) {
         var transform = this.ctx.mozCurrentTransform;
         return [
           transform[0] * x + transform[2] * y + transform[4],
           transform[1] * x + transform[3] * y + transform[5]
         ];
     }
@@ -5819,17 +5837,16 @@ var FontFaceObject = (function FontFaceO
       }
       return this.compiledGlyphs[character];
     }
   };
   return FontFaceObject;
 })();
 
 
-var HIGHLIGHT_OFFSET = 4; // px
 var ANNOT_MIN_SIZE = 10; // px
 
 var AnnotationUtils = (function AnnotationUtilsClosure() {
   // TODO(mack): This dupes some of the logic in CanvasGraphics.setFont()
   function setTextStyles(element, item, fontObj) {
 
     var style = element.style;
     style.fontSize = item.fontSize + 'px';
@@ -5846,51 +5863,46 @@ var AnnotationUtils = (function Annotati
 
     var fontName = fontObj.loadedName;
     var fontFamily = fontName ? '"' + fontName + '", ' : '';
     // Use a reasonable default font if the font doesn't specify a fallback
     var fallbackName = fontObj.fallbackName || 'Helvetica, sans-serif';
     style.fontFamily = fontFamily + fallbackName;
   }
 
-  // TODO(mack): Remove this, it's not really that helpful.
-  function getEmptyContainer(tagName, rect, borderWidth) {
-    var bWidth = borderWidth || 0;
-    var element = document.createElement(tagName);
-    element.style.borderWidth = bWidth + 'px';
-    var width = rect[2] - rect[0] - 2 * bWidth;
-    var height = rect[3] - rect[1] - 2 * bWidth;
-    element.style.width = width + 'px';
-    element.style.height = height + 'px';
-    return element;
-  }
-
-  function initContainer(item) {
-    var container = getEmptyContainer('section', item.rect, item.borderWidth);
-    container.style.backgroundColor = item.color;
-
-    var color = item.color;
-    item.colorCssRgb = Util.makeCssRgb(Math.round(color[0] * 255),
-                                       Math.round(color[1] * 255),
-                                       Math.round(color[2] * 255));
-
-    var highlight = document.createElement('div');
-    highlight.className = 'annotationHighlight';
-    highlight.style.left = highlight.style.top = -HIGHLIGHT_OFFSET + 'px';
-    highlight.style.right = highlight.style.bottom = -HIGHLIGHT_OFFSET + 'px';
-    highlight.setAttribute('hidden', true);
-
-    item.highlightElement = highlight;
-    container.appendChild(item.highlightElement);
-
+  function initContainer(item, drawBorder) {
+    var container = document.createElement('section');
+    var cstyle = container.style;
+    var width = item.rect[2] - item.rect[0];
+    var height = item.rect[3] - item.rect[1];
+
+    var bWidth = item.borderWidth || 0;
+    if (bWidth) {
+      width = width - 2 * bWidth;
+      height = height - 2 * bWidth;
+      cstyle.borderWidth = bWidth + 'px';
+      var color = item.color;
+      if (drawBorder && color) {
+        cstyle.borderStyle = 'solid';
+        cstyle.borderColor = Util.makeCssRgb(Math.round(color[0] * 255),
+                                             Math.round(color[1] * 255),
+                                             Math.round(color[2] * 255));
+      }
+    }
+    cstyle.width = width + 'px';
+    cstyle.height = height + 'px';
     return container;
   }
 
   function getHtmlElementForTextWidgetAnnotation(item, commonObjs) {
-    var element = getEmptyContainer('div', item.rect, 0);
+    var element = document.createElement('div');
+    var width = item.rect[2] - item.rect[0];
+    var height = item.rect[3] - item.rect[1];
+    element.style.width = width + 'px';
+    element.style.height = height + 'px';
     element.style.display = 'table';
 
     var content = document.createElement('div');
     content.textContent = item.fieldValue;
     var textAlignment = item.textAlignment;
     content.style.textAlign = ['left', 'center', 'right'][textAlignment];
     content.style.verticalAlign = 'middle';
     content.style.display = 'table-cell';
@@ -5910,17 +5922,17 @@ var AnnotationUtils = (function Annotati
     // sanity check because of OOo-generated PDFs
     if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) {
       rect[3] = rect[1] + ANNOT_MIN_SIZE;
     }
     if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
       rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
     }
 
-    var container = initContainer(item);
+    var container = initContainer(item, false);
     container.className = 'annotText';
 
     var image  = document.createElement('img');
     image.style.height = container.style.height;
     image.style.width = container.style.width;
     var iconName = item.name;
     image.src = PDFJS.imageResourcesPath + 'annotation-' +
       iconName.toLowerCase() + '.svg';
@@ -6019,22 +6031,19 @@ var AnnotationUtils = (function Annotati
     contentWrapper.appendChild(content);
     container.appendChild(image);
     container.appendChild(contentWrapper);
 
     return container;
   }
 
   function getHtmlElementForLinkAnnotation(item) {
-    var container = initContainer(item);
+    var container = initContainer(item, true);
     container.className = 'annotLink';
 
-    container.style.borderColor = item.colorCssRgb;
-    container.style.borderStyle = 'solid';
-
     var link = document.createElement('a');
     link.href = link.title = item.url || '';
 
     container.appendChild(link);
 
     return container;
   }
 
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -17,18 +17,18 @@
 /*jshint globalstrict: false */
 /* globals PDFJS */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '1.0.978';
-PDFJS.build = '20bf84a';
+PDFJS.version = '1.0.1040';
+PDFJS.build = '997096f';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -2293,16 +2293,20 @@ var Page = (function PageClosure() {
 /**
  * The `PDFDocument` holds all the data of the PDF file. Compared to the
  * `PDFDoc`, this one doesn't have any job management code.
  * Right now there exists one PDFDocument on the main thread + one object
  * for each worker. If there is no worker support enabled, there are two
  * `PDFDocument` objects on the main thread created.
  */
 var PDFDocument = (function PDFDocumentClosure() {
+  var FINGERPRINT_FIRST_BYTES = 1024;
+  var EMPTY_FINGERPRINT = '\x00\x00\x00\x00\x00\x00\x00' +
+    '\x00\x00\x00\x00\x00\x00\x00\x00\x00';
+
   function PDFDocument(pdfManager, arg, password) {
     if (isStream(arg)) {
       init.call(this, pdfManager, arg, password);
     } else if (isArrayBuffer(arg)) {
       init.call(this, pdfManager, new Stream(arg), password);
     } else {
       error('PDFDocument: Unknown argument type');
     }
@@ -2504,26 +2508,35 @@ var PDFDocument = (function PDFDocumentC
               info('Bad value in document info for "' + key + '"');
             }
           }
         }
       }
       return shadow(this, 'documentInfo', docInfo);
     },
     get fingerprint() {
-      var xref = this.xref, hash, fileID = '';
+      var xref = this.xref, idArray, hash, fileID = '';
 
       if (xref.trailer.has('ID')) {
-        hash = stringToBytes(xref.trailer.get('ID')[0]);
-      } else {
-        hash = calculateMD5(this.stream.bytes.subarray(0, 100), 0, 100);
+        idArray = xref.trailer.get('ID');
+      }
+      if (idArray && isArray(idArray) && idArray[0] !== EMPTY_FINGERPRINT) {
+        hash = stringToBytes(idArray[0]);
+      } else {
+        if (this.stream.ensureRange) {
+          this.stream.ensureRange(0,
+            Math.min(FINGERPRINT_FIRST_BYTES, this.stream.end));
+        }
+        hash = calculateMD5(this.stream.bytes.subarray(0,
+          FINGERPRINT_FIRST_BYTES), 0, FINGERPRINT_FIRST_BYTES);
       }
 
       for (var i = 0, n = hash.length; i < n; i++) {
-        fileID += hash[i].toString(16);
+        var hex = hash[i].toString(16);
+        fileID += hex.length === 1 ? '0' + hex : hex;
       }
 
       return shadow(this, 'fingerprint', fileID);
     },
 
     getPage: function PDFDocument_getPage(pageIndex) {
       return this.catalog.getPage(pageIndex);
     },
@@ -4369,39 +4382,53 @@ var Annotation = (function AnnotationClo
     var data = this.data = {};
 
     data.subtype = dict.get('Subtype').name;
     var rect = dict.get('Rect') || [0, 0, 0, 0];
     data.rect = Util.normalizeRect(rect);
     data.annotationFlags = dict.get('F');
 
     var color = dict.get('C');
-    if (isArray(color) && color.length === 3) {
-      // TODO(mack): currently only supporting rgb; need support different
-      // colorspaces
-      data.color = color;
-    } else {
+    if (!color) {
+      // The PDF spec does not mention how a missing color array is interpreted.
+      // Adobe Reader seems to default to black in this case.
       data.color = [0, 0, 0];
+    } else if (isArray(color)) {
+      switch (color.length) {
+        case 0:
+          // Empty array denotes transparent border.
+          data.color = null;
+          break;
+        case 1:
+          // TODO: implement DeviceGray
+          break;
+        case 3:
+          data.color = color;
+          break;
+        case 4:
+          // TODO: implement DeviceCMYK
+          break;
+      }
     }
 
     // Some types of annotations have border style dict which has more
     // info than the border array
     if (dict.has('BS')) {
       var borderStyle = dict.get('BS');
       data.borderWidth = borderStyle.has('W') ? borderStyle.get('W') : 1;
     } else {
       var borderArray = dict.get('Border') || [0, 0, 1];
       data.borderWidth = borderArray[2] || 0;
 
       // TODO: implement proper support for annotations with line dash patterns.
       var dashArray = borderArray[3];
       if (data.borderWidth > 0 && dashArray) {
         if (!isArray(dashArray)) {
           // Ignore the border if dashArray is not actually an array,
-          // this is consistent with the behaviour in Adobe Reader. 
+          // this is consistent with the behaviour in Adobe Reader.
           data.borderWidth = 0;
         } else {
           var dashArrayLength = dashArray.length;
           if (dashArrayLength > 0) {
             // According to the PDF specification: the elements in a dashArray
             // shall be numbers that are nonnegative and not all equal to zero.
             var isInvalid = false;
             var numPositive = 0;
@@ -4814,21 +4841,17 @@ var LinkAnnotation = (function LinkAnnot
   // Lets URLs beginning with 'www.' default to using the 'http://' protocol.
   function addDefaultProtocolToUrl(url) {
     if (url && url.indexOf('www.') === 0) {
       return ('http://' + url);
     }
     return url;
   }
 
-  Util.inherit(LinkAnnotation, InteractiveAnnotation, {
-    hasOperatorList: function LinkAnnotation_hasOperatorList() {
-      return false;
-    }
-  });
+  Util.inherit(LinkAnnotation, InteractiveAnnotation, { });
 
   return LinkAnnotation;
 })();
 
 
 var PDFFunction = (function PDFFunctionClosure() {
   var CONSTRUCT_SAMPLED = 0;
   var CONSTRUCT_INTERPOLATED = 2;
@@ -10115,17 +10138,17 @@ var PartialEvaluator = (function Partial
             operatorList.addOp(OPS.endGroup, [groupOptions]);
           }
         });
     },
 
     buildPaintImageXObject:
         function PartialEvaluator_buildPaintImageXObject(resources, image,
                                                          inline, operatorList,
-                                                         cacheKey, cache) {
+                                                         cacheKey, imageCache) {
       var self = this;
       var dict = image.dict;
       var w = dict.get('Width', 'W');
       var h = dict.get('Height', 'H');
 
       if (!(w && isNum(w)) || !(h && isNum(h))) {
         warn('Image dimensions are missing, or not numbers.');
         return;
@@ -10153,19 +10176,20 @@ var PartialEvaluator = (function Partial
 
         imgData = PDFImage.createMask(imgArray, width, height,
                                       image instanceof DecodeStream,
                                       inverseDecode);
         imgData.cached = true;
         args = [imgData];
         operatorList.addOp(OPS.paintImageMaskXObject, args);
         if (cacheKey) {
-          cache.key = cacheKey;
-          cache.fn = OPS.paintImageMaskXObject;
-          cache.args = args;
+          imageCache[cacheKey] = {
+            fn: OPS.paintImageMaskXObject,
+            args: args
+          };
         }
         return;
       }
 
       var softMask = (dict.get('SMask', 'SM') || false);
       var mask = (dict.get('Mask') || false);
 
       var SMALL_IMAGE_DIMENSIONS = 200;
@@ -10204,19 +10228,20 @@ var PartialEvaluator = (function Partial
             [imgData.data.buffer]);
         }).then(undefined, function (reason) {
           warn('Unable to decode image: ' + reason);
           self.handler.send('obj', [objId, self.pageIndex, 'Image', null]);
         });
 
       operatorList.addOp(OPS.paintImageXObject, args);
       if (cacheKey) {
-        cache.key = cacheKey;
-        cache.fn = OPS.paintImageXObject;
-        cache.args = args;
+        imageCache[cacheKey] = {
+          fn: OPS.paintImageXObject,
+          args: args
+        };
       }
     },
 
     handleSMask: function PartialEvaluator_handleSmask(smask, resources,
                                                        operatorList,
                                                        stateManager) {
       var smaskContent = smask.get('G');
       var smaskOptions = {
@@ -10600,18 +10625,18 @@ var PartialEvaluator = (function Partial
 
           switch (fn | 0) {
             case OPS.paintXObject:
               if (args[0].code) {
                 break;
               }
               // eagerly compile XForm objects
               var name = args[0].name;
-              if (imageCache.key === name) {
-                operatorList.addOp(imageCache.fn, imageCache.args);
+              if (imageCache[name] !== undefined) {
+                operatorList.addOp(imageCache[name].fn, imageCache[name].args);
                 args = null;
                 continue;
               }
 
               var xobj = xobjs.get(name);
               if (xobj) {
                 assert(isStream(xobj), 'XObject should be a stream');
 
@@ -10650,20 +10675,23 @@ var PartialEvaluator = (function Partial
                                         operatorList, stateManager.state).
                 then(function (loadedName) {
                   operatorList.addDependency(loadedName);
                   operatorList.addOp(OPS.setFont, [loadedName, fontSize]);
                   next(resolve, reject);
                 }, reject);
             case OPS.endInlineImage:
               var cacheKey = args[0].cacheKey;
-              if (cacheKey && imageCache.key === cacheKey) {
-                operatorList.addOp(imageCache.fn, imageCache.args);
-                args = null;
-                continue;
+              if (cacheKey) {
+                var cacheEntry = imageCache[cacheKey];
+                if (cacheEntry !== undefined) {
+                  operatorList.addOp(cacheEntry.fn, cacheEntry.args);
+                  args = null;
+                  continue;
+                }
               }
               self.buildPaintImageXObject(resources, args[0], true,
                 operatorList, cacheKey, imageCache);
               args = null;
               continue;
             case OPS.showText:
               args[0] = self.handleText(args[0], stateManager.state);
               break;
@@ -13820,19 +13848,22 @@ var stdFontMap = {
   'CourierNew': 'Courier',
   'CourierNew-Bold': 'Courier-Bold',
   'CourierNew-BoldItalic': 'Courier-BoldOblique',
   'CourierNew-Italic': 'Courier-Oblique',
   'CourierNewPS-BoldItalicMT': 'Courier-BoldOblique',
   'CourierNewPS-BoldMT': 'Courier-Bold',
   'CourierNewPS-ItalicMT': 'Courier-Oblique',
   'CourierNewPSMT': 'Courier',
+  'Helvetica': 'Helvetica',
   'Helvetica-Bold': 'Helvetica-Bold',
   'Helvetica-BoldItalic': 'Helvetica-BoldOblique',
+  'Helvetica-BoldOblique': 'Helvetica-BoldOblique',
   'Helvetica-Italic': 'Helvetica-Oblique',
+  'Helvetica-Oblique':'Helvetica-Oblique',
   'Symbol-Bold': 'Symbol',
   'Symbol-BoldItalic': 'Symbol',
   'Symbol-Italic': 'Symbol',
   'TimesNewRoman': 'Times-Roman',
   'TimesNewRoman-Bold': 'Times-Bold',
   'TimesNewRoman-BoldItalic': 'Times-BoldItalic',
   'TimesNewRoman-Italic': 'Times-Italic',
   'TimesNewRomanPS': 'Times-Roman',
@@ -13848,16 +13879,20 @@ var stdFontMap = {
   'TimesNewRomanPSMT-Italic': 'Times-Italic'
 };
 
 /**
  * Holds the map of the non-standard fonts that might be included as a standard
  * fonts without glyph data.
  */
 var nonStdFontMap = {
+  'CenturyGothic': 'Helvetica',
+  'CenturyGothic-Bold': 'Helvetica-Bold',
+  'CenturyGothic-BoldItalic': 'Helvetica-BoldOblique',
+  'CenturyGothic-Italic': 'Helvetica-Oblique',
   'ComicSansMS': 'Comic Sans MS',
   'ComicSansMS-Bold': 'Comic Sans MS-Bold',
   'ComicSansMS-BoldItalic': 'Comic Sans MS-BoldItalic',
   'ComicSansMS-Italic': 'Comic Sans MS-Italic',
   'LucidaConsole': 'Courier',
   'LucidaConsole-Bold': 'Courier-Bold',
   'LucidaConsole-BoldItalic': 'Courier-BoldOblique',
   'LucidaConsole-Italic': 'Courier-Oblique',
@@ -13871,17 +13906,18 @@ var nonStdFontMap = {
   'MS-Mincho-Italic': 'MS Mincho-Italic',
   'MS-PGothic': 'MS PGothic',
   'MS-PGothic-Bold': 'MS PGothic-Bold',
   'MS-PGothic-BoldItalic': 'MS PGothic-BoldItalic',
   'MS-PGothic-Italic': 'MS PGothic-Italic',
   'MS-PMincho': 'MS PMincho',
   'MS-PMincho-Bold': 'MS PMincho-Bold',
   'MS-PMincho-BoldItalic': 'MS PMincho-BoldItalic',
-  'MS-PMincho-Italic': 'MS PMincho-Italic'
+  'MS-PMincho-Italic': 'MS PMincho-Italic',
+  'Wingdings': 'ZapfDingbats'
 };
 
 var serifFonts = {
   'Adobe Jenson': true, 'Adobe Text': true, 'Albertus': true,
   'Aldus': true, 'Alexandria': true, 'Algerian': true,
   'American Typewriter': true, 'Antiqua': true, 'Apex': true,
   'Arno': true, 'Aster': true, 'Aurora': true,
   'Baskerville': true, 'Bell': true, 'Bembo': true,
@@ -15954,17 +15990,18 @@ var Font = (function FontClosure() {
         // attempting to recover by assuming that no file exists.
         warn('Font file is empty in "' + name + '" (' + this.loadedName + ')');
       }
 
       this.missingFile = true;
       // The file data is not specified. Trying to fix the font name
       // to be used with the canvas.font.
       var fontName = name.replace(/[,_]/g, '-');
-      var isStandardFont = fontName in stdFontMap;
+      var isStandardFont = !!stdFontMap[fontName] ||
+        (nonStdFontMap[fontName] && !!stdFontMap[nonStdFontMap[fontName]]);
       fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
 
       this.bold = (fontName.search(/bold/gi) !== -1);
       this.italic = ((fontName.search(/oblique/gi) !== -1) ||
                      (fontName.search(/italic/gi) !== -1));
 
       // Use 'name' instead of 'fontName' here because the original
       // name ArialBlack for example will be replaced by Helvetica.
@@ -16000,16 +16037,20 @@ var Font = (function FontClosure() {
         for (charCode in properties.differences) {
           fontChar = GlyphsUnicode[properties.differences[charCode]];
           if (!fontChar) {
             continue;
           }
           this.toFontChar[charCode] = fontChar;
         }
       } else if (/Dingbats/i.test(fontName)) {
+        if (/Wingdings/i.test(name)) {
+          warn('Wingdings font without embedded font file, ' +
+               'falling back to the ZapfDingbats encoding.');
+        }
         var dingbats = Encodings.ZapfDingbatsEncoding;
         for (charCode in dingbats) {
           fontChar = DingbatsGlyphsUnicode[dingbats[charCode]];
           if (!fontChar) {
             continue;
           }
           this.toFontChar[charCode] = fontChar;
         }
@@ -16185,16 +16226,17 @@ var Font = (function FontClosure() {
       // canvas if left in their current position. Also, move characters if the
       // font was symbolic and there is only an identity unicode map since the
       // characters probably aren't in the correct position (fixes an issue
       // with firefox and thuluthfont).
       if ((usedFontCharCodes[fontCharCode] !== undefined ||
            fontCharCode <= 0x1f || // Control chars
            fontCharCode === 0x7F || // Control char
            fontCharCode === 0xAD || // Soft hyphen
+           fontCharCode === 0xA0 || // Non breaking space
            (fontCharCode >= 0x80 && fontCharCode <= 0x9F) || // Control chars
            // Prevent drawing characters in the specials unicode block.
            (fontCharCode >= 0xFFF0 && fontCharCode <= 0xFFFF) ||
            (isSymbolic && isIdentityUnicode)) &&
           nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END) { // Room left.
         // Loop to try and find a free spot in the private use area.
         do {
           fontCharCode = nextAvailableFontCharCode++;
@@ -18203,44 +18245,50 @@ function type1FontGlyphMapping(propertie
   if (properties.baseEncodingName) {
     // If a valid base encoding name was used, the mapping is initialized with
     // that.
     baseEncoding = Encodings[properties.baseEncodingName];
     for (charCode = 0; charCode < baseEncoding.length; charCode++) {
       glyphId = glyphNames.indexOf(baseEncoding[charCode]);
       if (glyphId >= 0) {
         charCodeToGlyphId[charCode] = glyphId;
+      } else {
+        charCodeToGlyphId[charCode] = 0; // notdef
       }
     }
   } else if (!!(properties.flags & FontFlags.Symbolic)) {
     // For a symbolic font the encoding should be the fonts built-in
     // encoding.
     for (charCode in builtInEncoding) {
       charCodeToGlyphId[charCode] = builtInEncoding[charCode];
     }
   } else {
     // For non-symbolic fonts that don't have a base encoding the standard
     // encoding should be used.
     baseEncoding = Encodings.StandardEncoding;
     for (charCode = 0; charCode < baseEncoding.length; charCode++) {
       glyphId = glyphNames.indexOf(baseEncoding[charCode]);
       if (glyphId >= 0) {
         charCodeToGlyphId[charCode] = glyphId;
+      } else {
+        charCodeToGlyphId[charCode] = 0; // notdef
       }
     }
   }
 
   // Lastly, merge in the differences.
   var differences = properties.differences;
   if (differences) {
     for (charCode in differences) {
       var glyphName = differences[charCode];
       glyphId = glyphNames.indexOf(glyphName);
       if (glyphId >= 0) {
         charCodeToGlyphId[charCode] = glyphId;
+      } else {
+        charCodeToGlyphId[charCode] = 0; // notdef
       }
     }
   }
   return charCodeToGlyphId;
 }
 
 /*
  * CharStrings are encoded following the the CharString Encoding sequence
@@ -29406,26 +29454,24 @@ var Metrics = {
 
 
 var EOF = {};
 
 function isEOF(v) {
   return (v === EOF);
 }
 
+var MAX_LENGTH_TO_CACHE = 1000;
+
 var Parser = (function ParserClosure() {
   function Parser(lexer, allowStreams, xref) {
     this.lexer = lexer;
     this.allowStreams = allowStreams;
     this.xref = xref;
-    this.imageCache = {
-      length: 0,
-      adler32: 0,
-      stream: null
-    };
+    this.imageCache = {};
     this.refill();
   }
 
   Parser.prototype = {
     refill: function Parser_refill() {
       this.buf1 = this.lexer.getObj();
       this.buf2 = this.lexer.getObj();
     },
@@ -29506,113 +29552,188 @@ var Parser = (function ParserClosure() {
           str = cipherTransform.decryptString(str);
         }
         return str;
       }
 
       // simple object
       return buf1;
     },
-    makeInlineImage: function Parser_makeInlineImage(cipherTransform) {
-      var lexer = this.lexer;
-      var stream = lexer.stream;
-
-      // parse dictionary
-      var dict = new Dict(null);
-      while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) {
-        if (!isName(this.buf1)) {
-          error('Dictionary key must be a name object');
-        }
-
-        var key = this.buf1.name;
-        this.shift();
-        if (isEOF(this.buf1)) {
-          break;
-        }
-        dict.set(key, this.getObj(cipherTransform));
-      }
-
-      // parse image stream
-      var startPos = stream.pos;
-
-      // searching for the /EI\s/
-      var state = 0, ch, i, ii;
-      var E = 0x45, I = 0x49, SPACE = 0x20, NL = 0xA, CR = 0xD;
+    /**
+     * Find the end of the stream by searching for the /EI\s/.
+     * @returns {number} The inline stream length.
+     */
+    findDefaultInlineStreamEnd:
+        function Parser_findDefaultInlineStreamEnd(stream) {
+      var E = 0x45, I = 0x49, SPACE = 0x20, LF = 0xA, CR = 0xD;
+      var startPos = stream.pos, state = 0, ch, i, n, followingBytes;
       while ((ch = stream.getByte()) !== -1) {
         if (state === 0) {
           state = (ch === E) ? 1 : 0;
         } else if (state === 1) {
           state = (ch === I) ? 2 : 0;
         } else {
           assert(state === 2);
-          if (ch === SPACE || ch === NL || ch === CR) {
+          if (ch === SPACE || ch === LF || ch === CR) {
             // Let's check the next five bytes are ASCII... just be sure.
-            var n = 5;
-            var followingBytes = stream.peekBytes(n);
+            n = 5;
+            followingBytes = stream.peekBytes(n);
             for (i = 0; i < n; i++) {
               ch = followingBytes[i];
-              if (ch !== NL && ch !== CR && (ch < SPACE || ch > 0x7F)) {
+              if (ch !== LF && ch !== CR && (ch < SPACE || ch > 0x7F)) {
                 // Not a LF, CR, SPACE or any visible ASCII character, i.e.
                 // it's binary stuff. Resetting the state.
                 state = 0;
                 break;
               }
             }
             if (state === 2) {
-              break;  // finished!
+              break;  // Finished!
             }
           } else {
             state = 0;
           }
         }
       }
-
-      var length = (stream.pos - 4) - startPos;
+      return ((stream.pos - 4) - startPos);
+    },
+    /**
+     * Find the EOD (end-of-data) marker '~>' (i.e. TILDE + GT) of the stream.
+     * @returns {number} The inline stream length.
+     */
+    findASCII85DecodeInlineStreamEnd:
+        function Parser_findASCII85DecodeInlineStreamEnd(stream) {
+      var TILDE = 0x7E, GT = 0x3E;
+      var startPos = stream.pos, ch, length;
+      while ((ch = stream.getByte()) !== -1) {
+        if (ch === TILDE && stream.peekByte() === GT) {
+          stream.skip();
+          break;
+        }
+      }
+      length = stream.pos - startPos;
+      if (ch === -1) {
+        warn('Inline ASCII85Decode image stream: ' +
+             'EOD marker not found, searching for /EI/ instead.');
+        stream.skip(-length); // Reset the stream position.
+        return this.findDefaultInlineStreamEnd(stream);
+      }
+      this.inlineStreamSkipEI(stream);
+      return length;
+    },
+    /**
+     * Find the EOD (end-of-data) marker '>' (i.e. GT) of the stream.
+     * @returns {number} The inline stream length.
+     */
+    findASCIIHexDecodeInlineStreamEnd:
+        function Parser_findASCIIHexDecodeInlineStreamEnd(stream) {
+      var GT = 0x3E;
+      var startPos = stream.pos, ch, length;
+      while ((ch = stream.getByte()) !== -1) {
+        if (ch === GT) {
+          break;
+        }
+      }
+      length = stream.pos - startPos;
+      if (ch === -1) {
+        warn('Inline ASCIIHexDecode image stream: ' +
+             'EOD marker not found, searching for /EI/ instead.');
+        stream.skip(-length); // Reset the stream position.
+        return this.findDefaultInlineStreamEnd(stream);
+      }
+      this.inlineStreamSkipEI(stream);
+      return length;
+    },
+    /**
+     * Skip over the /EI/ for streams where we search for an EOD marker.
+     */
+    inlineStreamSkipEI: function Parser_inlineStreamSkipEI(stream) {
+      var E = 0x45, I = 0x49;
+      var state = 0, ch;
+      while ((ch = stream.getByte()) !== -1) {
+        if (state === 0) {
+          state = (ch === E) ? 1 : 0;
+        } else if (state === 1) {
+          state = (ch === I) ? 2 : 0;
+        } else if (state === 2) {
+          break;
+        }
+      }
+    },
+    makeInlineImage: function Parser_makeInlineImage(cipherTransform) {
+      var lexer = this.lexer;
+      var stream = lexer.stream;
+
+      // Parse dictionary.
+      var dict = new Dict(null);
+      while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) {
+        if (!isName(this.buf1)) {
+          error('Dictionary key must be a name object');
+        }
+        var key = this.buf1.name;
+        this.shift();
+        if (isEOF(this.buf1)) {
+          break;
+        }
+        dict.set(key, this.getObj(cipherTransform));
+      }
+
+      // Extract the name of the first (i.e. the current) image filter.
+      var filter = this.fetchIfRef(dict.get('Filter', 'F')), filterName;
+      if (isName(filter)) {
+        filterName = filter.name;
+      } else if (isArray(filter) && isName(filter[0])) {
+        filterName = filter[0].name;
+      }
+
+      // Parse image stream.
+      var startPos = stream.pos, length, i, ii;
+      if (filterName === 'ASCII85Decide' || filterName === 'A85') {
+        length = this.findASCII85DecodeInlineStreamEnd(stream);
+      } else if (filterName === 'ASCIIHexDecode' || filterName === 'AHx') {
+        length = this.findASCIIHexDecodeInlineStreamEnd(stream);
+      } else {
+        length = this.findDefaultInlineStreamEnd(stream);
+      }
       var imageStream = stream.makeSubStream(startPos, length, dict);
 
-      // trying to cache repeat images, first we are trying to "warm up" caching
-      // using length, then comparing adler32
-      var MAX_LENGTH_TO_CACHE = 1000;
-      var cacheImage = false, adler32;
-      if (length < MAX_LENGTH_TO_CACHE && this.imageCache.length === length) {
+      // Cache all images below the MAX_LENGTH_TO_CACHE threshold by their
+      // adler32 checksum.
+      var adler32;
+      if (length < MAX_LENGTH_TO_CACHE) {
         var imageBytes = imageStream.getBytes();
         imageStream.reset();
 
         var a = 1;
         var b = 0;
         for (i = 0, ii = imageBytes.length; i < ii; ++i) {
-          a = (a + (imageBytes[i] & 0xff)) % 65521;
-          b = (b + a) % 65521;
-        }
-        adler32 = (b << 16) | a;
-
-        if (this.imageCache.stream && this.imageCache.adler32 === adler32) {
+          // No modulo required in the loop if imageBytes.length < 5552.
+          a += imageBytes[i] & 0xff;
+          b += a;
+        }
+        adler32 = ((b % 65521) << 16) | (a % 65521);
+
+        if (this.imageCache.adler32 === adler32) {
           this.buf2 = Cmd.get('EI');
           this.shift();
 
-          this.imageCache.stream.reset();
-          return this.imageCache.stream;
-        }
-        cacheImage = true;
-      }
-      if (!cacheImage && !this.imageCache.stream) {
-        this.imageCache.length = length;
-        this.imageCache.stream = null;
+          this.imageCache[adler32].reset();
+          return this.imageCache[adler32];
+        }
       }
 
       if (cipherTransform) {
         imageStream = cipherTransform.createStream(imageStream, length);
       }
 
       imageStream = this.filter(imageStream, dict, length);
       imageStream.dict = dict;
-      if (cacheImage) {
+      if (adler32 !== undefined) {
         imageStream.cacheKey = 'inline_' + length + '_' + adler32;
-        this.imageCache.adler32 = adler32;
-        this.imageCache.stream = imageStream;
+        this.imageCache[adler32] = imageStream;
       }
 
       this.buf2 = Cmd.get('EI');
       this.shift();
 
       return imageStream;
     },
     fetchIfRef: function Parser_fetchIfRef(obj) {
@@ -29750,32 +29871,16 @@ var Parser = (function ParserClosure() {
             }
             return new PredictorStream(
               new LZWStream(stream, maybeLength, earlyChange),
               maybeLength, params);
           }
           return new LZWStream(stream, maybeLength, earlyChange);
         }
         if (name === 'DCTDecode' || name === 'DCT') {
-          // According to the specification: for inline images, the ID operator
-          // shall be followed by a single whitespace character (unless it uses
-          // ASCII85Decode or ASCIIHexDecode filters).
-          // In practice this only seems to be followed for inline JPEG images,
-          // and generally ignoring the first byte of the stream if it is a
-          // whitespace char can even *cause* issues (e.g. in the CCITTFaxDecode
-          // filters used in issue2984.pdf).
-          // Hence when the first byte of the stream of an inline JPEG image is
-          // a whitespace character, we thus simply skip over it.
-          if (isCmd(this.buf1, 'ID')) {
-            var firstByte = stream.peekByte();
-            if (firstByte === 0x0A /* LF */ || firstByte === 0x0D /* CR */ ||
-                firstByte === 0x20 /* SPACE */) {
-              stream.skip();
-            }
-          }
           xrefStreamStats[StreamType.DCT] = true;
           return new JpegStream(stream, maybeLength, stream.dict, this.xref);
         }
         if (name === 'JPXDecode' || name === 'JPX') {
           xrefStreamStats[StreamType.JPX] = true;
           return new JpxStream(stream, maybeLength, stream.dict);
         }
         if (name === 'ASCII85Decode' || name === 'A85') {
@@ -31313,18 +31418,25 @@ var PredictorStream = (function Predicto
  * Depending on the type of JPEG a JpegStream is handled in different ways. For
  * JPEG's that are supported natively such as DeviceGray and DeviceRGB the image
  * data is stored and then loaded by the browser.  For unsupported JPEG's we use
  * a library to decode these images and the stream behaves like all the other
  * DecodeStreams.
  */
 var JpegStream = (function JpegStreamClosure() {
   function JpegStream(stream, maybeLength, dict, xref) {
-    // TODO: per poppler, some images may have 'junk' before that
-    // need to be removed
+    // Some images may contain 'junk' before the SOI (start-of-image) marker.
+    // Note: this seems to mainly affect inline images.
+    var ch;
+    while ((ch = stream.getByte()) !== -1) {
+      if (ch === 0xFF) { // Find the first byte of the SOI marker (0xFFD8).
+        stream.skip(-1); // Reset the stream position to the SOI.
+        break;
+      }
+    }
     this.stream = stream;
     this.maybeLength = maybeLength;
     this.dict = dict;
 
     DecodeStream.call(this, maybeLength);
   }
 
   JpegStream.prototype = Object.create(DecodeStream.prototype);
@@ -32489,17 +32601,17 @@ var CCITTFaxStream = (function CCITTFaxS
           blackPixels ^= 1;
         }
       }
 
       var gotEOL = false;
 
       if (!this.eoblock && this.row === this.rows - 1) {
         this.eof = true;
-      } else if (this.eoline || !this.byteAlign) {
+      } else {
         code1 = this.lookBits(12);
         if (this.eoline) {
           while (code1 !== EOF && code1 !== 1) {
             this.eatBits(1);
             code1 = this.lookBits(12);
           }
         } else {
           while (code1 === 0) {
@@ -34859,22 +34971,16 @@ var JpxImage = (function JpxImageClosure
                   precinctsSizes.push({
                     PPx: precinctsSize & 0xF,
                     PPy: precinctsSize >> 4
                   });
                 }
                 cod.precinctsSizes = precinctsSizes;
               }
               var unsupported = [];
-              if (cod.sopMarkerUsed) {
-                unsupported.push('sopMarkerUsed');
-              }
-              if (cod.ephMarkerUsed) {
-                unsupported.push('ephMarkerUsed');
-              }
               if (cod.selectiveArithmeticCodingBypass) {
                 unsupported.push('selectiveArithmeticCodingBypass');
               }
               if (cod.resetContextProbabilities) {
                 unsupported.push('resetContextProbabilities');
               }
               if (cod.terminationOnEachCodingPass) {
                 unsupported.push('terminationOnEachCodingPass');
@@ -35232,16 +35338,240 @@ var JpxImage = (function JpxImageClosure
           }
           i = 0;
         }
         l = 0;
       }
       throw new Error('JPX Error: Out of packets');
     };
   }
+  function ResolutionPositionComponentLayerIterator(context) {
+    var siz = context.SIZ;
+    var tileIndex = context.currentTile.index;
+    var tile = context.tiles[tileIndex];
+    var layersCount = tile.codingStyleDefaultParameters.layersCount;
+    var componentsCount = siz.Csiz;
+    var l, r, c, p;
+    var maxDecompositionLevelsCount = 0;
+    for (c = 0; c < componentsCount; c++) {
+      var component = tile.components[c];
+      maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount,
+        component.codingStyleParameters.decompositionLevelsCount);
+    }
+    var maxNumPrecinctsInLevel = new Int32Array(
+      maxDecompositionLevelsCount + 1);
+    for (r = 0; r <= maxDecompositionLevelsCount; ++r) {
+      var maxNumPrecincts = 0;
+      for (c = 0; c < componentsCount; ++c) {
+        var resolutions = tile.components[c].resolutions;
+        if (r < resolutions.length) {
+          maxNumPrecincts = Math.max(maxNumPrecincts,
+            resolutions[r].precinctParameters.numprecincts);
+        }
+      }
+      maxNumPrecinctsInLevel[r] = maxNumPrecincts;
+    }
+    l = 0;
+    r = 0;
+    c = 0;
+    p = 0;
+    
+    this.nextPacket = function JpxImage_nextPacket() {
+      // Section B.12.1.3 Resolution-position-component-layer
+      for (; r <= maxDecompositionLevelsCount; r++) {
+        for (; p < maxNumPrecinctsInLevel[r]; p++) {
+          for (; c < componentsCount; c++) {
+            var component = tile.components[c];
+            if (r > component.codingStyleParameters.decompositionLevelsCount) {
+              continue;
+            }
+            var resolution = component.resolutions[r];
+            var numprecincts = resolution.precinctParameters.numprecincts;
+            if (p >= numprecincts) {
+              continue;
+            }
+            for (; l < layersCount;) {
+              var packet = createPacket(resolution, p, l);
+              l++;
+              return packet;
+            }
+            l = 0;
+          }
+          c = 0;
+        }
+        p = 0;
+      }
+      throw new Error('JPX Error: Out of packets');
+    };
+  }
+  function PositionComponentResolutionLayerIterator(context) {
+    var siz = context.SIZ;
+    var tileIndex = context.currentTile.index;
+    var tile = context.tiles[tileIndex];
+    var layersCount = tile.codingStyleDefaultParameters.layersCount;
+    var componentsCount = siz.Csiz;
+    var precinctsSizes = getPrecinctSizesInImageScale(tile);
+    var precinctsIterationSizes = precinctsSizes;
+    var l = 0, r = 0, c = 0, px = 0, py = 0;
+
+    this.nextPacket = function JpxImage_nextPacket() {
+      // Section B.12.1.4 Position-component-resolution-layer
+      for (; py < precinctsIterationSizes.maxNumHigh; py++) {
+        for (; px < precinctsIterationSizes.maxNumWide; px++) {
+          for (; c < componentsCount; c++) {
+            var component = tile.components[c];
+            var decompositionLevelsCount =
+              component.codingStyleParameters.decompositionLevelsCount;
+            for (; r <= decompositionLevelsCount; r++) {
+              var resolution = component.resolutions[r];
+              var sizeInImageScale =
+                precinctsSizes.components[c].resolutions[r];
+              var k = getPrecinctIndexIfExist(
+                px,
+                py,
+                sizeInImageScale,
+                precinctsIterationSizes,
+                resolution);
+              if (k === null) {
+                continue;
+              }
+              for (; l < layersCount;) {
+                var packet = createPacket(resolution, k, l);
+                l++;
+                return packet;
+              }
+              l = 0;
+            }
+            r = 0;
+          }
+          c = 0;
+        }
+        px = 0;
+      }
+      throw new Error('JPX Error: Out of packets');
+    };
+  }
+  function ComponentPositionResolutionLayerIterator(context) {
+    var siz = context.SIZ;
+    var tileIndex = context.currentTile.index;
+    var tile = context.tiles[tileIndex];
+    var layersCount = tile.codingStyleDefaultParameters.layersCount;
+    var componentsCount = siz.Csiz;
+    var precinctsSizes = getPrecinctSizesInImageScale(tile);
+    var l = 0, r = 0, c = 0, px = 0, py = 0;
+    
+    this.nextPacket = function JpxImage_nextPacket() {
+      // Section B.12.1.5 Component-position-resolution-layer
+      for (; c < componentsCount; ++c) {
+        var component = tile.components[c];
+        var precinctsIterationSizes = precinctsSizes.components[c];
+        var decompositionLevelsCount =
+          component.codingStyleParameters.decompositionLevelsCount;
+        for (; py < precinctsIterationSizes.maxNumHigh; py++) {
+          for (; px < precinctsIterationSizes.maxNumWide; px++) {
+            for (; r <= decompositionLevelsCount; r++) {
+              var resolution = component.resolutions[r];
+              var sizeInImageScale = precinctsIterationSizes.resolutions[r];
+              var k = getPrecinctIndexIfExist(
+                px,
+                py,
+                sizeInImageScale,
+                precinctsIterationSizes,
+                resolution);
+              if (k === null) {
+                continue;
+              }
+              for (; l < layersCount;) {
+                var packet = createPacket(resolution, k, l);
+                l++;
+                return packet;
+              }
+              l = 0;
+            }
+            r = 0;
+          }
+          px = 0;
+        }
+        py = 0;
+      }
+      throw new Error('JPX Error: Out of packets');
+    };
+  }
+  function getPrecinctIndexIfExist(
+    pxIndex, pyIndex, sizeInImageScale, precinctIterationSizes, resolution) {
+    var posX = pxIndex * precinctIterationSizes.minWidth;
+    var posY = pyIndex * precinctIterationSizes.minHeight;
+    if (posX % sizeInImageScale.width !== 0 ||
+        posY % sizeInImageScale.height !== 0) {
+      return null;
+    }
+    var startPrecinctRowIndex =
+      (posY / sizeInImageScale.width) *
+      resolution.precinctParameters.numprecinctswide;
+    return (posX / sizeInImageScale.height) + startPrecinctRowIndex;
+  }
+  function getPrecinctSizesInImageScale(tile) {
+    var componentsCount = tile.components.length;
+    var minWidth = Number.MAX_VALUE;
+    var minHeight = Number.MAX_VALUE;
+    var maxNumWide = 0;
+    var maxNumHigh = 0;
+    var sizePerComponent = new Array(componentsCount);
+    for (var c = 0; c < componentsCount; c++) {
+      var component = tile.components[c];
+      var decompositionLevelsCount =
+        component.codingStyleParameters.decompositionLevelsCount;
+      var sizePerResolution = new Array(decompositionLevelsCount + 1);
+      var minWidthCurrentComponent = Number.MAX_VALUE;
+      var minHeightCurrentComponent = Number.MAX_VALUE;
+      var maxNumWideCurrentComponent = 0;
+      var maxNumHighCurrentComponent = 0;
+      var scale = 1;
+      for (var r = decompositionLevelsCount; r >= 0; --r) {
+        var resolution = component.resolutions[r];
+        var widthCurrentResolution =
+          scale * resolution.precinctParameters.precinctWidth;
+        var heightCurrentResolution =
+          scale * resolution.precinctParameters.precinctHeight;
+        minWidthCurrentComponent = Math.min(
+          minWidthCurrentComponent,
+          widthCurrentResolution);
+        minHeightCurrentComponent = Math.min(
+          minHeightCurrentComponent,
+          heightCurrentResolution);
+        maxNumWideCurrentComponent = Math.max(maxNumWideCurrentComponent,
+          resolution.precinctParameters.numprecinctswide);
+        maxNumHighCurrentComponent = Math.max(maxNumHighCurrentComponent,
+          resolution.precinctParameters.numprecinctshigh);
+        sizePerResolution[r] = {
+          width: widthCurrentResolution,
+          height: heightCurrentResolution
+        };
+        scale <<= 1;
+      }
+      minWidth = Math.min(minWidth, minWidthCurrentComponent);
+      minHeight = Math.min(minHeight, minHeightCurrentComponent);
+      maxNumWide = Math.max(maxNumWide, maxNumWideCurrentComponent);
+      maxNumHigh = Math.max(maxNumHigh, maxNumHighCurrentComponent);
+      sizePerComponent[c] = {
+        resolutions: sizePerResolution,
+        minWidth: minWidthCurrentComponent,
+        minHeight: minHeightCurrentComponent,
+        maxNumWide: maxNumWideCurrentComponent,
+        maxNumHigh: maxNumHighCurrentComponent
+      };
+    }
+    return {
+      components: sizePerComponent,
+      minWidth: minWidth,
+      minHeight: minHeight,
+      maxNumWide: maxNumWide,
+      maxNumHigh: maxNumHigh
+    };
+  }
   function buildPackets(context) {
     var siz = context.SIZ;
     var tileIndex = context.currentTile.index;
     var tile = context.tiles[tileIndex];
     var componentsCount = siz.Csiz;
     // Creating resolutions and sub-bands for each component
     for (var c = 0; c < componentsCount; c++) {
       var component = tile.components[c];
@@ -35324,16 +35654,28 @@ var JpxImage = (function JpxImageClosure
       case 0:
         tile.packetsIterator =
           new LayerResolutionComponentPositionIterator(context);
         break;
       case 1:
         tile.packetsIterator =
           new ResolutionLayerComponentPositionIterator(context);
         break;
+      case 2:
+        tile.packetsIterator =
+          new ResolutionPositionComponentLayerIterator(context);
+        break;
+      case 3:
+        tile.packetsIterator =
+          new PositionComponentResolutionLayerIterator(context);
+        break;
+      case 4:
+        tile.packetsIterator =
+          new ComponentPositionResolutionLayerIterator(context);
+        break;
       default:
         throw new Error('JPX Error: Unsupported progression order ' +
                         progressionOrder);
     }
   }
   function parseTilePackets(context, data, offset, dataLength) {
     var position = 0;
     var buffer, bufferSize = 0, skipNextBit = false;
@@ -35351,16 +35693,31 @@ var JpxImage = (function JpxImageClosure
         }
         if (b === 0xFF) {
           skipNextBit = true;
         }
       }
       bufferSize -= count;
       return (buffer >>> bufferSize) & ((1 << count) - 1);
     }
+    function skipMarkerIfEqual(value) {
+      if (data[offset + position - 1] === 0xFF &&
+          data[offset + position] === value) {
+        skipBytes(1);
+        return true;
+      } else if (data[offset + position] === 0xFF &&
+                 data[offset + position + 1] === value) {
+        skipBytes(2);
+        return true;
+      }
+      return false;
+    }
+    function skipBytes(count) {
+      position += count;
+    }
     function alignToByte() {
       bufferSize = 0;
       if (skipNextBit) {
         position++;
         skipNextBit = false;
       }
     }
     function readCodingpasses() {
@@ -35378,23 +35735,29 @@ var JpxImage = (function JpxImageClosure
       if (value < 31) {
         return value + 6;
       }
       value = readBits(7);
       return value + 37;
     }
     var tileIndex = context.currentTile.index;
     var tile = context.tiles[tileIndex];
+    var sopMarkerUsed = context.COD.sopMarkerUsed;
+    var ephMarkerUsed = context.COD.ephMarkerUsed;
     var packetsIterator = tile.packetsIterator;
     while (position < dataLength) {
       alignToByte();
+      if (sopMarkerUsed && skipMarkerIfEqual(0x91)) {
+        // Skip also marker segment length and packet sequence ID
+        skipBytes(4);
+      }
+      var packet = packetsIterator.nextPacket();
       if (!readBits(1)) {
         continue;
       }
-      var packet = packetsIterator.nextPacket();
       var layerNumber = packet.layerNumber;
       var queue = [], codeblock;
       for (var i = 0, ii = packet.codeblocks.length; i < ii; i++) {
         codeblock = packet.codeblocks[i];
         var precinct = codeblock.precinct;
         var codeblockColumn = codeblock.cbx - precinct.cbxMin;
         var codeblockRow = codeblock.cby - precinct.cbyMin;
         var codeblockIncluded = false;
@@ -35463,16 +35826,19 @@ var JpxImage = (function JpxImageClosure
         var codedDataLength = readBits(bits);
         queue.push({
           codeblock: codeblock,
           codingpasses: codingpasses,
           dataLength: codedDataLength
         });
       }
       alignToByte();
+      if (ephMarkerUsed) {
+        skipMarkerIfEqual(0x92);
+      }
       while (queue.length > 0) {
         var packetItem = queue.shift();
         codeblock = packetItem.codeblock;
         if (codeblock['data'] === undefined) {
           codeblock.data = [];
         }
         codeblock.data.push({
           data: data,
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -98,21 +98,16 @@
   border: 0;
 }
 
 :fullscreen .pdfViewer .page {
   margin-bottom: 100%;
   border: 0;
 }
 
-.pdfViewer .page .annotationHighlight {
-  position: absolute;
-  border: 2px #FFFF99 solid;
-}
-
 .pdfViewer .page .annotText > img {
   position: absolute;
   cursor: pointer;
 }
 
 .pdfViewer .page .annotTextContentWrapper {
   position: absolute;
   width: 20em;
@@ -806,17 +801,16 @@ html[dir='rtl'] .dropdownToolbarButton {
 html[dir='ltr'] .dropdownToolbarButton {
   background-position: 95%;
 }
 html[dir='rtl'] .dropdownToolbarButton {
   background-position: 5%;
 }
 
 .dropdownToolbarButton > select {
-  -moz-appearance: none; /* in the future this might matter, see bugzilla bug #649849 */
   min-width: 140px;
   font-size: 12px;
   color: hsl(0,0%,95%);
   margin: 0;
   padding: 0;
   border: none;
   background: rgba(0,0,0,0); /* Opera does not support 'transparent' <select> background */
 }
@@ -1749,16 +1743,20 @@ html[dir='rtl'] #documentPropertiesOverl
     display: block;
   }
   #printContainer canvas {
     position: relative;
     top: 0;
     left: 0;
     display: block;
   }
+  #printContainer div {
+    page-break-after: always;
+    page-break-inside: avoid;
+  }
 }
 
 .visibleLargeView,
 .visibleMediumView,
 .visibleSmallView {
   display: none;
 }
 
new file mode 100644
--- /dev/null
+++ b/docshell/base/TimelineMarker.cpp
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 tw=80 et:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDocShell.h"
+#include "TimelineMarker.h"
+
+TimelineMarker::TimelineMarker(nsDocShell* aDocShell, const char* aName,
+                               TracingMetadata aMetaData)
+  : mName(aName)
+  , mMetaData(aMetaData)
+{
+  MOZ_COUNT_CTOR(TimelineMarker);
+  MOZ_ASSERT(aName);
+  aDocShell->Now(&mTime);
+  if (aMetaData == TRACING_INTERVAL_START) {
+    CaptureStack();
+  }
+}
+
+TimelineMarker::TimelineMarker(nsDocShell* aDocShell, const char* aName,
+                               TracingMetadata aMetaData,
+                               const nsAString& aCause)
+  : mName(aName)
+  , mMetaData(aMetaData)
+  , mCause(aCause)
+{
+  MOZ_COUNT_CTOR(TimelineMarker);
+  MOZ_ASSERT(aName);
+  aDocShell->Now(&mTime);
+  if (aMetaData == TRACING_INTERVAL_START) {
+    CaptureStack();
+  }
+}
+
+TimelineMarker::~TimelineMarker()
+{
+  MOZ_COUNT_DTOR(TimelineMarker);
+}
new file mode 100644
--- /dev/null
+++ b/docshell/base/TimelineMarker.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 tw=80 et:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TimelineMarker_h__
+#define TimelineMarker_h__
+
+#include "nsString.h"
+#include "GeckoProfiler.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "nsContentUtils.h"
+#include "jsapi.h"
+
+class nsDocShell;
+
+// Objects of this type can be added to the timeline.  The class can
+// also be subclassed to let a given marker creator provide custom
+// details.
+class TimelineMarker
+{
+public:
+  TimelineMarker(nsDocShell* aDocShell, const char* aName,
+                 TracingMetadata aMetaData);
+
+  TimelineMarker(nsDocShell* aDocShell, const char* aName,
+                 TracingMetadata aMetaData,
+                 const nsAString& aCause);
+
+  virtual ~TimelineMarker();
+
+  // Check whether two markers should be considered the same,
+  // for the purpose of pairing start and end markers.  Normally
+  // this definition suffices.
+  virtual bool Equals(const TimelineMarker* other)
+  {
+    return strcmp(mName, other->mName) == 0;
+  }
+
+  // Add details specific to this marker type to aMarker.  The
+  // standard elements have already been set.  This method is
+  // called on both the starting and ending markers of a pair.
+  // Ordinarily the ending marker doesn't need to do anything
+  // here.
+  virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker)
+  {
+  }
+
+  virtual void AddLayerRectangles(mozilla::dom::Sequence<mozilla::dom::ProfileTimelineLayerRect>&)
+  {
+    MOZ_ASSERT_UNREACHABLE("can only be called on layer markers");
+  }
+
+  const char* GetName() const
+  {
+    return mName;
+  }
+
+  TracingMetadata GetMetaData() const
+  {
+    return mMetaData;
+  }
+
+  DOMHighResTimeStamp GetTime() const
+  {
+    return mTime;
+  }
+
+  const nsString& GetCause() const
+  {
+    return mCause;
+  }
+
+  JSObject* GetStack()
+  {
+    if (mStackTrace) {
+      return mStackTrace->get();
+    }
+    return nullptr;
+  }
+
+protected:
+
+  void CaptureStack()
+  {
+    JSContext* ctx = nsContentUtils::GetCurrentJSContext();
+    if (ctx) {
+      JS::RootedObject stack(ctx);
+      if (JS::CaptureCurrentStack(ctx, &stack)) {
+        mStackTrace.emplace(ctx, stack.get());
+      } else {
+        JS_ClearPendingException(ctx);
+      }
+    }
+  }
+
+private:
+
+  const char* mName;
+  TracingMetadata mMetaData;
+  DOMHighResTimeStamp mTime;
+  nsString mCause;
+
+  // While normally it is not a good idea to make a persistent
+  // root, in this case changing nsDocShell to participate in
+  // cycle collection was deemed too invasive, the stack trace
+  // can't actually cause a cycle, and the markers are only held
+  // here temporarily to boot.
+  mozilla::Maybe<JS::PersistentRooted<JSObject*>> mStackTrace;
+};
+
+#endif /* TimelineMarker_h__ */
--- a/docshell/base/moz.build
+++ b/docshell/base/moz.build
@@ -57,16 +57,17 @@ UNIFIED_SOURCES += [
     'nsDocShellEditorData.cpp',
     'nsDocShellEnumerator.cpp',
     'nsDocShellLoadInfo.cpp',
     'nsDocShellTransferableHooks.cpp',
     'nsDownloadHistory.cpp',
     'nsDSURIContentListener.cpp',
     'nsWebNavigationInfo.cpp',
     'SerializedLoadContext.cpp',
+    'TimelineMarker.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 MSVC_ENABLE_PGO = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -27,16 +27,17 @@
 
 // Helper Classes
 #include "nsCOMPtr.h"
 #include "nsPoint.h" // mCurrent/mDefaultScrollbarPreferences
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
 #include "nsContentUtils.h"
+#include "TimelineMarker.h"
 
 // Threshold value in ms for META refresh based redirects
 #define REFRESH_REDIRECT_TIMER 15000
 
 // Interfaces Needed
 #include "nsIDocCharset.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIRefreshURI.h"
@@ -255,135 +256,16 @@ public:
 
     // Notify Scroll observers when an async panning/zooming transform
     // has started being applied
     void NotifyAsyncPanZoomStarted(const mozilla::CSSIntPoint aScrollPos);
     // Notify Scroll observers when an async panning/zooming transform
     // is no longer applied
     void NotifyAsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos);
 
-    // Objects of this type can be added to the timeline.  The class
-    // can also be subclassed to let a given marker creator provide
-    // custom details.
-    class TimelineMarker
-    {
-    public:
-        TimelineMarker(nsDocShell* aDocShell, const char* aName,
-                       TracingMetadata aMetaData)
-            : mName(aName)
-            , mMetaData(aMetaData)
-        {
-            MOZ_COUNT_CTOR(TimelineMarker);
-            MOZ_ASSERT(aName);
-            aDocShell->Now(&mTime);
-            if (aMetaData == TRACING_INTERVAL_START) {
-                CaptureStack();
-            }
-        }
-
-        TimelineMarker(nsDocShell* aDocShell, const char* aName,
-                       TracingMetadata aMetaData,
-                       const nsAString& aCause)
-            : mName(aName)
-            , mMetaData(aMetaData)
-            , mCause(aCause)
-        {
-            MOZ_COUNT_CTOR(TimelineMarker);
-            MOZ_ASSERT(aName);
-            aDocShell->Now(&mTime);
-            if (aMetaData == TRACING_INTERVAL_START) {
-                CaptureStack();
-            }
-        }
-
-        virtual ~TimelineMarker()
-        {
-            MOZ_COUNT_DTOR(TimelineMarker);
-        }
-
-        // Check whether two markers should be considered the same,
-        // for the purpose of pairing start and end markers.  Normally
-        // this definition suffices.
-        virtual bool Equals(const TimelineMarker* other)
-        {
-            return strcmp(mName, other->mName) == 0;
-        }
-
-        // Add details specific to this marker type to aMarker.  The
-        // standard elements have already been set.  This method is
-        // called on both the starting and ending markers of a pair.
-        // Ordinarily the ending marker doesn't need to do anything
-        // here.
-        virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker)
-        {
-        }
-
-        virtual void AddLayerRectangles(mozilla::dom::Sequence<mozilla::dom::ProfileTimelineLayerRect>&)
-        {
-            MOZ_ASSERT_UNREACHABLE("can only be called on layer markers");
-        }
-
-        const char* GetName() const
-        {
-            return mName;
-        }
-
-        TracingMetadata GetMetaData() const
-        {
-            return mMetaData;
-        }
-
-        DOMHighResTimeStamp GetTime() const
-        {
-            return mTime;
-        }
-
-        const nsString& GetCause() const
-        {
-            return mCause;
-        }
-
-        JSObject* GetStack()
-        {
-            if (mStackTrace) {
-                return mStackTrace->get();
-            }
-            return nullptr;
-        }
-
-    protected:
-
-        void CaptureStack()
-        {
-            JSContext* ctx = nsContentUtils::GetCurrentJSContext();
-            if (ctx) {
-                JS::RootedObject stack(ctx);
-                if (JS::CaptureCurrentStack(ctx, &stack)) {
-                    mStackTrace.emplace(ctx, stack.get());
-                } else {
-                    JS_ClearPendingException(ctx);
-                }
-            }
-        }
-
-    private:
-
-        const char* mName;
-        TracingMetadata mMetaData;
-        DOMHighResTimeStamp mTime;
-        nsString mCause;
-
-        // While normally it is not a good idea to make a persistent
-        // root, in this case changing nsDocShell to participate in
-        // cycle collection was deemed too invasive, the stack trace
-        // can't actually cause a cycle, and the markers are only held
-        // here temporarily to boot.
-        mozilla::Maybe<JS::PersistentRooted<JSObject*>> mStackTrace;
-    };
-
     // Add new profile timeline markers to this docShell. This will only add
     // markers if the docShell is currently recording profile timeline markers.
     // See nsIDocShell::recordProfileTimelineMarkers
     void AddProfileTimelineMarker(const char* aName,
                                   TracingMetadata aMetaData);
     void AddProfileTimelineMarker(mozilla::UniquePtr<TimelineMarker> &aMarker);
 
     // Global counter for how many docShells are currently recording profile
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -800,32 +800,32 @@ ReifyStack(nsIStackFrame* aStack, nsTArr
     NS_ENSURE_SUCCESS(rv, rv);
 
     stack.swap(caller);
   }
 
   return NS_OK;
 }
 
-class ConsoleTimelineMarker : public nsDocShell::TimelineMarker
+class ConsoleTimelineMarker : public TimelineMarker
 {
 public:
   ConsoleTimelineMarker(nsDocShell* aDocShell,
                         TracingMetadata aMetaData,
                         const nsAString& aCause)
-    : nsDocShell::TimelineMarker(aDocShell, "ConsoleTime", aMetaData, aCause)
+    : TimelineMarker(aDocShell, "ConsoleTime", aMetaData, aCause)
   {
     if (aMetaData == TRACING_INTERVAL_END) {
       CaptureStack();
     }
   }
 
-  virtual bool Equals(const nsDocShell::TimelineMarker* aOther)
+  virtual bool Equals(const TimelineMarker* aOther)
   {
-    if (!nsDocShell::TimelineMarker::Equals(aOther)) {
+    if (!TimelineMarker::Equals(aOther)) {
       return false;
     }
     // Console markers must have matching causes as well.
     return GetCause() == aOther->GetCause();
   }
 
   virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker)
   {
@@ -964,17 +964,17 @@ Console::Method(JSContext* aCx, MethodNa
       }
 
       if (isTimelineRecording && aData.Length() == 1) {
         JS::Rooted<JS::Value> value(aCx, aData[0]);
         JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
         if (jsString) {
           nsAutoJSString key;
           if (key.init(aCx, jsString)) {
-            mozilla::UniquePtr<nsDocShell::TimelineMarker> marker =
+            mozilla::UniquePtr<TimelineMarker> marker =
               MakeUnique<ConsoleTimelineMarker>(docShell,
                                                 aMethodName == MethodTime ? TRACING_INTERVAL_START : TRACING_INTERVAL_END,
                                                 key);
             docShell->AddProfileTimelineMarker(marker);
           }
         }
       }
 
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1019,22 +1019,22 @@ EventListenerManager::GetDocShellForTarg
 
   if (doc) {
     docShell = doc->GetDocShell();
   }
 
   return docShell;
 }
 
-class EventTimelineMarker : public nsDocShell::TimelineMarker
+class EventTimelineMarker : public TimelineMarker
 {
 public:
   EventTimelineMarker(nsDocShell* aDocShell, TracingMetadata aMetaData,
                       uint16_t aPhase, const nsAString& aCause)
-    : nsDocShell::TimelineMarker(aDocShell, "DOMEvent", aMetaData, aCause)
+    : TimelineMarker(aDocShell, "DOMEvent", aMetaData, aCause)
     , mPhase(aPhase)
   {
   }
 
   virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker)
   {
     if (GetMetaData() == TRACING_INTERVAL_START) {
       aMarker.mType.Construct(GetCause());
@@ -1109,17 +1109,17 @@ EventListenerManager::HandleEventInterna
               docShell->GetRecordProfileTimelineMarkers(&isTimelineRecording);
             }
             if (isTimelineRecording) {
               nsDocShell* ds = static_cast<nsDocShell*>(docShell.get());
               nsAutoString typeStr;
               (*aDOMEvent)->GetType(typeStr);
               uint16_t phase;
               (*aDOMEvent)->GetEventPhase(&phase);
-              mozilla::UniquePtr<nsDocShell::TimelineMarker> marker =
+              mozilla::UniquePtr<TimelineMarker> marker =
                 MakeUnique<EventTimelineMarker>(ds, TRACING_INTERVAL_START,
                                                 phase, typeStr);
               ds->AddProfileTimelineMarker(marker);
             }
           }
 
           if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent,
                                            aCurrentTarget))) {
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -4466,21 +4466,21 @@ static void DrawForcedBackgroundColor(Dr
 {
   if (NS_GET_A(aBackgroundColor) > 0) {
     nsIntRect r = aLayer->GetVisibleRegion().GetBounds();
     ColorPattern color(ToDeviceColor(aBackgroundColor));
     aDrawTarget.FillRect(Rect(r.x, r.y, r.width, r.height), color);
   }
 }
 
-class LayerTimelineMarker : public nsDocShell::TimelineMarker
+class LayerTimelineMarker : public TimelineMarker
 {
 public:
   LayerTimelineMarker(nsDocShell* aDocShell, const nsIntRegion& aRegion)
-    : nsDocShell::TimelineMarker(aDocShell, "Layer", TRACING_EVENT)
+    : TimelineMarker(aDocShell, "Layer", TRACING_EVENT)
     , mRegion(aRegion)
   {
   }
 
   ~LayerTimelineMarker()
   {
   }
 
@@ -4648,17 +4648,17 @@ FrameLayerBuilder::DrawPaintedLayer(Pain
     FlashPaint(aContext);
   }
 
   if (presContext && presContext->GetDocShell() && isActiveLayerManager) {
     nsDocShell* docShell = static_cast<nsDocShell*>(presContext->GetDocShell());
     bool isRecording;
     docShell->GetRecordProfileTimelineMarkers(&isRecording);
     if (isRecording) {
-      mozilla::UniquePtr<nsDocShell::TimelineMarker> marker =
+      mozilla::UniquePtr<TimelineMarker> marker =
         MakeUnique<LayerTimelineMarker>(docShell, aRegionToDraw);
       docShell->AddProfileTimelineMarker(marker);
     }
   }
 
   if (!aRegionToInvalidate.IsEmpty()) {
     aLayer->AddInvalidRect(aRegionToInvalidate.GetBounds());
   }
--- a/mobile/android/base/menu/GeckoMenuItem.java
+++ b/mobile/android/base/menu/GeckoMenuItem.java
@@ -439,17 +439,19 @@ public class GeckoMenuItem implements Me
     @Override
     public MenuItem setTitleCondensed(CharSequence title) {
         mTitleCondensed = title;
         return this;
     }
 
     @Override
     public MenuItem setVisible(boolean visible) {
-        if (mVisible != visible) {
+        // Action views are not normal menu items and visibility can get out
+        // of sync unless we dispatch whenever required.
+        if (isActionItem() || mVisible != visible) {
             mVisible = visible;
             if (mShouldDispatchChanges) {
                 mMenu.onItemChanged(this);
             } else {
                 mDidChange = true;
             }
         }
         return this;
--- a/mobile/android/base/resources/layout-v11/new_tablet_tabs_item_cell.xml
+++ b/mobile/android/base/resources/layout-v11/new_tablet_tabs_item_cell.xml
@@ -1,17 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
                                            xmlns:gecko="http://schemas.android.com/apk/res-auto"
                                            style="@style/TabsItem"
-                                           android:focusable="true"
                                            android:id="@+id/info"
                                            android:layout_width="wrap_content"
                                            android:layout_height="wrap_content"
                                            android:gravity="center"
                                            android:orientation="vertical">
 
     <LinearLayout android:layout_width="fill_parent"
                   android:layout_height="wrap_content"
@@ -32,17 +31,17 @@
                android:singleLine="true"
                android:duplicateParentState="true"
                gecko:fadeWidth="15dp"
                android:paddingRight="5dp"/>
 
 
         <!-- Use of baselineAlignBottom only supported from API 11+ - if this needs to work on lower API versions
              we'll need to override getBaseLine() and return image height, but we assume this won't happen -->
-        <ImageButton android:id="@+id/close"
+        <ImageView android:id="@+id/close"
                      style="@style/TabsItemClose"
                      android:layout_width="wrap_content"
                      android:layout_height="wrap_content"
                      android:scaleType="center"
                      android:baselineAlignBottom="true"
                      android:background="@android:color/transparent"
                      android:contentDescription="@string/close_tab"
                      android:src="@drawable/new_tablet_tab_item_close_button"
--- a/mobile/android/base/tabs/TabsGridLayout.java
+++ b/mobile/android/base/tabs/TabsGridLayout.java
@@ -22,16 +22,17 @@ import android.content.res.TypedArray;
 import android.graphics.PointF;
 import android.util.AttributeSet;
 import android.util.SparseArray;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.animation.DecelerateInterpolator;
+import android.widget.AdapterView;
 import android.widget.Button;
 import android.widget.GridView;
 import com.nineoldandroids.animation.Animator;
 import com.nineoldandroids.animation.AnimatorSet;
 import com.nineoldandroids.animation.ObjectAnimator;
 import com.nineoldandroids.animation.PropertyValuesHolder;
 import com.nineoldandroids.animation.ValueAnimator;
 
@@ -89,48 +90,48 @@ class TabsGridLayout extends GridView
 
         final Resources resources = getResources();
         mColumnWidth = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_column_width);
         setColumnWidth(mColumnWidth);
 
         final int padding = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_grid_padding);
         final int paddingTop = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_grid_padding_top);
         setPadding(padding, paddingTop, padding, padding);
+
+        setOnItemClickListener(new OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                TabsLayoutItemView tab = (TabsLayoutItemView) view;
+                Tabs.getInstance().selectTab(tab.getTabId());
+                autoHidePanel();
+            }
+        });
     }
 
     private class TabsGridLayoutAdapter extends TabsLayoutAdapter {
 
         final private Button.OnClickListener mCloseClickListener;
-        final private View.OnClickListener mSelectClickListener;
 
         public TabsGridLayoutAdapter (Context context) {
             super(context, R.layout.new_tablet_tabs_item_cell);
 
             mCloseClickListener = new Button.OnClickListener() {
                 @Override
                 public void onClick(View v) {
                     closeTab(v);
                 }
             };
-
-            mSelectClickListener = new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    TabsLayoutItemView tab = (TabsLayoutItemView) v;
-                    Tabs.getInstance().selectTab(tab.getTabId());
-                    autoHidePanel();
-                }
-            };
         }
 
         @Override
         TabsLayoutItemView newView(int position, ViewGroup parent) {
             final TabsLayoutItemView item = super.newView(position, parent);
-            item.setOnClickListener(mSelectClickListener);
+
             item.setCloseOnClickListener(mCloseClickListener);
+
             return item;
         }
 
         @Override
         public void bindView(TabsLayoutItemView view, Tab tab) {
             super.bindView(view, tab);
 
             // If we're recycling this view, there's a chance it was transformed during
--- a/mobile/android/base/tabs/TabsLayoutAdapter.java
+++ b/mobile/android/base/tabs/TabsLayoutAdapter.java
@@ -41,16 +41,17 @@ public class TabsLayoutAdapter extends B
         if (tabRemoved) {
             notifyDataSetChanged(); // Be sure to call this whenever mTabs changes.
         }
         return tabRemoved;
     }
 
     final void clear() {
         mTabs = null;
+
         notifyDataSetChanged(); // Be sure to call this whenever mTabs changes.
     }
 
     @Override
     public int getCount() {
         return (mTabs == null ? 0 : mTabs.size());
     }
 
@@ -67,16 +68,21 @@ public class TabsLayoutAdapter extends B
     final int getPositionForTab(Tab tab) {
         if (mTabs == null || tab == null)
             return -1;
 
         return mTabs.indexOf(tab);
     }
 
     @Override
+    public boolean isEnabled(int position) {
+        return true;
+    }
+
+    @Override
     final public TabsLayoutItemView getView(int position, View convertView, ViewGroup parent) {
         final TabsLayoutItemView view;
         if (convertView == null) {
             view = newView(position, parent);
         } else {
             view = (TabsLayoutItemView) convertView;
         }
         final Tab tab = mTabs.get(position);
--- a/mobile/android/base/tabs/TabsLayoutItemView.java
+++ b/mobile/android/base/tabs/TabsLayoutItemView.java
@@ -27,17 +27,17 @@ public class TabsLayoutItemView extends 
                                 implements Checkable {
     private static final String LOGTAG = "Gecko" + TabsLayoutItemView.class.getSimpleName();
     private static final int[] STATE_CHECKED = { android.R.attr.state_checked };
     private boolean mChecked;
 
     private int mTabId;
     private TextView mTitle;
     private ImageView mThumbnail;
-    private ImageButton mCloseButton;
+    private ImageView mCloseButton;
     private TabThumbnailWrapper mThumbnailWrapper;
 
     public TabsLayoutItemView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
     public int[] onCreateDrawableState(int extraSpace) {
@@ -46,16 +46,21 @@ public class TabsLayoutItemView extends 
         if (mChecked) {
             mergeDrawableStates(drawableState, STATE_CHECKED);
         }
 
         return drawableState;
     }
 
     @Override
+    public boolean isEnabled() {
+        return true;
+    }
+
+    @Override
     public boolean isChecked() {
         return mChecked;
     }
 
     @Override
     public void setChecked(boolean checked) {
         if (mChecked == checked) {
             return;
@@ -82,17 +87,17 @@ public class TabsLayoutItemView extends 
         mCloseButton.setOnClickListener(mOnClickListener);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mTitle = (TextView) findViewById(R.id.title);
         mThumbnail = (ImageView) findViewById(R.id.thumbnail);
-        mCloseButton = (ImageButton) findViewById(R.id.close);
+        mCloseButton = (ImageView) findViewById(R.id.close);
         mThumbnailWrapper = (TabThumbnailWrapper) findViewById(R.id.wrapper);
 
         if (NewTabletUI.isEnabled(getContext())) {
             growCloseButtonHitArea();
         }
     }
 
     private void growCloseButtonHitArea() {
--- a/toolkit/content/widgets/preferences.xml
+++ b/toolkit/content/widgets/preferences.xml
@@ -127,18 +127,22 @@
         if (!this.name)
           return;
 
         this.preferences.rootBranchInternal
             .addObserver(this.name, this.preferences, false);
         // In non-instant apply mode, we must try and use the last saved state
         // from any previous opens of a child dialog instead of the value from
         // preferences, to pick up any edits a user may have made. 
+
+        var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+                    .getService(Components.interfaces.nsIScriptSecurityManager);
         if (this.preferences.type == "child" && 
-            !this.instantApply && window.opener) {
+            !this.instantApply && window.opener &&
+            secMan.isSystemPrincipal(window.opener.document.nodePrincipal)) {
           var pdoc = window.opener.document;
 
           // Try to find a preference element for the same preference.
           var preference = null;
           var parentPreferences = pdoc.getElementsByTagName("preferences");
           for (var k = 0; (k < parentPreferences.length && !preference); ++k) {
             var parentPrefs = parentPreferences[k]
                                     .getElementsByAttribute("name", this.name);
@@ -1048,17 +1052,20 @@
     </implementation>
     <handlers>
       <handler event="dialogaccept">
       <![CDATA[
         if (!this._fireEvent("beforeaccept", this)){
           return false;
         }
 
-        if (this.type == "child" && window.opener) {
+        var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+                    .getService(Components.interfaces.nsIScriptSecurityManager);
+        if (this.type == "child" && window.opener &&
+            secMan.isSystemPrincipal(window.opener.document.nodePrincipal)) {
           var psvc = Components.classes["@mozilla.org/preferences-service;1"]
                                .getService(Components.interfaces.nsIPrefBranch);
           var instantApply = psvc.getBoolPref("browser.preferences.instantApply");
           if (instantApply) {
             var panes = this.preferencePanes;
             for (var i = 0; i < panes.length; ++i)
               panes[i].writePreferences(true);
           }
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/director-manager.js
@@ -0,0 +1,780 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const events = require("sdk/event/core");
+const protocol = require("devtools/server/protocol");
+
+const { Cu, Ci } = require("chrome");
+
+const { on, once, off, emit } = events;
+const { method, Arg, Option, RetVal, types } = protocol;
+
+const { sandbox, evaluate } = require('sdk/loader/sandbox');
+const { Class } = require("sdk/core/heritage");
+
+const { PlainTextConsole } = require('sdk/console/plain-text');
+
+const { DirectorRegistry } = require("./director-registry");
+
+/**
+ * E10S child setup helper
+ */
+
+const {DebuggerServer} = require("devtools/server/main");
+
+/**
+ * Error Messages
+ */
+
+const ERR_MESSAGEPORT_FINALIZED = "message port finalized";
+
+const ERR_DIRECTOR_UNKNOWN_SCRIPTID = "unkown director-script id";
+const ERR_DIRECTOR_UNINSTALLED_SCRIPTID = "uninstalled director-script id";
+
+/**
+ * Type describing a messageport event
+ */
+types.addDictType("messageportevent", {
+  isTrusted: "boolean",
+  data: "nullable:primitive",
+  origin: "nullable:string",
+  lastEventId: "nullable:string",
+  source: "messageport",
+  ports: "nullable:array:messageport"
+});
+
+/**
+ * A MessagePort Actor allowing communication through messageport events
+ * over the remote debugging protocol.
+ */
+let MessagePortActor = exports.MessagePortActor = protocol.ActorClass({
+  typeName: "messageport",
+
+  /**
+   * Create a MessagePort actor.
+   *
+   * @param DebuggerServerConnection conn
+   *        The server connection.
+   * @param MessagePort port
+   *        The wrapped MessagePort.
+   */
+  initialize: function(conn, port) {
+    protocol.Actor.prototype.initialize.call(this, conn);
+
+    // NOTE: can't get a weak reference because we need to subscribe events
+    // using port.onmessage or addEventListener
+    this.port = port;
+  },
+
+  destroy: function(conn) {
+    protocol.Actor.prototype.destroy.call(this, conn);
+    this.finalize();
+  },
+
+  /**
+   * Sends a message on the wrapped message port.
+   *
+   * @param Object msg
+   *        The JSON serializable message event payload
+   */
+  postMessage: method(function (msg) {
+    if (!this.port) {
+      console.error(ERR_MESSAGEPORT_FINALIZED);
+      return;
+    }
+
+    this.port.postMessage(msg);
+  }, {
+    oneway: true,
+    request: {
+      msg: Arg(0, "nullable:json")
+    }
+  }),
+
+  /**
+   * Starts to receive and send queued messages on this message port.
+   */
+  start: method(function () {
+    if (!this.port) {
+      console.error(ERR_MESSAGEPORT_FINALIZED);
+      return;
+    }
+
+    // NOTE: set port.onmessage to a function is an implicit start
+    // and starts to send queued messages.
+    // On the client side we should set MessagePortClient.onmessage
+    // to a setter which register an handler to the message event
+    // and call the actor start method to start receiving messages
+    // from the MessagePort's queue.
+    this.port.onmessage = (evt) => {
+      var ports;
+
+      // TODO: test these wrapped ports
+      if (Array.isArray(evt.ports)) {
+        ports = evt.ports.map((port) => {
+          let actor = new MessagePortActor(this.conn, port);
+          this.manage(actor);
+          return actor;
+        });
+      }
+
+      emit(this, "message", {
+        isTrusted: evt.isTrusted,
+        data: evt.data,
+        origin: evt.origin,
+        lastEventId: evt.lastEventId,
+        source: this,
+        ports: ports
+      });
+    };
+  }, {
+    oneway: true,
+    request: {}
+  }),
+
+  /**
+   * Starts to receive and send queued messages on this message port, or
+   * raise an exception if the port is null
+   */
+  close: method(function () {
+    if (!this.port) {
+      console.error(ERR_MESSAGEPORT_FINALIZED);
+      return;
+    }
+
+    this.port.onmessage = null;
+    this.port.close();
+  }, {
+    oneway: true,
+    request: {}
+  }),
+
+  finalize: method(function () {
+    this.close();
+    this.port = null;
+  }, {
+    oneway: true
+  }),
+
+  /**
+   * Events emitted by this actor.
+   */
+  events: {
+    "message": {
+      type: "message",
+      msg: Arg(0, "nullable:messageportevent")
+    }
+  }
+});
+
+/**
+ * The corresponding Front object for the MessagePortActor.
+ */
+let MessagePortFront = exports.MessagePortFront = protocol.FrontClass(MessagePortActor, {
+  initialize: function (client, form) {
+    protocol.Front.prototype.initialize.call(this, client, form);
+  }
+});
+
+
+/**
+ * Type describing a director-script error
+ */
+types.addDictType("director-script-error", {
+  directorScriptId: "string",
+  message: "string",
+  stack: "string",
+  fileName: "string",
+  lineNumber: "number",
+  columnNumber: "number"
+});
+
+/**
+ * Type describing a director-script attach event
+ */
+types.addDictType("director-script-attach", {
+  directorScriptId: "string",
+  url: "string",
+  innerId: "number",
+  port: "nullable:messageport"
+});
+
+/**
+ * Type describing a director-script detach event
+ */
+types.addDictType("director-script-detach", {
+  directorScriptId: "string",
+  innerId: "number"
+});
+
+/**
+ * The Director Script Actor manage javascript code running in a non-privileged sandbox with the same
+ * privileges of the target global (browser tab or a firefox os app).
+ *
+ * After retrieving an instance of this actor (from the tab director actor), you'll need to set it up
+ * by calling setup().
+ *
+ * After the setup, this actor will automatically attach/detach the content script (and optionally a
+ * directly connect the debugger client and the content script using a MessageChannel) on tab
+ * navigation.
+ */
+let DirectorScriptActor = exports.DirectorScriptActor = protocol.ActorClass({
+  typeName: "director-script",
+
+  /**
+   * Events emitted by this actor.
+   */
+  events: {
+    "error": {
+      type: "error",
+      data: Arg(0, "director-script-error")
+    },
+    "attach": {
+      type: "attach",
+      data: Arg(0, "director-script-attach")
+    },
+    "detach": {
+      type: "detach",
+      data: Arg(0, "director-script-detach")
+    }
+  },
+
+  /**
+   * Creates the director script actor
+   *
+   * @param DebuggerServerConnection conn
+   *        The server connection.
+   * @param Actor tabActor
+   *        The tab (or root) actor.
+   * @param String scriptId
+   *        The director-script id.
+   * @param String scriptCode
+   *        The director-script javascript source.
+   * @param Object scriptOptions
+   *        The director-script options object.
+   */
+  initialize: function(conn, tabActor, { scriptId, scriptCode, scriptOptions }) {
+    protocol.Actor.prototype.initialize.call(this, conn, tabActor);
+
+    this.tabActor = tabActor;
+
+    this._scriptId = scriptId;
+    this._scriptCode = scriptCode;
+    this._scriptOptions = scriptOptions;
+    this._setupCalled = false;
+
+    this._onGlobalCreated   = this._onGlobalCreated.bind(this);
+    this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this);
+  },
+  destroy: function(conn) {
+    protocol.Actor.prototype.destroy.call(this, conn);
+
+    this.finalize();
+  },
+
+  /**
+   * Starts listening to the tab global created, in order to create the director-script sandbox
+   * using the configured scriptCode, attached/detached automatically to the tab
+   * window on tab navigation.
+   *
+   * @param Boolean reload
+   *        attach the page immediately or reload it first.
+   * @param Boolean skipAttach
+   *        skip the attach
+   */
+  setup: method(function ({ reload, skipAttach }) {
+    if (this._setupCalled) {
+      // do nothing
+      return;
+    }
+
+    this._setupCalled = true;
+
+    on(this.tabActor, "window-ready", this._onGlobalCreated);
+    on(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
+
+    // optional skip attach (needed by director-manager for director scripts bulk activation)
+    if (skipAttach) {
+      return;
+    }
+
+    if (reload) {
+      this.window.location.reload();
+    } else {
+      // fake a global created event to attach without reload
+      this._onGlobalCreated({ id: getWindowID(this.window), window: this.window, isTopLevel: true });
+    }
+  }, {
+    request: {
+      reload: Option(0, "boolean"),
+      skipAttach: Option(0, "boolean")
+    },
+    oneway: true
+  }),
+
+  /**
+   * Get the attached MessagePort actor if any
+   */
+  getMessagePort: method(function () {
+    return this._messagePortActor;
+  }, {
+    request: { },
+    response: {
+      port: RetVal("nullable:messageport")
+    }
+  }),
+
+  /**
+   * Stop listening for document global changes, destroy the content worker and puts
+   * this actor to hibernation.
+   */
+  finalize: method(function () {
+    if (!this._setupCalled) {
+      return;
+    }
+
+    off(this.tabActor, "window-ready", this._onGlobalCreated);
+    off(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
+
+    this._onGlobalDestroyed({ id: this._lastAttachedWinId });
+
+    this._setupCalled = false;
+  }, {
+    oneway: true
+  }),
+
+  // local helpers
+  get window() {
+    return this.tabActor.window;
+  },
+
+  /* event handlers */
+  _onGlobalCreated: function({ id, window, isTopLevel }) {
+     if (!isTopLevel) {
+       // filter iframes
+       return;
+     }
+
+     if (this._lastAttachedWinId) {
+       // if we have received a global created without a previous global destroyed,
+       // it's time to cleanup the previous state
+       this._onGlobalDestroyed(this._lastAttachedWinId);
+     }
+
+     // TODO: check if we want to share a single sandbox per global
+     //       for multiple debugger clients
+
+     // create & attach the new sandbox
+     this._scriptSandbox = new DirectorScriptSandbox({
+       scriptId: this._scriptId,
+       scriptCode: this._scriptCode,
+       scriptOptions: this._scriptOptions
+     });
+
+     try {
+       // attach the global window
+       this._lastAttachedWinId = id;
+       var port = this._scriptSandbox.attach(window, id);
+       this._onDirectorScriptAttach(window, port);
+     } catch(e) {
+       this._onDirectorScriptError(e);
+     }
+  },
+  _onGlobalDestroyed: function({ id }) {
+     if (id !== this._lastAttachedWinId) {
+       // filter destroyed globals
+       return;
+     }
+
+     // unmanage and cleanup the messageport actor
+     if (this._messagePortActor) {
+       this.unmanage(this._messagePortActor);
+       this._messagePortActor = null;
+     }
+
+     // NOTE: destroy here the old worker
+     if (this._scriptSandbox) {
+       this._scriptSandbox.destroy(this._onDirectorScriptError.bind(this));
+
+       // send a detach event to the debugger client
+       emit(this, "detach", {
+         directorScriptId: this._scriptId,
+         innerId: this._lastAttachedWinId
+       });
+
+       this._lastAttachedWinId = null;
+       this._scriptSandbox = null;
+     }
+  },
+  _onDirectorScriptError: function(error) {
+    // route the content script error to the debugger client
+    emit(this, "error", {
+      directorScriptId: this._scriptId,
+      message: error.toString(),
+      stack: error.stack,
+      fileName: error.fileName,
+      lineNumber: error.lineNumber,
+      columnNumber: error.columnNumber
+    });
+  },
+  _onDirectorScriptAttach: function(window, port) {
+    let portActor = new MessagePortActor(this.conn, port);
+    this.manage(portActor);
+    this._messagePortActor = portActor;
+
+    emit(this, "attach", {
+      directorScriptId: this._scriptId,
+      url: (window && window.location) ? window.location.toString() : "",
+      innerId: this._lastAttachedWinId,
+      port: this._messagePortActor
+    });
+  }
+});
+
+/**
+ * The corresponding Front object for the DirectorScriptActor.
+ */
+let DirectorScriptFront = exports.DirectorScriptFront = protocol.FrontClass(DirectorScriptActor, {
+  initialize: function (client, form) {
+    protocol.Front.prototype.initialize.call(this, client, form);
+  }
+});
+
+/**
+ * The DirectorManager Actor is a tab actor which manages enabling/disabling director scripts.
+ */
+const DirectorManagerActor = exports.DirectorManagerActor = protocol.ActorClass({
+  typeName: "director-manager",
+
+  /**
+   * Events emitted by this actor.
+   */
+  events: {
+    "director-script-error": {
+      type: "error",
+      data: Arg(0, "director-script-error")
+    },
+    "director-script-attach": {
+      type: "attach",
+      data: Arg(0, "director-script-attach")
+    },
+    "director-script-detach": {
+      type: "detach",
+      data: Arg(0, "director-script-detach")
+    }
+  },
+
+  /* init & destroy methods */
+  initialize: function(conn, tabActor) {
+    protocol.Actor.prototype.initialize.call(this, conn);
+    this.tabActor = tabActor;
+    this._directorScriptActorsMap = new Map();
+  },
+  destroy: function(conn) {
+    protocol.Actor.prototype.destroy.call(this, conn);
+    this.finalize();
+  },
+
+  /**
+   * Retrieves the list of installed director-scripts.
+   */
+  list: method(function () {
+    var enabled_script_ids = [for (id of this._directorScriptActorsMap.keys()) id];
+
+    return {
+      installed: DirectorRegistry.list(),
+      enabled: enabled_script_ids
+    };
+  }, {
+    response: {
+      directorScripts: RetVal("json")
+    }
+  }),
+
+  /**
+   * Bulk enabling director-scripts.
+   *
+   * @param Array[String] selectedIds
+   *        The list of director-script ids to be enabled,
+   *        ["*"] will activate all the installed director-scripts
+   * @param Boolean reload
+   *        optionally reload the target window
+   */
+  enableByScriptIds: method(function(selectedIds, { reload }) {
+    if (selectedIds && selectedIds.length === 0) {
+      // filtered all director scripts ids
+      return;
+    }
+
+    for (let scriptId of DirectorRegistry.list()) {
+      // filter director script ids
+      if (selectedIds.indexOf("*") < 0 &&
+          selectedIds.indexOf(scriptId) < 0) {
+        continue;
+      }
+
+      let actor = this.getByScriptId(scriptId);
+
+      // skip attach if reload is true (activated director scripts
+      // will be automatically attached on the final reload)
+      actor.setup({ reload: false, skipAttach: reload });
+    }
+
+    if (reload) {
+      this.tabActor.window.location.reload();
+    }
+  }, {
+    oneway: true,
+    request: {
+      selectedIds: Arg(0, "array:string"),
+      reload: Option(1, "boolean")
+    }
+  }),
+
+  /**
+   * Bulk disabling director-scripts.
+   *
+   * @param Array[String] selectedIds
+   *        The list of director-script ids to be disable,
+   *        ["*"] will de-activate all the enable director-scripts
+   * @param Boolean reload
+   *        optionally reload the target window
+   */
+  disableByScriptIds: method(function(selectedIds, { reload }) {
+    if (selectedIds && selectedIds.length === 0) {
+      // filtered all director scripts ids
+      return;
+    }
+
+    for (let scriptId of this._directorScriptActorsMap.keys()) {
+      // filter director script ids
+      if (selectedIds.indexOf("*") < 0 &&
+          selectedIds.indexOf(scriptId) < 0) {
+        continue;
+      }
+
+      let actor = this._directorScriptActorsMap.get(scriptId);
+      this._directorScriptActorsMap.delete(scriptId);
+
+      // finalize the actor (which will produce director-script-detach event)
+      actor.finalize();
+      // unsubscribe event handlers on the disabled actor
+      off(actor);
+
+      this.unmanage(actor);
+    }
+
+    if (reload) {
+      this.tabActor.window.location.reload();
+    }
+  }, {
+    oneway: true,
+    request: {
+      selectedIds: Arg(0, "array:string"),
+      reload: Option(1, "boolean")
+    }
+  }),
+
+  /**
+   * Retrieves the actor instance of an installed director-script
+   * (and create the actor instance if it doesn't exists yet).
+   */
+  getByScriptId: method(function(scriptId) {
+    var id = scriptId;
+    // raise an unknown director-script id exception
+    if (!DirectorRegistry.checkInstalled(id)) {
+      console.error(ERR_DIRECTOR_UNKNOWN_SCRIPTID, id);
+      throw Error(ERR_DIRECTOR_UNKNOWN_SCRIPTID);
+    }
+
+    // get a previous created actor instance
+    let actor = this._directorScriptActorsMap.get(id);
+
+    // create a new actor instance
+    if (!actor) {
+      let directorScriptDefinition = DirectorRegistry.get(id);
+
+      // test lazy director-script (e.g. uninstalled in the parent process)
+      if (!directorScriptDefinition) {
+
+        console.error(ERR_DIRECTOR_UNINSTALLED_SCRIPTID, id);
+        throw Error(ERR_DIRECTOR_UNINSTALLED_SCRIPTID);
+      }
+
+      actor = new DirectorScriptActor(this.conn, this.tabActor, directorScriptDefinition);
+      this._directorScriptActorsMap.set(id, actor);
+
+      on(actor, "error", emit.bind(null, this, "director-script-error"));
+      on(actor, "attach", emit.bind(null, this, "director-script-attach"));
+      on(actor, "detach", emit.bind(null, this, "director-script-detach"));
+
+      this.manage(actor);
+    }
+
+    return actor;
+  }, {
+    request: {
+      scriptId: Arg(0, "string")
+    },
+    response: {
+      directorScript: RetVal("director-script")
+    }
+  }),
+
+  finalize: method(function() {
+    this.disableByScriptIds(["*"], false);
+  }, {
+    oneway: true
+  })
+});
+
+/**
+ * The corresponding Front object for the DirectorManagerActor.
+ */
+exports.DirectorManagerFront = protocol.FrontClass(DirectorManagerActor, {
+  initialize: function(client, { directorManagerActor }) {
+    protocol.Front.prototype.initialize.call(this, client, {
+      actor: directorManagerActor
+    });
+    this.manage(this);
+  }
+});
+
+/* private helpers */
+
+/**
+ * DirectorScriptSandbox is a private utility class, which attach a non-priviliged sandbox
+ * to a target window.
+ */
+const DirectorScriptSandbox = Class({
+  initialize: function({scriptId, scriptCode, scriptOptions}) {
+    this._scriptId = scriptId;
+    this._scriptCode = scriptCode;
+    this._scriptOptions = scriptOptions;
+  },
+
+  attach: function(window, innerId) {
+    this._innerId = innerId,
+    this._window = window;
+    this._proto = Cu.createObjectIn(this._window);
+
+    var id = this._scriptId;
+    var uri = this._scriptCode;
+
+    this._sandbox = sandbox(window, {
+      sandboxName: uri,
+      sandboxPrototype: this._proto,
+      sameZoneAs: window,
+      wantXrays: true,
+      wantComponents: false,
+      wantExportHelpers: false,
+      metadata: {
+        URI: uri,
+        addonID: id,
+        SDKDirectorScript: true,
+        "inner-window-id": innerId
+      }
+    });
+
+    // create a CommonJS module object which match the interface from addon-sdk
+    // (addon-sdk/sources/lib/toolkit/loader.js#L678-L686)
+    var module = Cu.cloneInto(Object.create(null, {
+      id: { enumerable: true, value: id },
+      uri: { enumerable: true, value: uri },
+      exports: { enumerable: true, value: Cu.createObjectIn(this._sandbox) }
+    }), this._sandbox);
+
+    // create a console API object
+    let directorScriptConsole = new PlainTextConsole(null, this._innerId);
+
+    // inject CommonJS module globals into the sandbox prototype
+    Object.defineProperties(this._proto, {
+      module: { enumerable: true, value: module },
+      exports: { enumerable: true, value: module.exports },
+      console: {
+        enumerable: true,
+        value: Cu.cloneInto(directorScriptConsole, this._sandbox, { cloneFunctions: true })
+      }
+    });
+
+    Object.defineProperties(this._sandbox, {
+      require: {
+        enumerable: true,
+        value: Cu.cloneInto(function() {
+          throw Error("NOT IMPLEMENTED");
+        }, this._sandbox, { cloneFunctions: true })
+      }
+    });
+
+    // evaluate the director script source in the sandbox
+    evaluate(this._sandbox, this._scriptCode, this._scriptId);
+
+    // prepare the messageport connected to the debugger client
+    let { port1, port2 } = new this._window.MessageChannel();
+
+    // prepare the unload callbacks queue
+    var sandboxOnUnloadQueue = this._sandboxOnUnloadQueue = [];
+
+    // create the attach options
+    var attachOptions = this._attachOptions = Cu.createObjectIn(this._sandbox);
+    Object.defineProperties(attachOptions, {
+      port: { enumerable: true, value: port1 },
+      window: { enumerable: true, value: window },
+      scriptOptions: { enumerable: true, value: Cu.cloneInto(this._scriptOptions, this._sandbox) },
+      onUnload: {
+        enumerable: true,
+        value: Cu.cloneInto(function (cb) {
+          // collect unload callbacks
+          if (typeof cb == "function") {
+            sandboxOnUnloadQueue.push(cb);
+          }
+        }, this._sandbox, { cloneFunctions: true })
+      }
+    });
+
+    // select the attach method
+    var exports = this._proto.module.exports;
+    if ("attachMethod" in this._scriptOptions) {
+      this._sandboxOnAttach = exports[this._scriptOptions.attachMethod];
+    } else {
+      this._sandboxOnAttach = exports;
+    }
+
+    if (typeof this._sandboxOnAttach !== "function") {
+      throw Error("the configured attachMethod '" +
+                  (this._scriptOptions.attachMethod || "module.exports") +
+                  "' is not exported by the directorScript");
+    }
+
+    // call the attach method
+    this._sandboxOnAttach.call(this._sandbox, attachOptions);
+
+    return port2;
+  },
+  destroy:  function(onError) {
+    // evaluate queue unload methods if any
+    while(this._sandboxOnUnloadQueue.length > 0) {
+      let cb = this._sandboxOnUnloadQueue.pop();
+
+      try {
+        cb();
+      } catch(e) {
+        console.error("Exception on DirectorScript Sandbox destroy", e);
+        onError(e);
+      }
+    }
+
+    Cu.nukeSandbox(this._sandbox);
+  }
+});
+
+function getWindowID(window) {
+  return window.QueryInterface(Ci.nsIInterfaceRequestor)
+               .getInterface(Ci.nsIDOMWindowUtils)
+               .currentInnerWindowID;
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/director-registry.js
@@ -0,0 +1,295 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const protocol = require("devtools/server/protocol");
+const { method, Arg, Option, RetVal } = protocol;
+
+const {DebuggerServer} = require("devtools/server/main");
+
+/**
+ * Error Messages
+ */
+
+const ERR_DIRECTOR_INSTALL_TWICE = "Trying to install a director-script twice";
+const ERR_DIRECTOR_INSTALL_EMPTY = "Trying to install an empty director-script";
+const ERR_DIRECTOR_UNINSTALL_UNKNOWN = "Trying to uninstall an unkown director-script";
+
+const ERR_DIRECTOR_PARENT_UNKNOWN_METHOD = "Unknown parent process method";
+const ERR_DIRECTOR_CHILD_NOTIMPLEMENTED_METHOD = "Unexpected call to notImplemented method";
+const ERR_DIRECTOR_CHILD_MULTIPLE_REPLIES = "Unexpected multiple replies to called parent method";
+const ERR_DIRECTOR_CHILD_NO_REPLY = "Unexpected no reply to called parent method";
+
+/**
+ * Director Registry
+ */
+
+// Map of director scripts ids to director script definitions
+var gDirectorScripts = Object.create(null);
+
+const DirectorRegistry = exports.DirectorRegistry = {
+  /**
+   * Register a Director Script with the debugger server.
+   * @param id string
+   *    The ID of a director script.
+   * @param directorScriptDef object
+   *    The definition of a director script.
+   */
+  install: function (id, scriptDef) {
+    if (id in gDirectorScripts) {
+      console.error(ERR_DIRECTOR_INSTALL_TWICE,id);
+      return false;
+    }
+
+    if (!scriptDef) {
+      console.error(ERR_DIRECTOR_INSTALL_EMPTY, id);
+      return false;
+    }
+
+    gDirectorScripts[id] = scriptDef;
+
+    return true;
+  },
+
+  /**
+   * Unregister a Director Script with the debugger server.
+   * @param id string
+   *    The ID of a director script.
+   */
+  uninstall: function(id) {
+    if (id in gDirectorScripts) {
+      delete gDirectorScripts[id];
+
+      return true;
+    }
+
+    console.error(ERR_DIRECTOR_UNINSTALL_UNKNOWN, id);
+
+    return false;
+  },
+
+  /**
+   * Returns true if a director script id has been registered.
+   * @param id string
+   *    The ID of a director script.
+   */
+  checkInstalled: function (id) {
+    return (this.list().indexOf(id) >= 0);
+  },
+
+  /**
+   * Returns a registered director script definition by id.
+   * @param id string
+   *    The ID of a director script.
+   */
+  get: function(id) {
+    return gDirectorScripts[id];
+  },
+
+  /**
+   * Returns an array of registered director script ids.
+   */
+  list: function() {
+    return Object.keys(gDirectorScripts);
+  },
+
+  /**
+   * Removes all the registered director scripts.
+   */
+  clear: function() {
+   gDirectorScripts = Object.create(null);
+  }
+};
+
+/**
+ * E10S parent/child setup helpers
+ */
+
+let gTrackedMessageManager = new Set();
+
+exports.setupParentProcess = function setupParentProcess({mm, childID}) {
+  // prevents multiple subscriptions on the same messagemanager
+  if (gTrackedMessageManager.has(mm)) {
+    return;
+  }
+  gTrackedMessageManager.add(mm);
+
+  // listen for director-script requests from the child process
+  mm.addMessageListener("debug:director-registry-request", handleChildRequest);
+
+  DebuggerServer.once("disconnected-from-child:" + childID, handleMessageManagerDisconnected);
+
+  /* parent process helpers */
+
+  function handleMessageManagerDisconnected(evt, { mm: disconnected_mm }) {
+    // filter out not subscribed message managers
+    if (disconnected_mm !== mm || !gTrackedMessageManager.has(mm)) {
+      return;
+    }
+
+    gTrackedMessageManager.delete(mm);
+
+    // unregister for director-script requests handlers from the parent process (if any)
+    mm.removeMessageListener("debug:director-registry-request", handleChildRequest);
+  }
+
+  function handleChildRequest(msg) {
+    switch (msg.json.method) {
+    case "get":
+      return DirectorRegistry.get(msg.json.args[0]);
+    case "list":
+      return DirectorRegistry.list();
+    default:
+      console.error(ERR_DIRECTOR_PARENT_UNKNOWN_METHOD, msg.json.method);
+      throw new Error(ERR_DIRECTOR_PARENT_UNKNOWN_METHOD);
+    }
+  }
+};
+
+// skip child setup if this actor module is not running in a child process
+if (DebuggerServer.isInChildProcess) {
+  setupChildProcess();
+}
+
+function setupChildProcess() {
+  const { sendSyncMessage } = DebuggerServer.parentMessageManager;
+
+  DebuggerServer.setupInParent({
+    module: "devtools/server/actors/director-registry",
+    setupParent: "setupParentProcess"
+  });
+
+  DirectorRegistry.install = notImplemented.bind(null, "install");
+  DirectorRegistry.uninstall = notImplemented.bind(null, "uninstall");
+  DirectorRegistry.clear = notImplemented.bind(null, "clear");
+
+  DirectorRegistry.get = callParentProcess.bind(null, "get");
+  DirectorRegistry.list = callParentProcess.bind(null, "list");
+
+  /* child process helpers */
+
+  function notImplemented(method) {
+    console.error(ERR_DIRECTOR_CHILD_NOTIMPLEMENTED_METHOD, method);
+    throw Error(ERR_DIRECTOR_CHILD_NOTIMPLEMENTED_METHOD);
+  }
+
+  function callParentProcess(method, ...args) {
+    var reply = sendSyncMessage("debug:director-registry-request", {
+      method: method,
+      args: args
+    });
+
+    if (reply.length === 0) {
+      console.error(ERR_DIRECTOR_CHILD_NO_REPLY);
+      throw Error(ERR_DIRECTOR_CHILD_NO_REPLY);
+    } else if (reply.length > 1) {
+      console.error(ERR_DIRECTOR_CHILD_MULTIPLE_REPLIES);
+      throw Error(ERR_DIRECTOR_CHILD_MULTIPLE_REPLIES);
+    }
+
+    return reply[0];
+  };
+};
+
+/**
+ * The DirectorRegistry Actor is a global actor which manages install/uninstall of
+ * director scripts definitions.
+ */
+const DirectorRegistryActor = exports.DirectorRegistryActor = protocol.ActorClass({
+  typeName: "director-registry",
+
+  /* init & destroy methods */
+  initialize: function(conn, parentActor) {
+    protocol.Actor.prototype.initialize.call(this, conn);
+  },
+  destroy: function(conn) {
+    protocol.Actor.prototype.destroy.call(this, conn);
+    this.finalize();
+  },
+
+  finalize: method(function() {
+    // nothing to cleanup
+  }, {
+    oneway: true
+  }),
+
+  /**
+   * Install a new director-script definition.
+   *
+   * @param String id
+   *        The director-script definition identifier.
+   * @param String scriptCode
+   *        The director-script javascript source.
+   * @param Object scriptOptions
+   *        The director-script option object.
+   */
+  install: method(function(id, { scriptCode, scriptOptions }) {
+    // TODO: add more checks on id format?
+    if (!id || id.length === 0) {
+      throw Error("director-script id is mandatory");
+    }
+
+    if (!scriptCode) {
+      throw Error("director-script scriptCode is mandatory");
+    }
+
+    return DirectorRegistry.install(id, {
+      scriptId: id,
+      scriptCode: scriptCode,
+      scriptOptions: scriptOptions
+    });
+  }, {
+    request: {
+      scriptId: Arg(0, "string"),
+      scriptCode: Option(1, "string"),
+      scriptOptions: Option(1, "nullable:json")
+    },
+    response: {
+      success: RetVal("boolean")
+    }
+  }),
+
+  /**
+   * Uninstall a director-script definition.
+   *
+   * @param String id
+   *        The identifier of the director-script definition to be removed
+   */
+  uninstall: method(function (id) {
+    return DirectorRegistry.uninstall(id);
+  }, {
+    request: {
+      scritpId: Arg(0, "string")
+    },
+    response: {
+      success: RetVal("boolean")
+    }
+  }),
+
+  /**
+   * Retrieves the list of installed director-scripts.
+   */
+  list: method(function () {
+    return DirectorRegistry.list();
+  }, {
+    response: {
+      directorScripts: RetVal("array:string")
+    }
+  })
+});
+
+/**
+ * The corresponding Front object for the DirectorRegistryActor.
+ */
+exports.DirectorRegistryFront = protocol.FrontClass(DirectorRegistryActor, {
+  initialize: function(client, { directorRegistryActor }) {
+    protocol.Front.prototype.initialize.call(this, client, {
+      actor: directorRegistryActor
+    });
+    this.manage(this);
+  }
+});
--- a/toolkit/devtools/server/actors/root.js
+++ b/toolkit/devtools/server/actors/root.js
@@ -153,16 +153,18 @@ RootActor.prototype = {
     // Whether the style rule actor implements the modifySelector method
     // that modifies the rule's selector
     selectorEditable: true,
     // Whether the page style actor implements the addNewRule method that
     // adds new rules to the page
     addNewRule: true,
     // Whether the dom node actor implements the getUniqueSelector method
     getUniqueSelector: true,
+    // Whether the director scripts are supported
+    directorScripts: true,
     // Whether the debugger server supports
     // blackboxing/pretty-printing (not supported in Fever Dream yet)
     noBlackBoxing: false,
     noPrettyPrinting: false,
     // Whether the page style actor implements the getUsedFontFaces method
     // that returns the font faces used on a node
     getUsedFontFaces: true
   },
--- a/toolkit/devtools/server/actors/styles.js
+++ b/toolkit/devtools/server/actors/styles.js
@@ -234,34 +234,67 @@ var PageStyleActor = protocol.ActorClass
       filter: Option(1, "string"),
     },
     response: {
       computed: RetVal("json")
     }
   }),
 
   /**
+   * Get all the fonts from a page.
+   *
+   * @param object options
+   *   `includePreviews`: Whether to also return image previews of the fonts.
+   *   `previewText`: The text to display in the previews.
+   *   `previewFontSize`: The font size of the text in the previews.
+   *
+   * @returns object
+   *   object with 'fontFaces', a list of fonts that apply to this node.
+   */
+  getAllUsedFontFaces: method(function(options) {
+    let windows = this.inspector.tabActor.windows;
+    let fontsList = [];
+    for(let win of windows){
+      fontsList = [...fontsList,
+                   ...this.getUsedFontFaces(win.document.body, options)];
+    }
+    return fontsList;
+  },
+  {
+    request: {
+      includePreviews: Option(0, "boolean"),
+      previewText: Option(0, "string"),
+      previewFontSize: Option(0, "string"),
+      previewFillStyle: Option(0, "string")
+    },
+    response: {
+      fontFaces: RetVal("array:fontface")
+    }
+  }),
+
+  /**
    * Get the font faces used in an element.
    *
-   * @param NodeActor node
+   * @param NodeActor node / actual DOM node
    *    The node to get fonts from.
    * @param object options
    *   `includePreviews`: Whether to also return image previews of the fonts.
    *   `previewText`: The text to display in the previews.
    *   `previewFontSize`: The font size of the text in the previews.
    *
    * @returns object
    *   object with 'fontFaces', a list of fonts that apply to this node.
    */
   getUsedFontFaces: method(function(node, options) {
-    let contentDocument = node.rawNode.ownerDocument;
-
+    // node.rawNode is defined for NodeActor objects
+    let actualNode = node.rawNode || node;
+    let contentDocument = actualNode.ownerDocument;
     // We don't get fonts for a node, but for a range
     let rng = contentDocument.createRange();
-    rng.selectNodeContents(node.rawNode);
+    rng.selectNodeContents(actualNode);
     let fonts = DOMUtils.getUsedFontFaces(rng);
     let fontsArray = [];
 
     for (let i = 0; i < fonts.length; i++) {
       let font = fonts.item(i);
       let fontFace = {
         name: font.name,
         CSSFamilyName: font.CSSFamilyName,
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -384,16 +384,21 @@ var DebuggerServer = {
       constructor: "WebappsActor",
       type: { global: true }
     });
     this.registerModule("devtools/server/actors/device", {
       prefix: "device",
       constructor: "DeviceActor",
       type: { global: true }
     });
+    this.registerModule("devtools/server/actors/director-registry", {
+      prefix: "directorRegistry",
+      constructor: "DirectorRegistryActor",
+      type: { global: true }
+    });
   },
 
   /**
    * Install tab actors in documents loaded in content childs
    */
   addChildActors: function () {
     // In case of apps being loaded in parent process, DebuggerServer is already
     // initialized and browser actors are already loaded,
@@ -499,16 +504,21 @@ var DebuggerServer = {
       constructor: "MonitorActor",
       type: { global: true, tab: true }
     });
     this.registerModule("devtools/server/actors/timeline", {
       prefix: "timeline",
       constructor: "TimelineActor",
       type: { global: true, tab: true }
     });
+    this.registerModule("devtools/server/actors/director-manager", {
+      prefix: "directorManager",
+      constructor: "DirectorManagerActor",
+      type: { global: false, tab: true }
+    });
     if ("nsIProfiler" in Ci) {
       this.registerModule("devtools/server/actors/profiler", {
         prefix: "profiler",
         constructor: "ProfilerActor",
         type: { global: true, tab: true }
       });
     }
     this.registerModule("devtools/server/actors/animation", {
--- a/toolkit/devtools/server/moz.build
+++ b/toolkit/devtools/server/moz.build
@@ -37,16 +37,18 @@ EXTRA_JS_MODULES.devtools.server.actors 
     'actors/animation.js',
     'actors/call-watcher.js',
     'actors/canvas.js',
     'actors/child-process.js',
     'actors/childtab.js',
     'actors/common.js',
     'actors/csscoverage.js',
     'actors/device.js',
+    'actors/director-manager.js',
+    'actors/director-registry.js',
     'actors/eventlooplag.js',
     'actors/framerate.js',
     'actors/gcli.js',
     'actors/highlighter.js',
     'actors/inspector.js',
     'actors/layout.js',
     'actors/memory.js',
     'actors/monitor.js',
--- a/toolkit/devtools/server/tests/mochitest/chrome.ini
+++ b/toolkit/devtools/server/tests/mochitest/chrome.ini
@@ -1,10 +1,12 @@
 [DEFAULT]
 support-files =
+  director-helpers.js
+  director-script-target.html
   inspector-helpers.js
   inspector-styles-data.css
   inspector-styles-data.html
   inspector-traversal-data.html
   nonchrome_unsafeDereference.html
   inspector_getImageData.html
   large-image.jpg
   memory-helpers.js
@@ -68,10 +70,13 @@ skip-if = buildapp == 'mulet'
 [test_memory_allocations_05.html]
 [test_memory_attach_01.html]
 [test_memory_attach_02.html]
 [test_memory_census.html]
 [test_memory_gc_01.html]
 [test_preference.html]
 [test_connectToChild.html]
 skip-if = buildapp == 'mulet'
+[test_director.html]
+[test_director_connectToChild.html]
++skip-if = buildapp == 'mulet'
 [test_attachProcess.html]
 skip-if = buildapp == 'mulet'
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/director-helpers.js
@@ -0,0 +1,100 @@
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
+Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+Cu.import("resource://gre/modules/devtools/Loader.jsm");
+
+const Services = devtools.require("Services");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+Services.prefs.setBoolPref("dom.mozBrowserFramesEnabled", true);
+
+SimpleTest.registerCleanupFunction(function() {
+  Services.prefs.clearUserPref("devtools.debugger.log");
+  Services.prefs.clearUserPref("dom.mozBrowserFramesEnabled");
+});
+
+const {Class} = devtools.require("sdk/core/heritage");
+
+const {promiseInvoke} = devtools.require("devtools/async-utils");
+
+const { DirectorRegistry,
+        DirectorRegistryFront } = devtools.require("devtools/server/actors/director-registry");
+
+const { DirectorManagerFront } = devtools.require("devtools/server/actors/director-manager");
+const protocol = devtools.require("devtools/server/protocol");
+
+const {Task} = devtools.require("resource://gre/modules/Task.jsm");
+
+/***********************************
+ *  director helpers functions
+ **********************************/
+
+function waitForEvent(target, name) {
+  return new Promise((resolve, reject) => {
+      target.once(name, (...args) => { resolve(args); });
+  });
+}
+
+function* newConnectedDebuggerClient(opts) {
+  var transport = DebuggerServer.connectPipe();
+  var client = new DebuggerClient(transport);
+
+  yield promiseInvoke(client, client.connect);
+
+  var root = yield promiseInvoke(client, client.listTabs);
+
+  return {
+    client: client,
+    root: root,
+    transport: transport
+  };
+}
+
+function* installTestDirectorScript(client, root,  scriptId, scriptDefinition) {
+  var directorRegistryClient = new DirectorRegistryFront(client, root);
+
+  yield directorRegistryClient.install(scriptId, scriptDefinition);
+
+  directorRegistryClient.destroy();
+}
+
+function* getTestDirectorScript(manager, tab, scriptId) {
+  var directorScriptClient = yield manager.getByScriptId(scriptId);
+  return directorScriptClient;
+}
+
+function purgeInstalledDirectorScripts() {
+  DirectorRegistry.clear();
+}
+
+function* installDirectorScriptAndWaitAttachOrError({client, root, manager,
+                                                     scriptId, scriptDefinition}) {
+  yield installTestDirectorScript(client, root, scriptId, scriptDefinition);
+
+  var selectedTab = root.tabs[root.selected];
+  var testDirectorScriptClient = yield getTestDirectorScript(manager, selectedTab, scriptId);
+
+  var waitForDirectorScriptAttach = waitForEvent(testDirectorScriptClient, "attach");
+  var waitForDirectorScriptError = waitForEvent(testDirectorScriptClient, "error");
+
+  testDirectorScriptClient.setup({reload: false});
+
+  var [receivedEvent] = yield Promise.race([waitForDirectorScriptAttach,
+                                            waitForDirectorScriptError]);
+
+  testDirectorScriptClient.finalize();
+
+  return receivedEvent;
+}
+
+function assertIsDirectorScriptError(error) {
+  ok(!!error, "received error should be defined");
+  ok(!!error.message, "errors should contain a message");
+  ok(!!error.stack, "errors should contain a stack trace");
+  ok(!!error.fileName, "errors should contain a fileName");
+  ok(typeof error.columnNumber == "number", "errors should contain a columnNumber");
+  ok(typeof error.lineNumber == "number", "errors should contain a lineNumber");
+
+  ok(!!error.directorScriptId, "errors should contain a directorScriptId");
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/director-script-target.html
@@ -0,0 +1,15 @@
+<html>
+  <head>
+    <script>
+      // change the eval function to ensure the window object in the debug-script is correctly wrapped
+      window.eval = function () {
+        return "unsecure-eval-called";
+      };
+
+      var globalAccessibleVar = "global-value";
+    </script>
+  </head>
+  <body>
+    <h1>debug script target</h1>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_director.html
@@ -0,0 +1,479 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug </title>
+
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+  <script type="application/javascript;version=1.8" src="./director-helpers.js"></script>
+  <script type="application/javascript;version=1.8">
+window.onload = function() {
+  Task.spawn(function* () {
+    SimpleTest.waitForExplicitFinish();
+
+    var tests = [
+      runDirectorScriptModuleExports,
+      runDirectorScriptErrorOnNoAttachExports,
+      runDirectorScriptErrorOnLoadTest,
+      runDirectorScriptErrorOnRequire,
+      runDirectorScriptErrorOnUnloadTest,
+      runDirectorScriptSetupAndReceiveMessagePortTest,
+      runDirectorEnableDirectorScriptsTest,
+      runDirectorScriptDetachEventTest,
+      runDirectorScriptWindowEval
+    ].map((testCase) => {
+      return function* () {
+        setup();
+        yield testCase().then(null, (e) => {
+          console.error("Exception during testCase run", e);
+          ok(false, "Exception during testCase run: " + [e, e.fileName, e.lineNumber].join("\n\t"));
+        });
+
+        teardown();
+      };
+    });
+
+    for (var test of tests) {
+      yield test();
+    }
+  }).then(
+    function success() {
+      SimpleTest.finish()
+    },
+    function error(e) {
+      console.error("Exception during testCase run", e);
+      ok(false, "Exception during testCase run: " + [e, e.fileName, e.lineNumber].join("\n\t"));
+
+      SimpleTest.finish();
+    }
+  );
+};
+
+var targetWin = null;
+
+function setup() {
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init(() => true);
+    DebuggerServer.addBrowserActors();
+
+    SimpleTest.registerCleanupFunction(teardown);
+  }
+}
+
+function teardown() {
+  purgeInstalledDirectorScripts();
+
+  DebuggerServer.destroy();
+  if (targetWin) {
+    targetWin.close();
+  }
+}
+
+/***********************************
+ *  test cases
+ **********************************/
+
+function runDirectorScriptModuleExports() {
+  targetWin = window.open("about:blank");
+
+  var testDirectorScriptModuleExports = {
+    scriptCode: "(" + (function() {
+       module.exports = function() {};
+    }).toString() + ")();",
+    scriptOptions: {}
+  }
+
+  var testDirectorScriptAttachMethodOption = {
+    scriptCode: "(" + (function() {
+       exports.attach = function() {};
+    }).toString() + ")();",
+    scriptOptions: {
+       attachMethod: "attach"
+    }
+  }
+
+  return Task.spawn(function* () {
+    var { client, root } = yield newConnectedDebuggerClient();
+
+    var selectedTab = root.tabs[root.selected];
+    var manager = new DirectorManagerFront(client, selectedTab);
+
+    var receivedEvent1 = yield installDirectorScriptAndWaitAttachOrError({
+      client: client, root: root, manager: manager,
+      scriptId: "testDirectorscriptModuleExports",
+      scriptDefinition: testDirectorScriptModuleExports
+    });
+    ok(!!receivedEvent1.port, "received attach from testDirectorScriptModuleExports");
+
+    var receivedEvent2 = yield installDirectorScriptAndWaitAttachOrError({
+      client: client, root: root, manager: manager,
+      scriptId: "testDirectorscriptAttachMethodOption",
+      scriptDefinition: testDirectorScriptModuleExports
+    });
+    ok(!!receivedEvent2.port, "received attach event from testDirectorScriptAttachMethodOption");
+
+    client.close();
+   })
+}
+
+function runDirectorScriptErrorOnNoAttachExports() {
+  targetWin = window.open("about:blank");
+
+  var testDirectorScriptRaiseErrorOnNoAttachExports = {
+    scriptCode: "(" + (function() {
+      // this director script should raise an error
+      // because it doesn't export any attach method
+    }).toString() + ")();",
+    scriptOptions: {}
+  }
+
+  return Task.spawn(function* () {
+    var { client, root } = yield newConnectedDebuggerClient();
+
+    var selectedTab = root.tabs[root.selected];
+    var manager = new DirectorManagerFront(client, selectedTab);
+
+    var error = yield installDirectorScriptAndWaitAttachOrError({
+      client: client, root: root, manager: manager,
+      scriptId: "testDirectorscriptRaiseErrorOnNoAttachExports",
+      scriptDefinition: testDirectorScriptRaiseErrorOnNoAttachExports
+    });
+
+    assertIsDirectorScriptError(error);
+
+    client.close();
+  });
+}
+
+function runDirectorScriptErrorOnRequire() {
+  targetWin = window.open("about:blank");
+
+  var testDirectorScriptRaiseErrorOnRequire = {
+    scriptCode: "(" + (function() {
+      // this director script should raise an error
+      // because require raise a "not implemented" exception
+      console.log("PROVA", this)
+      require("fake_module");
+    }).toString() + ")();",
+    scriptOptions: {}
+  }
+
+  return Task.spawn(function* () {
+    var { client, root } = yield newConnectedDebuggerClient();
+
+    var selectedTab = root.tabs[root.selected];
+    var manager = new DirectorManagerFront(client, selectedTab);
+
+    var error = yield installDirectorScriptAndWaitAttachOrError({
+      client: client, root: root, manager: manager,
+      scriptId: "testDirectorscriptRaiseErrorOnRequire",
+      scriptDefinition: testDirectorScriptRaiseErrorOnRequire
+    });
+
+    assertIsDirectorScriptError(error);
+    is(error.message, "Error: NOT IMPLEMENTED", "error message should contains the expected error message");
+    client.close();
+  });
+}
+
+function runDirectorScriptErrorOnLoadTest() {
+  targetWin = window.open("about:blank");
+
+  var testDirectorScriptRaiseErrorOnLoad = {
+    scriptCode: "(" + (function() {
+       // this will raise an exception on evaluating
+       // the director script
+       raise.an_error.during.content_script.load();
+    }).toString() + ")();",
+    scriptOptions: {}
+  }
+
+  return Task.spawn(function* () {
+    var { client, root } = yield newConnectedDebuggerClient();
+
+    yield installTestDirectorScript(client, root, "testDirectorScript",
+                                  testDirectorScriptRaiseErrorOnLoad);
+
+    var selectedTab = root.tabs[root.selected];
+    var manager = new DirectorManagerFront(client, selectedTab);
+    var testDirectorScriptClient = yield getTestDirectorScript(manager, selectedTab, "testDirectorScript");
+
+    var waitForDirectorScriptError = waitForEvent(testDirectorScriptClient, "error");
+
+    // activate the director script without window reloading
+    testDirectorScriptClient.setup({reload: false});
+
+    var [error] = yield waitForDirectorScriptError;
+
+    assertIsDirectorScriptError(error);
+
+    client.close();
+  });
+}
+
+function runDirectorScriptErrorOnUnloadTest() {
+  targetWin = window.open("about:blank");
+
+  var testDirectorScriptRaiseErrorOnUnload = {
+    scriptCode: "(" + (function() {
+       module.exports = function({onUnload}) {
+         // this will raise an exception on unload the director script
+         onUnload(function() {
+           raise_an_error_onunload();
+         });
+       };
+    }).toString() + ")();",
+    scriptOptions: {}
+  }
+
+  return Task.spawn(function* () {
+    var { client, root } = yield newConnectedDebuggerClient();
+
+    yield installTestDirectorScript(client, root, "testDirectorScript",
+                                  testDirectorScriptRaiseErrorOnUnload);
+
+    var selectedTab = root.tabs[root.selected];
+    var manager = new DirectorManagerFront(client, selectedTab);
+    var testDirectorScriptClient = yield getTestDirectorScript(manager, selectedTab, "testDirectorScript");
+    var waitForDirectorScriptAttach = waitForEvent(testDirectorScriptClient, "attach");
+
+    // activate the director script without window reloading
+    testDirectorScriptClient.setup({reload: false});
+
+    yield waitForDirectorScriptAttach;
+
+    var waitForDirectorScriptError = waitForEvent(testDirectorScriptClient, "error");
+
+    testDirectorScriptClient.finalize();
+
+    var [error] = yield waitForDirectorScriptError;
+
+    assertIsDirectorScriptError(error);
+
+    client.close();
+  });
+}
+
+
+function runDirectorScriptSetupAndReceiveMessagePortTest() {
+  targetWin = window.open("about:blank");
+
+  var testDirectorScriptOptions = {
+    scriptCode: "(" + (function() {
+        module.exports = function({port}) {
+          port.onmessage = function(evt) {
+            // echo messages
+            evt.source.postMessage(evt.data);
+          };
+        };
+    }).toString() + ")();",
+    scriptOptions: {}
+  }
+
+  return Task.spawn(function* () {
+    var { client, root } = yield newConnectedDebuggerClient();
+
+    yield installTestDirectorScript(client, root, "testDirectorScript",
+                                  testDirectorScriptOptions);
+
+    var selectedTab = root.tabs[root.selected];
+
+    // get a testDirectorScriptClient
+    var manager = new DirectorManagerFront(client, selectedTab);
+    var testDirectorScriptClient = yield getTestDirectorScript(manager, selectedTab, "testDirectorScript");
+
+    var waitForDirectorScriptAttach = waitForEvent(testDirectorScriptClient, "attach");
+
+    // activate the director script without window reloading
+    // (and wait for attach)
+    testDirectorScriptClient.setup({reload: false});
+
+    var [attachEvent] = yield waitForDirectorScriptAttach;
+
+    // call the connectPort method to get a MessagePortClient
+    var port = attachEvent.port;
+
+    ok(!!port && !!port.postMessage, "messageport actor client received");
+
+    // exchange messages over the MessagePort
+    var waitForMessagePortMessage = waitForEvent(port, "message");
+    // needs to explicit start the port
+    port.start();
+
+    var msg = { k1: "v1", k2: [1, 2, 3] };
+    port.postMessage(msg);
+
+    var reply = yield waitForMessagePortMessage;
+
+    ok(JSON.stringify(reply[0].data) === JSON.stringify(msg),
+       "echo reply received on the MessagePortClient");
+
+    yield client.close();
+  });
+}
+
+function runDirectorEnableDirectorScriptsTest() {
+  targetWin = window.open("about:blank");
+
+  var testDirectorScriptOptions = {
+    scriptCode: "(" + (function() {
+      module.exports = function({port}) {
+        port.onmessage = function(evt) {
+          // echo messages
+          evt.source.postMessage(evt.data);
+        };
+      };
+    }).toString() + ")();",
+    scriptOptions: {}
+  }
+
+  return Task.spawn(function* () {
+    var { client, root } = yield newConnectedDebuggerClient();
+
+    yield installTestDirectorScript(client, root, "testDirectorScript",
+                                 testDirectorScriptOptions);
+
+    var selectedTab = root.tabs[root.selected];
+
+    var tabDirectorClient = new DirectorManagerFront(client, selectedTab);
+
+    var waitForDirectorScriptAttach = waitForEvent(tabDirectorClient, "director-script-attach");
+
+    tabDirectorClient.enableByScriptIds(["*"], { reload: false });
+
+    var [attachEvent] = yield waitForDirectorScriptAttach;
+
+    is(attachEvent.directorScriptId, "testDirectorScript", "attach event should contains directorScriptId");
+
+    yield client.close();
+  });
+}
+
+function runDirectorScriptDetachEventTest() {
+  targetWin = window.open("director-script-target.html");
+
+  var testDirectorScriptOptions = {
+    scriptCode: "(" + (function() {
+        exports.attach = function({port, onUnload}) {
+            onUnload(function() {
+              port.postMessage("ONUNLOAD");
+            });
+        };
+    }).toString() + ")();",
+    scriptOptions: {
+      attachMethod: "attach"
+    }
+  }
+
+  return Task.spawn(function* () {
+    var { client, root } = yield newConnectedDebuggerClient();
+
+    yield installTestDirectorScript(client, root, "testDirectorScript",
+                                 testDirectorScriptOptions);
+
+    var selectedTab = root.tabs[root.selected];
+
+    // NOTE: tab needs to be attached to receive director-script-detach events
+    yield promiseInvoke(client, client.attachTab, selectedTab.actor);
+
+    var tabDirectorClient = new DirectorManagerFront(client, selectedTab);
+
+    var waitForDirectorScriptAttach = waitForEvent(tabDirectorClient, "director-script-attach");
+    var waitForDirectorScriptDetach = waitForEvent(tabDirectorClient, "director-script-detach");
+
+    tabDirectorClient.enableByScriptIds(["*"], {reload: true});
+
+    var [attachEvent] = yield waitForDirectorScriptAttach;
+
+    // exchange messages over the MessagePort
+    var waitForMessagePortEvent = waitForEvent(attachEvent.port, "message");
+    // needs to explicit start the port
+    attachEvent.port.start();
+
+    tabDirectorClient.disableByScriptIds(["*"], {reload: false});
+
+    // changing the window location should generate a director-script-detach event
+    var [detachEvent] = yield waitForDirectorScriptDetach;
+
+    is(detachEvent.directorScriptId, "testDirectorScript", "detach event should contains directorScriptId");
+
+    var [portEvent] = yield waitForMessagePortEvent;
+
+    is(portEvent.data, "ONUNLOAD", "director-script's exports.onUnload called on detach");
+
+    yield client.close();
+  });
+}
+
+function runDirectorScriptWindowEval() {
+  targetWin = window.open("http://mochi.test:8888/chrome/toolkit/devtools/server/tests/mochitest/director-script-target.html");
+
+  var testDirectorScriptOptions = {
+    scriptCode: "(" + (function() {
+      exports.attach = function({window, port}) {
+        var onpageloaded = function() {
+          var globalVarValue = window.eval("window.globalAccessibleVar;");
+          port.postMessage(globalVarValue);
+        };
+
+        if (window.document.readyState === "complete") {
+          onpageloaded();
+        } else {
+          window.onload = onpageloaded;
+        }
+      };
+    }).toString() + ")();",
+    scriptOptions: {
+      attachMethod: "attach"
+    }
+  }
+
+  return Task.spawn(function* () {
+    var { client, root } = yield newConnectedDebuggerClient();
+
+    yield installTestDirectorScript(client, root, "testDirectorScript",
+                                 testDirectorScriptOptions);
+
+    var selectedTab = root.tabs[root.selected];
+
+    // NOTE: tab needs to be attached to receive director-script-detach events
+    yield promiseInvoke(client, client.attachTab, selectedTab.actor);
+
+    var tabDirectorClient = new DirectorManagerFront(client, selectedTab);
+
+    var waitForDirectorScriptAttach = waitForEvent(tabDirectorClient, "director-script-attach");
+    var waitForDirectorScriptError = waitForEvent(tabDirectorClient, "director-script-error");
+
+    tabDirectorClient.enableByScriptIds(["*"], {reload: false});
+
+    var [receivedEvent] = yield Promise.race([waitForDirectorScriptAttach,
+                                              waitForDirectorScriptError]);
+
+    ok(!!receivedEvent.port, "received director-script-attach");
+
+    // exchange messages over the MessagePort
+    var waitForMessagePortEvent = waitForEvent(receivedEvent.port, "message");
+    // needs to explicit start the port
+    receivedEvent.port.start();
+
+    var [portEvent] = yield waitForMessagePortEvent;
+
+    ok(portEvent.data !== "unsecure-eval", "window.eval should be wrapped and safe");
+
+    is(portEvent.data, "global-value", "window.globalAccessibleVar should be accessible through window.eval");
+
+    yield client.close();
+  });
+}
+
+  </script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_director_connectToChild.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug </title>
+
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+  <script type="application/javascript;version=1.8" src="./director-helpers.js"></script>
+  <script type="application/javascript;version=1.8">
+window.onload = function() {
+  Task.spawn(function* () {
+    SimpleTest.waitForExplicitFinish();
+
+    var tests = [
+      runPropagateDirectorScriptsToChildTest,
+    ].map((testCase) => {
+      return function* () {
+        setup();
+        yield testCase().then(null, (e) => {
+          ok(false, "Exception during testCase run: " + [e, e.fileName, e.lineNumber].join("\n\t"));
+        });
+
+        teardown();
+      };
+    });
+
+    for (var test of tests) {
+      yield test();
+    }
+
+    SimpleTest.finish();
+  });
+};
+
+function setup() {
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init(() => true);
+    DebuggerServer.addBrowserActors();
+    SimpleTest.registerCleanupFunction(function() {
+      DebuggerServer.destroy();
+    });
+  }
+}
+
+function teardown() {
+  purgeInstalledDirectorScripts();
+  DebuggerServer.destroy();
+}
+
+/***********************************
+ *  test cases
+ **********************************/
+