Merge m-c to cedar
authorJames Graham <james@hoppipolla.co.uk>
Mon, 22 Jun 2015 09:34:28 -0700
changeset 326828 f5e3b4a5fdc3c65803e1a0c27e061c560e6deeb4
parent 326827 dc9ea6206b36a2fa468b590ddddfc80561d38d77 (current diff)
parent 273783 be81b8d6fae99c89e8b14591b11dd26eec0a416e (diff)
child 326829 bf7938244daf14e5be8008150d62c952ca69fa83
push id10169
push userdminor@mozilla.com
push dateThu, 28 Jan 2016 13:10:48 +0000
milestone41.0a1
Merge m-c to cedar
CLOBBER
browser/base/content/test/general/browser_trackingUI.js
browser/devtools/layoutview/view.css
browser/devtools/performance/system.js
browser/devtools/performance/test/browser_perf-jit-model-01.js
browser/devtools/performance/test/browser_perf-jit-model-02.js
browser/devtools/performance/views/jit-optimizations.js
browser/devtools/shared/widgets/Graphs.jsm
caps/tests/mochitest/moz.build
content/moz.build
content/test/reftest/bug427779-ref.xml
content/test/reftest/bug427779.xml
content/test/reftest/bug439965-ref.html
content/test/reftest/bug439965.html
content/test/reftest/bug453105-ref.html
content/test/reftest/bug453105.html
content/test/reftest/bug456008-ref.html
content/test/reftest/bug456008.xhtml
content/test/reftest/bug559996-iframe.html
content/test/reftest/bug559996-ref-iframe.html
content/test/reftest/bug559996-ref.html
content/test/reftest/bug559996.html
content/test/reftest/bug591981-1.html
content/test/reftest/bug591981-2.html
content/test/reftest/bug591981-ref.html
content/test/reftest/bug591981-script.js
content/test/reftest/bug592366-1.html
content/test/reftest/bug592366-1.xhtml
content/test/reftest/bug592366-2.html
content/test/reftest/bug592366-2.xhtml
content/test/reftest/bug592366-ref.html
content/test/reftest/bug592366-ref.xhtml
content/test/reftest/bug798068-ref.xhtml
content/test/reftest/bug798068.xhtml
content/test/reftest/child592366-1.html
content/test/reftest/child592366-1.xhtml
content/test/reftest/child592366-2.html
content/test/reftest/child592366-2.xhtml
content/test/reftest/optiontext-ref.html
content/test/reftest/optiontext.html
content/test/reftest/reftest.list
content/test/reftest/script592366-2.js
content/test/reftest/xml-stylesheet/css_relative_href.xml
content/test/reftest/xml-stylesheet/css_relative_href_also_external.xml
content/test/reftest/xml-stylesheet/css_relative_href_also_external.xml^headers^
content/test/reftest/xml-stylesheet/css_relative_href_also_external_override.xml
content/test/reftest/xml-stylesheet/css_relative_href_also_external_override.xml^headers^
content/test/reftest/xml-stylesheet/embedded_dtd_id.svg
content/test/reftest/xml-stylesheet/error_no_href.svg
content/test/reftest/xml-stylesheet/fail.svg
content/test/reftest/xml-stylesheet/failer.css
content/test/reftest/xml-stylesheet/lreas_selflink_dtd_id.svg
content/test/reftest/xml-stylesheet/lreas_selflink_empty_href.svg
content/test/reftest/xml-stylesheet/lreas_selflink_relative_href.svg
content/test/reftest/xml-stylesheet/pass.svg
content/test/reftest/xml-stylesheet/passer.css
content/test/reftest/xml-stylesheet/passer_override.css
content/test/reftest/xml-stylesheet/reftest.list
content/test/reftest/xml-stylesheet/svg_passer.xslt
content/test/reftest/xml-stylesheet/xslt_relative_href.svg
content/test/reftest/xml-stylesheet/xslt_selflink_dtd_id.xml
content/test/reftest/xml-stylesheet/xslt_selflink_empty_href.xml
content/test/reftest/xml-stylesheet/xslt_selflink_relative_href.xml
content/test/unit/empty_document.xml
content/test/unit/head_content.js
content/test/unit/isequalnode_data.xml
content/test/unit/nodelist_data_1.xml
content/test/unit/nodelist_data_2.xul
content/test/unit/test_delete_range.xml
content/test/unit/test_isequalnode.js
content/test/unit/test_nodelist.js
content/test/unit/test_normalize.js
content/test/unit/test_range.js
content/test/unit/test_treewalker.js
content/test/unit/test_xml_parser.js
content/test/unit/test_xml_serializer.js
content/test/unit/xpcshell.ini
dom/base/MessageChannel.cpp
dom/base/MessageChannel.h
dom/base/MessagePort.cpp
dom/base/MessagePort.h
dom/base/MessagePortList.cpp
dom/base/MessagePortList.h
dom/base/test/chrome.ini
dom/base/test/iframe_messageChannel_chrome.html
dom/base/test/iframe_messageChannel_cloning.html
dom/base/test/iframe_messageChannel_pingpong.html
dom/base/test/iframe_messageChannel_post.html
dom/base/test/test_bug1060938.html
dom/base/test/test_bug1075702.html
dom/base/test/test_messageChannel.html
dom/base/test/test_messageChannel.xul
dom/base/test/test_messageChannel_cloning.html
dom/base/test/test_messageChannel_pingpong.html
dom/base/test/test_messageChannel_post.html
dom/base/test/test_messageChannel_pref.html
dom/base/test/test_messageChannel_start.html
dom/base/test/test_messageChannel_transferable.html
dom/base/test/test_messageChannel_unshipped.html
dom/browser-element/mochitest/priority/test_ForegroundLRU.html
dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/mochitest.ini
dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/test_interfaces.html.json
dom/indexedDB/test/unit/test_cursor_update_updates_indexes.js
dom/indexedDB/test/unit/test_objectStore_remove_values.js
dom/indexedDB/test/unit/test_setVersion.js
dom/media/wmf/DXVA2Manager.cpp
dom/media/wmf/DXVA2Manager.h
dom/media/wmf/WMF.h
dom/media/wmf/WMFByteStream.cpp
dom/media/wmf/WMFByteStream.h
dom/media/wmf/WMFDecoder.cpp
dom/media/wmf/WMFDecoder.h
dom/media/wmf/WMFReader.cpp
dom/media/wmf/WMFReader.h
dom/media/wmf/WMFSourceReaderCallback.cpp
dom/media/wmf/WMFSourceReaderCallback.h
dom/media/wmf/WMFUtils.cpp
dom/media/wmf/WMFUtils.h
dom/media/wmf/moz.build
dom/push/PushService.jsm
dom/settings/tests/chrome.ini
image/test/mochitest/test_bug512435.html
layout/base/nsRefreshDriver.cpp
media/libvpx/apple-clang.patch
media/libvpx/build/make/obj_int_extract.c
media/libvpx/vp8/common/arm/neon/buildintrapredictorsmby_neon.asm
media/libvpx/vp8/common/arm/neon/copymem16x16_neon.asm
media/libvpx/vp8/common/arm/neon/copymem8x4_neon.asm
media/libvpx/vp8/common/arm/neon/copymem8x8_neon.asm
media/libvpx/vp8/common/arm/neon/dc_only_idct_add_neon.asm
media/libvpx/vp8/common/arm/neon/dequant_idct_neon.asm
media/libvpx/vp8/common/arm/neon/dequantizeb_neon.asm
media/libvpx/vp8/common/arm/neon/idct_dequant_0_2x_neon.asm
media/libvpx/vp8/common/arm/neon/idct_dequant_full_2x_neon.asm
media/libvpx/vp8/common/arm/neon/iwalsh_neon.asm
media/libvpx/vp8/common/arm/neon/loopfilter_neon.asm
media/libvpx/vp8/common/arm/neon/loopfiltersimplehorizontaledge_neon.asm
media/libvpx/vp8/common/arm/neon/loopfiltersimpleverticaledge_neon.asm
media/libvpx/vp8/common/arm/neon/mbloopfilter_neon.asm
media/libvpx/vp8/common/arm/neon/sad16_neon.asm
media/libvpx/vp8/common/arm/neon/sad8_neon.asm
media/libvpx/vp8/common/arm/neon/save_reg_neon.asm
media/libvpx/vp8/common/arm/neon/shortidct4x4llm_neon.asm
media/libvpx/vp8/common/arm/neon/sixtappredict16x16_neon.asm
media/libvpx/vp8/common/arm/neon/sixtappredict4x4_neon.asm
media/libvpx/vp8/common/arm/neon/sixtappredict8x4_neon.asm
media/libvpx/vp8/common/arm/neon/sixtappredict8x8_neon.asm
media/libvpx/vp8/common/arm/neon/variance_neon.asm
media/libvpx/vp8/common/arm/neon/vp8_subpixelvariance16x16_neon.asm
media/libvpx/vp8/common/arm/neon/vp8_subpixelvariance16x16s_neon.asm
media/libvpx/vp8/common/arm/neon/vp8_subpixelvariance8x8_neon.asm
media/libvpx/vp8/common/arm/reconintra_arm.c
media/libvpx/vp8/common/pragmas.h
media/libvpx/vp8/common/x86/loopfilter_block_sse2.asm
media/libvpx/vp8/common/x86/postproc_x86.c
media/libvpx/vp8/encoder/arm/armv5te/boolhuff_armv5te.asm
media/libvpx/vp8/encoder/arm/armv5te/vp8_packtokens_armv5.asm
media/libvpx/vp8/encoder/arm/armv5te/vp8_packtokens_mbrow_armv5.asm
media/libvpx/vp8/encoder/arm/armv5te/vp8_packtokens_partitions_armv5.asm
media/libvpx/vp8/encoder/arm/armv6/vp8_fast_quantize_b_armv6.asm
media/libvpx/vp8/encoder/arm/armv6/vp8_subtract_armv6.asm
media/libvpx/vp8/encoder/arm/boolhuff_arm.c
media/libvpx/vp8/encoder/arm/neon/fastquantizeb_neon.asm
media/libvpx/vp8/encoder/arm/neon/picklpf_arm.c
media/libvpx/vp8/encoder/arm/neon/shortfdct_neon.asm
media/libvpx/vp8/encoder/arm/neon/subtract_neon.asm
media/libvpx/vp8/encoder/arm/neon/vp8_memcpy_neon.asm
media/libvpx/vp8/encoder/arm/neon/vp8_mse16x16_neon.asm
media/libvpx/vp8/encoder/arm/neon/vp8_shortwalsh4x4_neon.asm
media/libvpx/vp8/encoder/arm/quantize_arm.c
media/libvpx/vp8/encoder/psnr.c
media/libvpx/vp8/encoder/psnr.h
media/libvpx/vp8/encoder/vp8_asm_enc_offsets.c
media/libvpx/vp8/encoder/x86/quantize_sse4.asm
media/libvpx/vp8/encoder/x86/quantize_ssse3.asm
media/libvpx/vp9/common/arm/neon/vp9_avg_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_convolve8_avg_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_convolve8_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_copy_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_dc_only_idct_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct16x16_1_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct16x16_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct32x32_1_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct32x32_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct4x4_1_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct4x4_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct8x8_1_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_idct8x8_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_iht4x4_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_iht8x8_add_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_loopfilter_16_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_loopfilter_neon.asm
media/libvpx/vp9/common/arm/neon/vp9_reconintra_neon.asm
media/libvpx/vp9/common/generic/vp9_systemdependent.c
media/libvpx/vp9/common/vp9_onyx.h
media/libvpx/vp9/common/vp9_pragmas.h
media/libvpx/vp9/common/x86/vp9_postproc_mmx.asm
media/libvpx/vp9/common/x86/vp9_postproc_x86.h
media/libvpx/vp9/decoder/vp9_onyxd.h
media/libvpx/vp9/decoder/vp9_onyxd_if.c
media/libvpx/vp9/decoder/vp9_onyxd_int.h
media/libvpx/vp9/decoder/vp9_thread.c
media/libvpx/vp9/decoder/vp9_thread.h
media/libvpx/vp9/encoder/vp9_onyx_if.c
media/libvpx/vp9/encoder/vp9_onyx_int.h
media/libvpx/vp9/encoder/vp9_psnr.c
media/libvpx/vp9/encoder/vp9_psnr.h
media/libvpx/vp9/encoder/vp9_vaq.c
media/libvpx/vp9/encoder/vp9_vaq.h
media/libvpx/vp9/encoder/x86/vp9_mcomp_x86.h
media/libvpx/vp9/encoder/x86/vp9_quantize_ssse3.asm
media/libvpx/vp9/encoder/x86/vp9_sad_mmx.asm
media/libvpx/vp9/encoder/x86/vp9_subpel_variance_impl_sse2.asm
media/libvpx/vp9/encoder/x86/vp9_variance_impl_mmx.asm
media/libvpx/vp9/encoder/x86/vp9_variance_impl_sse2.asm
media/libvpx/vp9/encoder/x86/vp9_variance_mmx.c
media/libvpx/vpx_ports/asm_offsets.h
media/libvpx/vpx_scale/arm/neon/vp8_vpxyv12_copy_y_neon.asm
media/libvpx/vpx_scale/arm/neon/vp8_vpxyv12_copyframe_func_neon.asm
media/libvpx/vpx_scale/arm/neon/vp8_vpxyv12_copysrcframe_func_neon.asm
media/libvpx/vpx_scale/arm/neon/vp8_vpxyv12_extendframeborders_neon.asm
media/libvpx/vpx_scale/arm/neon/yv12extend_arm.c
media/libvpx/vpx_scale/vpx_scale_asm_offsets.c
media/mtransport/runnable_utils.py
media/mtransport/runnable_utils_generated.h
mobile/android/base/JavaAddonManager.java
mobile/android/base/resources/drawable-hdpi/menu_pb.png
mobile/android/base/resources/drawable-mdpi/menu_pb.png
mobile/android/base/resources/drawable-xhdpi/menu_pb.png
mobile/android/base/resources/drawable/menu_level.xml
mobile/android/tests/browser/robocop/roboextender/SelectionUtils.js
mobile/android/tests/browser/robocop/roboextender/paymentsUI.html
mobile/android/tests/browser/robocop/roboextender/robocop_home_banner.html
mobile/android/tests/browser/robocop/roboextender/robocop_prompt_gridinput.html
mobile/android/tests/browser/robocop/roboextender/testInputSelections.html
mobile/android/tests/browser/robocop/roboextender/testSelectionHandler.html
mobile/android/tests/browser/robocop/roboextender/testTextareaSelections.html
mobile/android/tests/browser/robocop/testBrowserProviderPerf.java
mobile/android/tests/browser/robocop/testCheck.java
mobile/android/tests/browser/robocop/testPan.java
netwerk/protocol/websocket/WebSocketChannel.cpp
security/manager/ssl/tests/unit/test_pinning_dynamic/badca.der
security/manager/ssl/tests/unit/test_pinning_dynamic/cn-a.pinning2.example.com-badca.der
security/manager/ssl/tests/unit/test_pinning_dynamic/cn-a.pinning2.example.com-pinningroot.der
security/manager/ssl/tests/unit/test_pinning_dynamic/cn-b.pinning2.example.com-badca.der
security/manager/ssl/tests/unit/test_pinning_dynamic/cn-b.pinning2.example.com-pinningroot.der
security/manager/ssl/tests/unit/test_pinning_dynamic/cn-www.example.com-alt-a.pinning2.example-badca.der
security/manager/ssl/tests/unit/test_pinning_dynamic/cn-www.example.com-alt-a.pinning2.example-pinningroot.der
security/manager/ssl/tests/unit/test_pinning_dynamic/cn-x.a.pinning2.example.com-badca.der
security/manager/ssl/tests/unit/test_pinning_dynamic/cn-x.a.pinning2.example.com-pinningroot.der
security/manager/ssl/tests/unit/test_pinning_dynamic/cn-x.b.pinning2.example.com-badca.der
security/manager/ssl/tests/unit/test_pinning_dynamic/cn-x.b.pinning2.example.com-pinningroot.der
security/manager/ssl/tests/unit/test_pinning_dynamic/generate.py
security/manager/ssl/tests/unit/test_pinning_dynamic/pinning_root_generate.py
security/manager/ssl/tests/unit/test_pinning_dynamic/pinningroot.der
security/manager/ssl/tests/unit/test_pinning_dynamic/pinningroot.key
testing/mochitest/roboextender/Makefile.in
testing/mochitest/roboextender/bootstrap.js
testing/mochitest/roboextender/chrome.manifest
testing/mochitest/roboextender/install.rdf
testing/mochitest/roboextender/moz.build
testing/mozharness/mozharness.json
testing/web-platform/meta/media-source/mediasource-duration.html.ini
testing/web-platform/meta/webmessaging/Channel_MessagePort_initial_disabled.htm.ini
testing/web-platform/meta/webmessaging/Channel_MessagePort_onmessage_start.htm.ini
testing/web-platform/meta/webmessaging/Channel_postMessage_DataCloneErr.htm.ini
testing/web-platform/meta/webmessaging/Channel_postMessage_clone_port.htm.ini
testing/web-platform/meta/webmessaging/Channel_postMessage_clone_port_error.htm.ini
testing/web-platform/meta/webmessaging/Channel_postMessage_event_properties.htm.ini
testing/web-platform/meta/webmessaging/Channel_postMessage_target_source.htm.ini
testing/web-platform/meta/webmessaging/Transferred_objects_unusable.sub.htm.ini
testing/web-platform/meta/webmessaging/event.ports.sub.htm.ini
testing/web-platform/meta/webmessaging/message-channels/001.html.ini
testing/web-platform/meta/webmessaging/message-channels/002.html.ini
testing/web-platform/meta/webmessaging/message-channels/003.html.ini
testing/web-platform/meta/webmessaging/message-channels/004.html.ini
testing/web-platform/meta/webmessaging/postMessage_MessagePorts_sorigin.htm.ini
testing/web-platform/meta/webmessaging/postMessage_MessagePorts_xorigin.sub.htm.ini
testing/web-platform/meta/webmessaging/without-ports/023.html.ini
testing/web-platform/meta/webmessaging/without-ports/024.html.ini
testing/web-platform/meta/webmessaging/without-ports/025.html.ini
testing/web-platform/meta/workers/MessagePort_initial_disabled.htm.ini
testing/web-platform/meta/workers/MessagePort_onmessage_start.htm.ini
testing/web-platform/meta/workers/WorkerLocation_hash_encoding.htm.ini
testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html.ini
testing/web-platform/meta/workers/postMessage_clone_port.htm.ini
testing/web-platform/meta/workers/postMessage_clone_port_error.htm.ini
testing/web-platform/meta/workers/postMessage_target_source.htm.ini
testing/web-platform/meta/workers/semantics/multiple-workers/008.html.ini
toolkit/devtools/server/ChromeUtils.cpp
toolkit/devtools/server/ChromeUtils.h
toolkit/devtools/server/tests/mochitest/chrome.ini
xpcom/base/StackWalk.h
xpcom/base/nsStackWalk.cpp
xpcom/base/nsStackWalk.h
xpcom/base/nsStackWalkPrivate.h
xpcom/ds/TimeStamp.cpp
xpcom/ds/TimeStamp.h
xpcom/ds/TimeStamp_darwin.cpp
xpcom/ds/TimeStamp_posix.cpp
xpcom/ds/TimeStamp_windows.cpp
xpcom/ds/TimeStamp_windows.h
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1167064: Switch to bluetooth APIv2.
+Bug 1151175 - Update libvpx to 1.4.0.
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -683,17 +683,17 @@ getRoleCB(AtkObject *aAtkObj)
   else if (aAtkObj->role == ATK_ROLE_TABLE_ROW && !IsAtkVersionAtLeast(2, 1))
     aAtkObj->role = ATK_ROLE_LIST_ITEM;
   else if (aAtkObj->role == ATK_ROLE_MATH && !IsAtkVersionAtLeast(2, 12))
     aAtkObj->role = ATK_ROLE_PANEL;
   else if (aAtkObj->role == ATK_ROLE_STATIC && !IsAtkVersionAtLeast(2, 16))
     aAtkObj->role = ATK_ROLE_TEXT;
   else if ((aAtkObj->role == ATK_ROLE_MATH_FRACTION ||
             aAtkObj->role == ATK_ROLE_MATH_ROOT) && !IsAtkVersionAtLeast(2, 16))
-    aAtkObj->role = ATK_ROLE_UNKNOWN;
+    aAtkObj->role = ATK_ROLE_SECTION;
 
   return aAtkObj->role;
 }
 
 static AtkAttributeSet*
 ConvertToAtkAttributeSet(nsIPersistentProperties* aAttributes)
 {
     if (!aAttributes)
--- a/accessible/base/MarkupMap.h
+++ b/accessible/base/MarkupMap.h
@@ -232,28 +232,16 @@ MARKUPMAP(maction_,
           roles::MATHML_ACTION,
           AttrFromDOM(actiontype_, actiontype_),
           AttrFromDOM(selection_, selection_))
 
 MARKUPMAP(merror_,
           New_HyperText,
           roles::MATHML_ERROR)
 
-MARKUPMAP(semantics_,
-          New_HyperText,
-          roles::MATHML_SEMANTICS)
-
-MARKUPMAP(annotation_,
-          New_HyperText,
-          roles::MATHML_ANNOTATION)
-
-MARKUPMAP(annotation_xml_,
-          New_HyperText,
-          roles::MATHML_XML_ANNOTATION)
-
 MARKUPMAP(mstack_,
           New_HyperText,
           roles::MATHML_STACK,
           AttrFromDOM(align, align),
           AttrFromDOM(position, position))
 
 MARKUPMAP(mlongdiv_,
           New_HyperText,
--- a/accessible/base/Role.h
+++ b/accessible/base/Role.h
@@ -922,75 +922,60 @@ enum Role {
   MATHML_ACTION = 156,
 
   /**
    * A MathML error message (merror in MathML).
    */
   MATHML_ERROR = 157,
 
   /**
-   * A MathML semantics annotation element (semantics in MathML).
-   */
-  MATHML_SEMANTICS = 158,
-
-  /**
-   * A MathML annotation (annotation in MathML).
-   */
-  MATHML_ANNOTATION = 159,
-
-  /**
-   * A MathML XML annotation (annotation-xml in MathML).
-   */
-  MATHML_XML_ANNOTATION = 160,
-
-  /**
    * A MathML stacked (rows of numbers) element (mstack in MathML).
    */
-  MATHML_STACK = 161,
+  MATHML_STACK = 158,
 
   /**
    * A MathML long division element (mlongdiv in MathML).
    */
-  MATHML_LONG_DIVISION = 162,
+  MATHML_LONG_DIVISION = 159,
 
   /**
    * A MathML stack group (msgroup in MathML).
    */
-  MATHML_STACK_GROUP = 163,
+  MATHML_STACK_GROUP = 160,
 
   /**
    * A MathML stack row (msrow in MathML).
    */
-  MATHML_STACK_ROW = 164,
+  MATHML_STACK_ROW = 161,
 
   /**
    * MathML carries, borrows, or crossouts for a row (mscarries in MathML).
    */
-  MATHML_STACK_CARRIES = 165,
+  MATHML_STACK_CARRIES = 162,
 
   /**
    * A MathML carry, borrow, or crossout for a column (mscarry in MathML).
    */
-  MATHML_STACK_CARRY = 166,
+  MATHML_STACK_CARRY = 163,
 
   /**
    * A MathML line in a stack (msline in MathML).
    */
-  MATHML_STACK_LINE = 167,
+  MATHML_STACK_LINE = 164,
 
   /**
    * A group containing radio buttons
    */
-  RADIO_GROUP = 168,
+  RADIO_GROUP = 165,
 
   /**
    * A text container exposing brief amount of information. See related
    * TEXT_CONTAINER role.
    */
-  TEXT = 169,
+  TEXT = 166,
 
   LAST_ROLE = TEXT
 };
 
 } // namespace role
 
 typedef enum mozilla::a11y::roles::Role role;
 
--- a/accessible/base/RoleMap.h
+++ b/accessible/base/RoleMap.h
@@ -1275,40 +1275,16 @@ ROLE(MATHML_ACTION,
 ROLE(MATHML_ERROR,
      "mathml error",
      ATK_ROLE_PANEL,
      NSAccessibilityUnknownRole,
      0,
      IA2_ROLE_UNKNOWN,
      eNoNameRule)
 
-ROLE(MATHML_SEMANTICS,
-     "mathml semantics",
-     ATK_ROLE_UNKNOWN,
-     NSAccessibilityUnknownRole,
-     0,
-     IA2_ROLE_UNKNOWN,
-     eNoNameRule)
-
-ROLE(MATHML_ANNOTATION,
-     "mathml annotation",
-     ATK_ROLE_UNKNOWN,
-     NSAccessibilityUnknownRole,
-     0,
-     IA2_ROLE_UNKNOWN,
-     eNoNameRule)
-
-ROLE(MATHML_XML_ANNOTATION,
-     "mathml xml annotation",
-     ATK_ROLE_UNKNOWN,
-     NSAccessibilityUnknownRole,
-     0,
-     IA2_ROLE_UNKNOWN,
-     eNoNameRule)
-
 ROLE(MATHML_STACK,
      "mathml stack",
      ATK_ROLE_UNKNOWN,
      NSAccessibilityUnknownRole,
      0,
      IA2_ROLE_UNKNOWN,
      eNoNameRule)
 
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -1186,22 +1186,25 @@ nsAccessibilityService::GetOrCreateAcces
 
     } else if (content->IsMathMLElement()) {
       const MarkupMapInfo* markupMap =
         mMarkupMaps.Get(content->NodeInfo()->NameAtom());
       if (markupMap && markupMap->new_func)
         newAcc = markupMap->new_func(content, aContext);
 
       // Fall back to text when encountering Content MathML.
-      if (!newAcc && !content->IsAnyOfMathMLElements(nsGkAtoms::mpadded_,
+      if (!newAcc && !content->IsAnyOfMathMLElements(nsGkAtoms::annotation_,
+                                                     nsGkAtoms::annotation_xml_,
+                                                     nsGkAtoms::mpadded_,
                                                      nsGkAtoms::mphantom_,
                                                      nsGkAtoms::maligngroup_,
                                                      nsGkAtoms::malignmark_,
-                                                     nsGkAtoms::mspace_)) {
-        newAcc = new HyperTextAccessible(content, document);
+                                                     nsGkAtoms::mspace_,
+                                                     nsGkAtoms::semantics_)) {
+       newAcc = new HyperTextAccessible(content, document);
       }
     }
   }
 
   // If no accessible, see if we need to create a generic accessible because
   // of some property that makes this object interesting
   // We don't do this for <body>, <html>, <window>, <dialog> etc. which
   // correspond to the doc accessible and will be created in any case
--- a/accessible/interfaces/nsIAccessibleRole.idl
+++ b/accessible/interfaces/nsIAccessibleRole.idl
@@ -3,17 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 /**
  * Defines cross platform (Gecko) roles.
  */
-[scriptable, uuid(94add87a-190c-443e-9549-d11131affb2a)]
+[scriptable, uuid(da0c7824-147c-11e5-917c-60a44c717042)]
 interface nsIAccessibleRole : nsISupports
 {
   /**
    * Used when accessible hans't strong defined role.
    */
   const unsigned long ROLE_NOTHING = 0;
 
   /**
@@ -916,68 +916,53 @@ interface nsIAccessibleRole : nsISupport
   const unsigned long ROLE_MATHML_ACTION = 156;
 
   /**
    * A MathML error message (merror in MathML).
    */
   const unsigned long ROLE_MATHML_ERROR = 157;
 
   /**
-   * A MathML semantics annotation element (semantics in MathML).
-   */
-  const unsigned long ROLE_MATHML_SEMANTICS = 158;
-
-  /**
-   * A MathML annotation (annotation in MathML).
-   */
-  const unsigned long ROLE_MATHML_ANNOTATION = 159;
-
-  /**
-   * A MathML XML annotation (annotation-xml in MathML).
-   */
-  const unsigned long ROLE_MATHML_XML_ANNOTATION = 160;
-
-  /**
    * A MathML stacked (rows of numbers) element (mstack in MathML).
    */
-  const unsigned long ROLE_MATHML_STACK = 161;
+  const unsigned long ROLE_MATHML_STACK = 158;
 
   /**
    * A MathML long division element (mlongdiv in MathML).
    */
-  const unsigned long ROLE_MATHML_LONG_DIVISION = 162;
+  const unsigned long ROLE_MATHML_LONG_DIVISION = 159;
 
   /**
    * A MathML stack group (msgroup in MathML).
    */
-  const unsigned long ROLE_MATHML_STACK_GROUP = 163;
+  const unsigned long ROLE_MATHML_STACK_GROUP = 160;
 
   /**
    * A MathML stack row (msrow in MathML).
    */
-  const unsigned long ROLE_MATHML_STACK_ROW = 164;
+  const unsigned long ROLE_MATHML_STACK_ROW = 161;
 
   /**
    * MathML carries, borrows, or crossouts for a row (mscarries in MathML).
    */
-  const unsigned long ROLE_MATHML_STACK_CARRIES = 165;
+  const unsigned long ROLE_MATHML_STACK_CARRIES = 162;
 
   /**
    * A MathML carry, borrow, or crossout for a column (mscarry in MathML).
    */
-  const unsigned long ROLE_MATHML_STACK_CARRY = 166;
+  const unsigned long ROLE_MATHML_STACK_CARRY = 163;
 
   /**
    * A MathML line in a stack (msline in MathML).
    */
-  const unsigned long ROLE_MATHML_STACK_LINE = 167;
+  const unsigned long ROLE_MATHML_STACK_LINE = 164;
 
   /**
    * A group containing radio buttons
    */
-  const unsigned long ROLE_RADIO_GROUP = 168;
+  const unsigned long ROLE_RADIO_GROUP = 165;
 
   /**
    * A text container exposing brief amount of information. See related
    * TEXT_CONTAINER role.
    */
-  const unsigned long ROLE_TEXT = 169;
+  const unsigned long ROLE_TEXT = 166;
 };
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -511,17 +511,17 @@ GetClosestInterestingAccessible(id anObj
   }
 
   return nil;
 }
 
 struct RoleDescrMap
 {
   NSString* role;
-  const nsString& description;
+  const nsString description;
 };
 
 static const RoleDescrMap sRoleDescrMap[] = {
   { @"AXDefinition", NS_LITERAL_STRING("definition") },
   { @"AXLandmarkBanner", NS_LITERAL_STRING("banner") },
   { @"AXLandmarkComplementary", NS_LITERAL_STRING("complementary") },
   { @"AXLandmarkContentInfo", NS_LITERAL_STRING("content") },
   { @"AXLandmarkMain", NS_LITERAL_STRING("main") },
--- a/accessible/tests/mochitest/elm/test_MathMLSpec.html
+++ b/accessible/tests/mochitest/elm/test_MathMLSpec.html
@@ -262,38 +262,20 @@
       // merror
 
       obj = {
         role: ROLE_MATHML_ERROR,
       };
       testElm("merror", obj);
 
       //////////////////////////////////////////////////////////////////////////
-      // semantics
-
-      obj = {
-        role: ROLE_MATHML_SEMANTICS,
-      };
-      testElm("semantics", obj);
-
-      //////////////////////////////////////////////////////////////////////////
-      // annotation
-
-      obj = {
-        role: ROLE_MATHML_ANNOTATION,
-      };
-      testElm("annotation", obj);
-
-      //////////////////////////////////////////////////////////////////////////
-      // annotation-xml
-
-      obj = {
-        role: ROLE_MATHML_XML_ANNOTATION,
-      };
-      testElm("annotation-xml", obj);
+      // semantics, annotation, annotation-xml
+      ok(!isAccessible("semantics"), "semantics should not have accessible");
+      ok(!isAccessible("annotation"), "annotation should not have accessible");
+      ok(!isAccessible("annotation-xml"), "annotation-xml should not have accessible");
 
       //////////////////////////////////////////////////////////////////////////
       // mstack
 
       obj = {
         role: ROLE_MATHML_STACK,
         attributes: { align: "center" }
       };
--- a/accessible/tests/mochitest/role.js
+++ b/accessible/tests/mochitest/role.js
@@ -63,19 +63,16 @@ const ROLE_MATHML_OVER = nsIAccessibleRo
 const ROLE_MATHML_UNDER_OVER = nsIAccessibleRole.ROLE_MATHML_UNDER_OVER;
 const ROLE_MATHML_MULTISCRIPTS = nsIAccessibleRole.ROLE_MATHML_MULTISCRIPTS;
 const ROLE_MATHML_TABLE = nsIAccessibleRole.ROLE_MATHML_TABLE;
 const ROLE_MATHML_LABELED_ROW = nsIAccessibleRole.ROLE_MATHML_LABELED_ROW;
 const ROLE_MATHML_TABLE_ROW = nsIAccessibleRole.ROLE_MATHML_TABLE_ROW;
 const ROLE_MATHML_CELL = nsIAccessibleRole.ROLE_MATHML_CELL;
 const ROLE_MATHML_ACTION = nsIAccessibleRole.ROLE_MATHML_ACTION;
 const ROLE_MATHML_ERROR = nsIAccessibleRole.ROLE_MATHML_ERROR;
-const ROLE_MATHML_SEMANTICS = nsIAccessibleRole.ROLE_MATHML_SEMANTICS;
-const ROLE_MATHML_ANNOTATION = nsIAccessibleRole.ROLE_MATHML_ANNOTATION;
-const ROLE_MATHML_XML_ANNOTATION = nsIAccessibleRole.ROLE_MATHML_XML_ANNOTATION;
 const ROLE_MATHML_STACK = nsIAccessibleRole.ROLE_MATHML_STACK;
 const ROLE_MATHML_LONG_DIVISION = nsIAccessibleRole.ROLE_MATHML_LONG_DIVISION;
 const ROLE_MATHML_STACK_GROUP = nsIAccessibleRole.ROLE_MATHML_STACK_GROUP;
 const ROLE_MATHML_STACK_ROW = nsIAccessibleRole.ROLE_MATHML_STACK_ROW;
 const ROLE_MATHML_STACK_CARRIES = nsIAccessibleRole.ROLE_MATHML_STACK_CARRIES;
 const ROLE_MATHML_STACK_CARRY = nsIAccessibleRole.ROLE_MATHML_STACK_CARRY;
 const ROLE_MATHML_STACK_LINE = nsIAccessibleRole.ROLE_MATHML_STACK_LINE;
 const ROLE_MENUBAR = nsIAccessibleRole.ROLE_MENUBAR;
--- a/addon-sdk/source/lib/sdk/io/buffer.js
+++ b/addon-sdk/source/lib/sdk/io/buffer.js
@@ -242,21 +242,21 @@ Object.defineProperties(Buffer.prototype
 
       return buffer;
     }
   },
   write: {
     value: function(string, offset, length, encoding = 'utf8') {
       // write(string, encoding);
       if (typeof(offset) === 'string' && Number.isNaN(parseInt(offset))) {
-        ([offset, length, encoding]) = [0, null, offset];
+        [offset, length, encoding] = [0, null, offset];
       }
       // write(string, offset, encoding);
       else if (typeof(length) === 'string')
-        ([length, encoding]) = [null, length];
+        [length, encoding] = [null, length];
 
       if (offset < 0 || offset > this.length)
         throw new RangeError('offset is outside of valid range');
 
       offset = ~~offset;
 
       // Clamp length if it would overflow buffer, or if its
       // undefined
--- a/addon-sdk/source/lib/sdk/panel/utils.js
+++ b/addon-sdk/source/lib/sdk/panel/utils.js
@@ -124,33 +124,33 @@ function display(panel, options, anchor)
 
   if (!anchor) {
     // The XUL Panel doesn't have an arrow, so the margin needs to be reset
     // in order to, be positioned properly
     panel.style.margin = "0";
 
     let viewportRect = document.defaultView.gBrowser.getBoundingClientRect();
 
-    ({x, y, width, height}) = calculateRegion(options, viewportRect);
+    ({x, y, width, height} = calculateRegion(options, viewportRect));
   }
   else {
     // The XUL Panel has an arrow, so the margin needs to be reset
     // to the default value.
     panel.style.margin = "";
     let { CustomizableUI, window } = anchor.ownerDocument.defaultView;
 
     // In Australis, widgets may be positioned in an overflow panel or the
     // menu panel.
     // In such cases clicking this widget will hide the overflow/menu panel,
     // and the widget's panel will show instead.
     // If `CustomizableUI` is not available, it means the anchor is not in a
     // chrome browser window, and therefore there is no need for this check.
     if (CustomizableUI) {
       let node = anchor;
-      ({anchor}) = CustomizableUI.getWidget(anchor.id).forWindow(window);
+      ({anchor} = CustomizableUI.getWidget(anchor.id).forWindow(window));
 
       // if `node` is not the `anchor` itself, it means the widget is
       // positioned in a panel, therefore we have to hide it before show
       // the widget's panel in the same anchor
       if (node !== anchor)
         CustomizableUI.hidePanelForNode(anchor);
     }
 
--- a/addon-sdk/source/lib/sdk/util/sequence.js
+++ b/addon-sdk/source/lib/sdk/util/sequence.js
@@ -240,17 +240,17 @@ const map = (f, ...sequences) => seq(fun
     // Run loop yielding of applying `f` to the set of
     // items at each step until one of the `inputs` is
     // exhausted.
     let done = false;
     while (!done) {
       let index = 0;
       let value = void(0);
       while (index < count && !done) {
-        ({ done, value }) = inputs[index].next();
+        ({ done, value } = inputs[index].next());
 
         // If input is not exhausted yet store value in args.
         if (!done) {
           args[index] = value;
           index = index + 1;
         }
       }
 
@@ -268,20 +268,20 @@ exports.map = map;
 //
 // Implements clojure reductions:
 // http://clojuredocs.org/clojure_core/clojure.core/reductions
 const reductions = (...params) => {
   const count = params.length;
   let hasInitial = false;
   let f, initial, source;
   if (count === 2) {
-    ([f, source]) = params;
+    [f, source] = params;
   }
   else if (count === 3) {
-    ([f, initial, source]) = params;
+    [f, initial, source] = params;
     hasInitial = true;
   }
   else {
     throw Error("Invoked with wrong number of arguments: " + count);
   }
 
   const sequence = seq(source);
 
--- a/addon-sdk/source/python-lib/cuddlefish/prefs.py
+++ b/addon-sdk/source/python-lib/cuddlefish/prefs.py
@@ -55,17 +55,16 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
     'media.gmp-manager.url.override': 'http://localhost/dummy-gmp-manager.xml',
     'browser.aboutHomeSnippets.updateUrl': 'https://localhost/snippet-dummy',
     'browser.newtab.url' : 'about:blank',
     'browser.search.update': False,
     'browser.search.suggest.enabled' : False,
     'browser.safebrowsing.enabled' : False,
     'browser.safebrowsing.updateURL': 'http://localhost/safebrowsing-dummy/update',
     'browser.safebrowsing.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
-    'browser.safebrowsing.reportURL': 'http://localhost/safebrowsing-dummy/report',
     'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport',
     'browser.selfsupport.url': 'https://localhost/selfsupport-dummy',
     'browser.trackingprotection.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
     'browser.trackingprotection.updateURL': 'http://localhost/safebrowsing-dummy/update',
 
     # Disable app update
     'app.update.enabled' : False,
     'app.update.staging.enabled': False,
--- a/addon-sdk/source/test/preferences/no-connections.json
+++ b/addon-sdk/source/test/preferences/no-connections.json
@@ -12,17 +12,16 @@
   "media.gmp-manager.url.override": "http://localhost/dummy-gmp-manager.xml",
   "browser.aboutHomeSnippets.updateUrl": "https://localhost/snippet-dummy",
   "browser.newtab.url": "about:blank",
   "browser.search.update": false,
   "browser.search.suggest.enabled": false,
   "browser.safebrowsing.enabled": false,
   "browser.safebrowsing.updateURL": "http://localhost/safebrowsing-dummy/update",
   "browser.safebrowsing.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
-  "browser.safebrowsing.reportURL": "http://localhost/safebrowsing-dummy/report",
   "browser.safebrowsing.malware.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
   "browser.selfsupport.url": "https://localhost/selfsupport-dummy",
   "browser.trackingprotection.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
   "browser.trackingprotection.updateURL": "http://localhost/safebrowsing-dummy/update",
   "browser.newtabpage.directory.source": "data:application/json,{'jetpack':1}",
   "browser.newtabpage.directory.ping": "",
   "extensions.update.url": "http://localhost/extensions-dummy/updateURL",
   "extensions.update.background.url": "http://localhost/extensions-dummy/updateBackgroundURL",
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -217,16 +217,20 @@ pref("content.sink.perf_parse_time", 500
 pref("dom.use_watchdog", false);
 
 // The slow script dialog can be triggered from inside the JS engine as well,
 // ensure that those calls don't accidentally trigger the dialog.
 pref("dom.max_script_run_time", 0);
 pref("dom.max_chrome_script_run_time", 0);
 pref("dom.max_child_script_run_time", 0);
 
+// Temporarily disable support for offsetX/Y to work around Google Maps bug
+// (bug 1150284)
+pref("dom.mouseEvent.offsetXY.enabled", false);
+
 // plugins
 pref("plugin.disable", true);
 pref("dom.ipc.plugins.enabled", true);
 
 // product URLs
 // The breakpad report server to link to in about:crashes
 pref("breakpad.reportURL", "https://crash-stats.mozilla.com/report/index/");
 pref("app.releaseNotesURL", "http://www.mozilla.com/%LOCALE%/b2g/%VERSION%/releasenotes/");
@@ -345,32 +349,34 @@ pref("image.onload.decode.limit", 24); /
 
 // XXX this isn't a good check for "are touch events supported", but
 // we don't really have a better one at the moment.
 // enable touch events interfaces
 pref("dom.w3c_touch_events.enabled", 1);
 pref("dom.w3c_touch_events.safetyX", 0); // escape borders in units of 1/240"
 pref("dom.w3c_touch_events.safetyY", 120); // escape borders in units of 1/240"
 
+// W3C draft pointer events
+pref("dom.w3c_pointer_events.enabled", false);
+// W3C touch-action css property (related to touch and pointer events)
+pref("layout.css.touch_action.enabled", false);
+
 #ifdef MOZ_SAFE_BROWSING
 // Safe browsing does nothing unless this pref is set
-pref("browser.safebrowsing.enabled", false);
+pref("browser.safebrowsing.enabled", true);
 
 // Prevent loading of pages identified as malware
-pref("browser.safebrowsing.malware.enabled", false);
+pref("browser.safebrowsing.malware.enabled", true);
 
 pref("browser.safebrowsing.debug", false);
 pref("browser.safebrowsing.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
 pref("browser.safebrowsing.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
-pref("browser.safebrowsing.reportURL", "https://safebrowsing.google.com/safebrowsing/report?");
-pref("browser.safebrowsing.reportGenericURL", "http://%LOCALE%.phish-generic.mozilla.com/?hl=%LOCALE%");
-pref("browser.safebrowsing.reportErrorURL", "http://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%");
-pref("browser.safebrowsing.reportPhishURL", "http://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%");
-pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mozilla.com/?hl=%LOCALE%");
-pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
+pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
 
 pref("browser.safebrowsing.id", "Firefox");
 
 // Tables for application reputation.
 pref("urlclassifier.downloadBlockTable", "goog-badbinurl-shavar");
 
 // Non-enhanced mode (local url lists) URL list to check for updates
@@ -400,16 +406,21 @@ pref("urlclassifier.gethash.timeout_ms",
 
 // If an urlclassifier table has not been updated in this number of seconds,
 // a gethash request will be forced to check that the result is still in
 // the database.
 pref("urlclassifier.max-complete-age", 2700);
 
 // URL for checking the reason for a malware warning.
 pref("browser.safebrowsing.malware.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
+
+// Tracking protection
+pref("privacy.trackingprotection.enabled", true);
+pref("privacy.trackingprotection.pbmode.enabled", false);
+
 #endif
 
 // True if this is the first time we are showing about:firstrun
 pref("browser.firstrun.show.uidiscovery", true);
 pref("browser.firstrun.show.localepicker", true);
 
 // initiated by a user
 pref("content.ime.strict_policy", true);
@@ -709,17 +720,17 @@ pref("dom.ipc.processPriorityManager.ena
 pref("dom.ipc.processPriorityManager.backgroundGracePeriodMS", 1000);
 pref("dom.ipc.processPriorityManager.backgroundPerceivableGracePeriodMS", 5000);
 pref("dom.ipc.processPriorityManager.temporaryPriorityLockMS", 5000);
 
 // Number of different background/foreground levels for background/foreground
 // processes.  We use these different levels to force the low-memory killer to
 // kill processes in a LRU order.
 pref("dom.ipc.processPriorityManager.BACKGROUND.LRUPoolLevels", 5);
-pref("dom.ipc.processPriorityManager.FOREGROUND.LRUPoolLevels", 3);
+pref("dom.ipc.processPriorityManager.BACKGROUND_PERCEIVABLE.LRUPoolLevels", 4);
 
 // Kernel parameters for process priorities.  These affect how processes are
 // killed on low-memory and their relative CPU priorities.
 //
 // The kernel can only accept 6 (OomScoreAdjust, KillUnderKB) pairs. But it is
 // okay, kernel will still kill processes with larger OomScoreAdjust first even
 // its OomScoreAdjust don't have a corresponding KillUnderKB.
 
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -323,16 +323,28 @@ var shell = {
     this.contentBrowser = container.appendChild(systemAppFrame);
 
     systemAppFrame.contentWindow
                   .QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIWebNavigation)
                   .sessionHistory = Cc["@mozilla.org/browser/shistory;1"]
                                       .createInstance(Ci.nsISHistory);
 
+    this.allowedAudioChannels = new Map();
+    let audioChannels = systemAppFrame.allowedAudioChannels;
+    audioChannels && audioChannels.forEach(function(audioChannel) {
+      this.allowedAudioChannels.set(audioChannel.name, audioChannel);
+      audioChannel.addEventListener('activestatechanged', this);
+      // Set all audio channels as unmuted by default
+      // because some audio in System app will be played
+      // before AudioChannelService[1] is Gaia is loaded.
+      // [1]: https://github.com/mozilla-b2g/gaia/blob/master/apps/system/js/audio_channel_service.js
+      audioChannel.setMuted(false);
+    }.bind(this));
+
     // On firefox mulet, shell.html is loaded in a tab
     // and we have to listen on the chrome event handler
     // to catch key events
     let chromeEventHandler = window.QueryInterface(Ci.nsIInterfaceRequestor)
                                    .getInterface(Ci.nsIWebNavigation)
                                    .QueryInterface(Ci.nsIDocShell)
                                    .chromeEventHandler || window;
     // Capture all key events so we can filter out hardware buttons
@@ -556,16 +568,28 @@ var shell = {
         window.removeEventListener('MozAfterPaint', this);
         this.sendChromeEvent({
           type: 'system-first-paint'
         });
         break;
       case 'unload':
         this.stop();
         break;
+      case 'activestatechanged':
+        var channel = evt.target;
+        // TODO: We should get the `isActive` state from evt.isActive.
+        // Then we don't need to do `channel.isActive()` here.
+        channel.isActive().onsuccess = function(evt) {
+          this.sendChromeEvent({
+            type: 'system-audiochannel-state-changed',
+            name: channel.name,
+            isActive: evt.target.result
+          });
+        }.bind(this);
+        break;
     }
   },
 
   // Send an event to a specific window, document or element.
   sendEvent: function shell_sendEvent(target, type, details) {
     let doc = target.document || target.ownerDocument || target;
     let event = doc.createEvent('CustomEvent');
     event.initCustomEvent(type, true, true, details ? details : {});
@@ -734,16 +758,21 @@ var CustomEventManager = {
       case 'captive-portal-login-cancel':
         CaptivePortalLoginHelper.handleEvent(detail);
         break;
       case 'inputmethod-update-layouts':
       case 'inputregistry-add':
       case 'inputregistry-remove':
         KeyboardHelper.handleEvent(detail);
         break;
+      case 'system-audiochannel-list':
+      case 'system-audiochannel-mute':
+      case 'system-audiochannel-volume':
+        SystemAppMozBrowserHelper.handleEvent(detail);
+        break;
       case 'do-command':
         DoCommandHelper.handleEvent(detail.cmd);
         break;
       case 'copypaste-do-command':
         Services.obs.notifyObservers({ wrappedJSObject: shell.contentBrowser },
                                      'ask-children-to-execute-copypaste-command', detail.cmd);
         break;
     }
@@ -862,16 +891,73 @@ let KeyboardHelper = {
       case 'inputregistry-remove':
         Keyboard.inputRegistryGlue.returnMessage(detail);
 
         break;
     }
   }
 };
 
+let SystemAppMozBrowserHelper = {
+  handleEvent: function systemAppMozBrowser_handleEvent(detail) {
+    let request;
+    let name;
+    switch (detail.type) {
+      case 'system-audiochannel-list':
+        let audioChannels = [];
+        shell.allowedAudioChannels.forEach(function(value, name) {
+          audioChannels.push(name);
+        });
+        SystemAppProxy._sendCustomEvent('mozSystemWindowChromeEvent', {
+          type: 'system-audiochannel-list',
+          audioChannels: audioChannels
+        });
+        break;
+      case 'system-audiochannel-mute':
+        name = detail.name;
+        let isMuted = detail.isMuted;
+        request = shell.allowedAudioChannels.get(name).setMuted(isMuted);
+        request.onsuccess = function() {
+          SystemAppProxy._sendCustomEvent('mozSystemWindowChromeEvent', {
+            type: 'system-audiochannel-mute-onsuccess',
+            name: name,
+            isMuted: isMuted
+          });
+        };
+        request.onerror = function() {
+          SystemAppProxy._sendCustomEvent('mozSystemWindowChromeEvent', {
+            type: 'system-audiochannel-mute-onerror',
+            name: name,
+            isMuted: isMuted
+          });
+        };
+        break;
+      case 'system-audiochannel-volume':
+        name = detail.name;
+        let volume = detail.volume;
+        request = shell.allowedAudioChannels.get(name).setVolume(volume);
+        request.onsuccess = function() {
+          sSystemAppProxy._sendCustomEvent('mozSystemWindowChromeEvent', {
+            type: 'system-audiochannel-volume-onsuccess',
+            name: name,
+            volume: volume
+          });
+        };
+        request.onerror = function() {
+          SystemAppProxy._sendCustomEvent('mozSystemWindowChromeEvent', {
+            type: 'system-audiochannel-volume-onerror',
+            name: name,
+            volume: volume
+          });
+        };
+        break;
+    }
+  }
+};
+
 // This is the backend for Gaia's screenshot feature.  Gaia requests a
 // screenshot by sending a mozContentEvent with detail.type set to
 // 'take-screenshot'.  Then we take a screenshot and send a
 // mozChromeEvent with detail.type set to 'take-screenshot-success'
 // and detail.file set to the an image/png blob
 window.addEventListener('ContentStart', function ss_onContentStart() {
   let content = shell.contentBrowser.contentWindow;
   content.addEventListener('mozContentEvent', function ss_onMozContentEvent(e) {
--- a/b2g/components/test/unit/test_logcapture_gonk.js
+++ b/b2g/components/test/unit/test_logcapture_gonk.js
@@ -28,17 +28,27 @@ add_test(function test_readLogFile() {
 
   run_next_test();
 });
 
 add_test(function test_readProperties() {
   let propertiesLog = LogCapture.readProperties();
   notEqual(propertiesLog, null, "Properties should not be null");
   notEqual(propertiesLog, undefined, "Properties should not be undefined");
-  equal(propertiesLog["ro.kernel.qemu"], "1", "QEMU property should be 1");
+
+  for (let propertyName in propertiesLog) {
+    equal(typeof(propertiesLog[propertyName]), "string",
+          "Property " + propertyName + " should be a string");
+  }
+
+  equal(propertiesLog["ro.product.locale.language"], "en",
+        "Locale language should be read correctly. See bug 1171577.");
+
+  equal(propertiesLog["ro.product.locale.region"], "US",
+        "Locale region should be read correctly. See bug 1171577.");
 
   run_next_test();
 });
 
 add_test(function test_readAppIni() {
   let appIni = LogCapture.readLogFile("/system/b2g/application.ini");
   verifyLog(appIni);
 
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/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="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="311c4e59936a407e64509f54fecb440d8a78e3c8"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="9515d87a841daaf28f2577e92edf5206070d2d51"/>
   <!-- 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"/>
@@ -141,17 +141,17 @@
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="2a1ded216a91bf62a72b1640cf01ab4998f37028"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="a74adcf8d88320d936daa8d20ce88ca0107fb916"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="9883ea57b0668d8f60dba025d4522dfa69a1fbfa"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="a558dc844bf5144fc38603fd8f4df8d9557052a5"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="57ee1320ed7b4a1a1274d8f3f6c177cd6b9becb2"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="12b1977cc704b35f2e9db2bb423fa405348bc2f3"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="985bf15264d865fe7b9c5b45f61c451cbaafa43d"/>
   <project name="platform/system/core" path="system/core" revision="42839aedcf70bf6bc92a3b7ea4a5cc9bf9aef3f9"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="476b3e194d8582ec8bc9968190d896c10c7b3be7"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
   <project name="platform/system/qcom" path="system/qcom" revision="63e3f6f176caad587d42bba4c16b66d953fb23c2"/>
   <project name="platform/vendor/qcom/copper" path="device/qcom/msm8974" revision="ec7bc1a26610922156d7d412b4d3de6b4adb93da"/>
   <project name="vendor_broadcom_wlan" path="vendor/broadcom/wlan" remote="b2g" revision="114b9491a8a919687da4e22fbd89fab511d6d8d7"/>
   <!-- Shinano specific things -->
   <project name="device-shinano" path="device/sony/shinano" remote="b2g" revision="afb93dac826346fdeaab6f8ce5dd70eaaaec676d"/>
   <!-- Aries specific things -->
   <project name="device-aries" path="device/sony/aries" remote="b2g" revision="2916e2368074b5383c80bf5a0fba3fc83ba310bd"/>
 </manifest>
--- 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="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="311c4e59936a407e64509f54fecb440d8a78e3c8"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="9515d87a841daaf28f2577e92edf5206070d2d51"/>
   <!-- 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,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <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="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="311c4e59936a407e64509f54fecb440d8a78e3c8"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9d0e5057ee5404a31ec1bf76131cb11336a7c3b6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,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="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="311c4e59936a407e64509f54fecb440d8a78e3c8"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="9515d87a841daaf28f2577e92edf5206070d2d51"/>
   <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"/>
@@ -130,12 +130,12 @@
   <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="197cd9492b9fadaa915c5daf36ff557f8f4a8d1c"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="libnfcemu" path="external/libnfcemu" remote="b2g" revision="125ccf9bd5986c7728ea44508b3e1d1185ac028b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c5f8d282efe4a4e8b1e31a37300944e338e60e4f"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9f28c4faea3b2f01db227b2467b08aeba96d9bec"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="476b3e194d8582ec8bc9968190d896c10c7b3be7"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
   <project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
 </manifest>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="311c4e59936a407e64509f54fecb440d8a78e3c8"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="9515d87a841daaf28f2577e92edf5206070d2d51"/>
   <!-- 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"/>
@@ -128,13 +128,13 @@
   <default remote="caf" revision="refs/tags/android-4.4.2_r1" sync-j="4"/>
   <!-- Emulator specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="72ffdf71c68a96309212eb13d63560d66db14c9e"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="f390788a00706c06e5248edfd8d27b365387e84a"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="4bebbe8d92368befc31e8b4a99da2d29cc26bfbc"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="f37bd545063039e30a92f2550ae78c0e6e4e2d08"/>
   <project name="platform_external_wpa_supplicant_8" path="external/wpa_supplicant_8" remote="b2g" revision="0c6a6547cd1fd302fa2b0f6e375654df36bf0ec4"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2b62676f02abb7633aac619e0f92c7fd70216860"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="476b3e194d8582ec8bc9968190d896c10c7b3be7"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
   <project name="platform/development" path="development" revision="5968ff4e13e0d696ad8d972281fc27ae5a12829b"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="0951179277915335251c5e11d242e4e1a8c2236f"/>
   <project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
 </manifest>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/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="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="311c4e59936a407e64509f54fecb440d8a78e3c8"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="9515d87a841daaf28f2577e92edf5206070d2d51"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="8af5ff6f5dced9eb5a8127459df6c75d24342204"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="30915518fa7ea07166efedc191a4f40aef516fe7"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="96eee58e3389fb05a835310d6a06a6ba4486097a"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="7c8a46698171aa2e0be09edb43d15a6acf832770"/>
   <project groups="pdk,linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" revision="24b2038be8a636fd4a5d21f0abae1e466b07bcf7"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <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="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="311c4e59936a407e64509f54fecb440d8a78e3c8"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9d0e5057ee5404a31ec1bf76131cb11336a7c3b6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,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="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="311c4e59936a407e64509f54fecb440d8a78e3c8"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="9515d87a841daaf28f2577e92edf5206070d2d51"/>
   <!-- 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"/>
@@ -141,13 +141,13 @@
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="2a1ded216a91bf62a72b1640cf01ab4998f37028"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="a74adcf8d88320d936daa8d20ce88ca0107fb916"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="9883ea57b0668d8f60dba025d4522dfa69a1fbfa"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="a558dc844bf5144fc38603fd8f4df8d9557052a5"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="57ee1320ed7b4a1a1274d8f3f6c177cd6b9becb2"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="12b1977cc704b35f2e9db2bb423fa405348bc2f3"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="985bf15264d865fe7b9c5b45f61c451cbaafa43d"/>
   <project name="platform/system/core" path="system/core" revision="42839aedcf70bf6bc92a3b7ea4a5cc9bf9aef3f9"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="476b3e194d8582ec8bc9968190d896c10c7b3be7"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
   <project name="platform/system/qcom" path="system/qcom" revision="63e3f6f176caad587d42bba4c16b66d953fb23c2"/>
   <project name="platform/vendor/qcom-opensource/wlan/prima" path="vendor/qcom/opensource/wlan/prima" revision="d8952a42771045fca73ec600e2b42a4c7129d723"/>
   <project name="platform/vendor/qcom/msm8610" path="device/qcom/msm8610" revision="4c187c1f3a0dffd8e51a961735474ea703535b99"/>
 </manifest>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "c92e9d14022f0191f8ccdaadf1752352cde97be0", 
+        "git_revision": "311c4e59936a407e64509f54fecb440d8a78e3c8", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "d9efff50aff6c261c55cfdff66e4fedd81250e0b", 
+    "revision": "7e585532a2935425fcddb964cb9002b18b94b3ff", 
     "repo_path": "integration/gaia-central"
 }
--- 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="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="311c4e59936a407e64509f54fecb440d8a78e3c8"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="9515d87a841daaf28f2577e92edf5206070d2d51"/>
   <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"/>
@@ -125,17 +125,17 @@
   <project name="platform/system/netd" path="system/netd" revision="56112dd7b811301b718d0643a82fd5cac9522073"/>
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
   <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/>
   <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/>
   <!-- Nexus 4 specific things -->
   <project name="device-mako" path="device/lge/mako" remote="b2g" revision="78d17f0c117f0c66dd55ee8d5c5dde8ccc93ecba"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device/lge/mako-kernel" path="device/lge/mako-kernel" revision="d1729e53d71d711c8fde25eab8728ff2b9b4df0e"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="476b3e194d8582ec8bc9968190d896c10c7b3be7"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="0e1929fa3aa38bf9d40e9e953d619fab8164c82e"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="b0a528d839cfd9d170d092fe3743b5252b4243a6"/>
   <project name="platform/hardware/qcom/bt" path="hardware/qcom/bt" revision="380945eaa249a2dbdde0daa4c8adb8ca325edba6"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="6f3b0272cefaffeaed2a7d2bb8f633059f163ddc"/>
   <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="16da8262c997a5a0d797885788a64a0771b26910"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="689b476ba3eb46c34b81343295fe144a0e81a18e"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/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="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="311c4e59936a407e64509f54fecb440d8a78e3c8"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="9515d87a841daaf28f2577e92edf5206070d2d51"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="8af5ff6f5dced9eb5a8127459df6c75d24342204"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="30915518fa7ea07166efedc191a4f40aef516fe7"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="96eee58e3389fb05a835310d6a06a6ba4486097a"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="7c8a46698171aa2e0be09edb43d15a6acf832770"/>
   <project groups="pdk,linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" revision="24b2038be8a636fd4a5d21f0abae1e466b07bcf7"/>
@@ -151,10 +151,10 @@
   <project name="hardware_qcom_display" path="hardware/qcom/display" remote="b2g" revision="d8a56d7215bd26a61e43dcde20e64826a5fec265"/>
   <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="eaede9f8bc206736a889bc57817047c31e205589"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="12364db20d6710f0003a4f00962c9790ad3c13e3"/>
   <project name="platform/hardware/qcom/msm8x74" path="hardware/qcom/msm8x74" revision="f09920b2b488cf68bcbe9f7bbbde84a509a1d7a5"/>
   <project name="platform/hardware/qcom/power" path="hardware/qcom/power" revision="29e9aeaf278b16fea4cb1ab4d05b8b8c9083c15b"/>
   <project name="platform/hardware/qcom/sensors" path="hardware/qcom/sensors" revision="fde83fdf67e9b919f8a49008725bd595221bf33f"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="6417804bea95f6e46094a01a06025a86e28c5b0d"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="e00d716e7e3d31729f75399855b6921e90cb0b66"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="476b3e194d8582ec8bc9968190d896c10c7b3be7"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
 </manifest>
--- a/b2g/dev/app/mulet.js
+++ b/b2g/dev/app/mulet.js
@@ -13,8 +13,13 @@ pref("browser.sessionstore.resume_from_c
 pref("devtools.toolbox.host", "side");
 pref("devtools.toolbox.sidebar.width", 800);
 
 // Disable e10s as we don't want to run shell.html,
 // nor the system app OOP, but only inner apps
 pref("browser.tabs.remote.autostart", false);
 pref("browser.tabs.remote.autostart.1", false);
 pref("browser.tabs.remote.autostart.2", false);
+
+// W3C draft pointer events
+pref("dom.w3c_pointer_events.enabled", false);
+// W3C touch-action css property (related to touch and pointer events)
+pref("layout.css.touch_action.enabled", false);
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1433264296000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1433888926000">
   <emItems>
       <emItem  blockID="i58" id="webmaster@buzzzzvideos.info">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i71" id="youtube@2youtube.com">
@@ -156,16 +156,25 @@
       <emItem  blockID="i800" id="{424b0d11-e7fe-4a04-b7df-8f2c77f58aaf}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
                   <pref>browser.startup.homepage</pref>
                   <pref>browser.search.defaultenginename</pref>
               </prefs>
     </emItem>
+      <emItem  blockID="i926" id="{B1FC07E1-E05B-4567-8891-E63FBE545BA8}">
+                        <versionRange  minVersion="0" maxVersion="1.2.0" severity="1">
+                      <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+                              <versionRange  minVersion="39.0a1" maxVersion="*" />
+                          </targetApplication>
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i62" id="jid0-EcdqvFOgWLKHNJPuqAnawlykCGZ@jetpack">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i624" id="/^({b95faac1-a3d7-4d69-8943-ddd5a487d966}|{ecce0073-a837-45a2-95b9-600420505f7e}|{2713b394-286f-4d7c-89ea-4174eeab9f5a}|{da7a20cf-bef4-4342-ad78-0240fdf87055})$/">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
@@ -761,16 +770,25 @@
               </prefs>
     </emItem>
       <emItem  blockID="i596" id="{b99c8534-7800-48fa-bd71-519a46cdc7e1}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i924" id="{DAC3F861-B30D-40dd-9166-F4E75327FAC7}">
+                        <versionRange  minVersion="0" maxVersion="1.3.1" severity="1">
+                      <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+                              <versionRange  minVersion="39.0a1" maxVersion="*" />
+                          </targetApplication>
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i740" id="ascsurfingprotection@iobit.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i360" id="ytd@mybrowserbar.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
@@ -971,16 +989,25 @@
               </prefs>
     </emItem>
       <emItem  blockID="i816" id="noOpus@outlook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i922" id="{34712C68-7391-4c47-94F3-8F88D49AD632}">
+                        <versionRange  minVersion="0" maxVersion="1.3.0" severity="1">
+                      <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+                              <versionRange  minVersion="39.0a1" maxVersion="*" />
+                          </targetApplication>
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i528" id="008abed2-b43a-46c9-9a5b-a771c87b82da@1ad61d53-2bdc-4484-a26b-b888ecae1906.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i540" id="/^(ffxtlbr@mixidj\.com|{c0c2693d-2ee8-47b4-9df7-b67a0ee31988}|{67097627-fd8e-4f6b-af4b-ecb65e50112e}|{f6f0f973-a4a3-48cf-9a7a-b7a69c30d71a}|{a3d0e35f-f1da-4ccb-ae77-e9d27777e68d}|{1122b43d-30ee-403f-9bfa-3cc99b0caddd})$/">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
@@ -1690,16 +1717,25 @@
               </prefs>
     </emItem>
       <emItem  blockID="i523" id="/^({7e8a1050-cf67-4575-92df-dcc60e7d952d}|{b3420a9c-a397-4409-b90d-bcf22da1a08a}|{eca6641f-2176-42ba-bdbe-f3e327f8e0af}|{707dca12-3f99-4d94-afea-06dcc0ae0108}|{aea20431-87fc-40be-bc5b-18066fe2819c}|{30ee6676-1ba6-455a-a7e8-298fa863a546})$/">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i920" id="{FCE04E1F-9378-4f39-96F6-5689A9159E45}">
+                        <versionRange  minVersion="0" maxVersion="1.3.2" severity="1">
+                      <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+                              <versionRange  minVersion="39.0a1" maxVersion="*" />
+                          </targetApplication>
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i656" id="hdv@vovcacik.addons.mozilla.org">
                         <versionRange  minVersion="102.0" maxVersion="102.0" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i503" id="{9CE11043-9A15-4207-A565-0C94C42D590D}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -266,16 +266,17 @@ pref("general.autoScroll", false);
 #else
 pref("general.autoScroll", true);
 #endif
 
 // At startup, check if we're the default browser and prompt user if not.
 pref("browser.shell.checkDefaultBrowser", true);
 pref("browser.shell.shortcutFavicons",true);
 pref("browser.shell.mostRecentDateSetAsDefault", "");
+pref("browser.shell.windows10DefaultBrowserABTest", -1);
 
 // 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session
 // The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore
 pref("browser.startup.page",                1);
 pref("browser.startup.homepage",            "chrome://branding/locale/browserconfig.properties");
 
 pref("browser.slowStartup.notificationDisabled", false);
 pref("browser.slowStartup.timeThreshold", 40000);
@@ -291,17 +292,17 @@ pref("browser.casting.enabled", false);
 pref("browser.chrome.site_icons", true);
 pref("browser.chrome.favicons", true);
 // browser.warnOnQuit == false will override all other possible prompts when quitting or restarting
 pref("browser.warnOnQuit", true);
 // browser.showQuitWarning specifically controls the quit warning dialog. We
 // might still show the window closing dialog with showQuitWarning == false.
 pref("browser.showQuitWarning", false);
 pref("browser.fullscreen.autohide", true);
-pref("browser.fullscreen.animateUp", 1);
+pref("browser.fullscreen.animate", true);
 pref("browser.overlink-delay", 80);
 
 #ifdef UNIX_BUT_NOT_MAC
 pref("browser.urlbar.clickSelectsAll", false);
 #else
 pref("browser.urlbar.clickSelectsAll", true);
 #endif
 #ifdef UNIX_BUT_NOT_MAC
@@ -500,20 +501,27 @@ pref("general.warnOnAboutConfig",       
 pref("dom.disable_window_open_feature.location",  true);
 // prevent JS from setting status messages
 pref("dom.disable_window_status_change",          true);
 // allow JS to move and resize existing windows
 pref("dom.disable_window_move_resize",            false);
 // prevent JS from monkeying with window focus, etc
 pref("dom.disable_window_flip",                   true);
 
-// Disable touch events on Desktop Firefox by default until they are properly
-// supported (bug 736048)
+// Disable touch events on Desktop Firefox by default
+// until they are properly supported (bug 736048)
 pref("dom.w3c_touch_events.enabled",        0);
 
+#ifdef NIGHTLY_BUILD
+// W3C draft pointer events
+pref("dom.w3c_pointer_events.enabled", true);
+// W3C touch-action css property (related to touch and pointer events)
+pref("layout.css.touch_action.enabled", true);
+#endif
+
 // popups.policy 1=allow,2=reject
 pref("privacy.popups.policy",               1);
 pref("privacy.popups.usecustom",            true);
 pref("privacy.popups.showBrowserMessage",   true);
 
 pref("privacy.item.cookies",                false);
 
 pref("privacy.clearOnShutdown.history",     true);
@@ -616,17 +624,17 @@ pref("mousewheel.with_meta.action", 1); 
 #endif
 pref("mousewheel.with_control.action",3);
 pref("mousewheel.with_win.action", 1);
 
 pref("browser.xul.error_pages.enabled", true);
 pref("browser.xul.error_pages.expert_bad_cert", false);
 
 // If true, network link events will change the value of navigator.onLine
-pref("network.manage-offline-status", false);
+pref("network.manage-offline-status", true);
 
 // We want to make sure mail URLs are handled externally...
 pref("network.protocol-handler.external.mailto", true); // for mail
 pref("network.protocol-handler.external.news", true);   // for news
 pref("network.protocol-handler.external.snews", true);  // for secure news
 pref("network.protocol-handler.external.nntp", true);   // also news
 #ifdef XP_WIN
 pref("network.protocol-handler.external.ms-windows-store", true);
@@ -976,23 +984,19 @@ pref("browser.safebrowsing.enabled", tru
 pref("browser.safebrowsing.malware.enabled", true);
 pref("browser.safebrowsing.downloads.enabled", true);
 pref("browser.safebrowsing.downloads.remote.enabled", true);
 pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
 pref("browser.safebrowsing.debug", false);
 
 pref("browser.safebrowsing.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
 pref("browser.safebrowsing.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
-pref("browser.safebrowsing.reportURL", "https://safebrowsing.google.com/safebrowsing/report?");
-pref("browser.safebrowsing.reportGenericURL", "http://%LOCALE%.phish-generic.mozilla.com/?hl=%LOCALE%");
-pref("browser.safebrowsing.reportErrorURL", "http://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%");
-pref("browser.safebrowsing.reportPhishURL", "http://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%");
-pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mozilla.com/?hl=%LOCALE%");
-pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
-
+pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.malware.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
 
 pref("browser.safebrowsing.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
 
 #ifdef MOZILLA_OFFICIAL
 // Normally the "client ID" sent in updates is appinfo.name, but for
 // official Firefox releases from Mozilla we use a special identifier.
 pref("browser.safebrowsing.id", "navclient-auto-ffox");
@@ -1199,16 +1203,17 @@ pref("security.sandbox.windows.log", fal
 // On windows these levels are:
 // 0 - no sandbox
 // 1 - sandbox with USER_NON_ADMIN access token level
 // 2 - a more strict sandbox, which might cause functionality issues. This now
 //     includes running at low integrity.
 // 3 - the strongest settings we seem to be able to use without breaking
 //     everything, but will probably cause some functionality restrictions
 pref("dom.ipc.plugins.sandbox-level.default", 0);
+pref("dom.ipc.plugins.sandbox-level.flash", 0);
 
 #if defined(MOZ_CONTENT_SANDBOX)
 // This controls the strength of the Windows content process sandbox for testing
 // purposes. This will require a restart.
 // On windows these levels are:
 // 0 - sandbox with USER_NON_ADMIN access token level
 // 1 - level 0 plus low integrity
 // 2 - a policy that we can reasonably call an effective sandbox
@@ -1401,18 +1406,18 @@ pref("devtools.command-button-rulers.ena
 // Inspector preferences
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 // What was the last active sidebar in the inspector
 pref("devtools.inspector.activeSidebar", "ruleview");
 // Enable the markup preview
 pref("devtools.inspector.markupPreview", false);
 pref("devtools.inspector.remote", false);
-// Expand pseudo-elements by default in the rule-view
-pref("devtools.inspector.show_pseudo_elements", true);
+// Collapse pseudo-elements by default in the rule-view
+pref("devtools.inspector.show_pseudo_elements", false);
 // The default size for image preview tooltips in the rule-view/computed-view/markup-view
 pref("devtools.inspector.imagePreviewTooltipSize", 300);
 // Enable user agent style inspection in rule-view
 pref("devtools.inspector.showUserAgentStyles", false);
 // Show all native anonymous content (like controls in <video> tags)
 pref("devtools.inspector.showAllAnonymousContent", false);
 // Enable the MDN docs tooltip
 pref("devtools.inspector.mdnDocsTooltip.enabled", true);
@@ -1433,19 +1438,20 @@ pref("devtools.debugger.remote-host", "l
 pref("devtools.debugger.remote-timeout", 20000);
 pref("devtools.debugger.pause-on-exceptions", false);
 pref("devtools.debugger.ignore-caught-exceptions", true);
 pref("devtools.debugger.source-maps-enabled", true);
 pref("devtools.debugger.pretty-print-enabled", true);
 pref("devtools.debugger.auto-pretty-print", false);
 pref("devtools.debugger.auto-black-box", true);
 pref("devtools.debugger.tracer", false);
+pref("devtools.debugger.workers", false);
 
 // The default Debugger UI settings
-pref("devtools.debugger.ui.panes-sources-width", 200);
+pref("devtools.debugger.ui.panes-workers-and-sources-width", 200);
 pref("devtools.debugger.ui.panes-instruments-width", 300);
 pref("devtools.debugger.ui.panes-visible-on-startup", false);
 pref("devtools.debugger.ui.variables-sorting-enabled", true);
 pref("devtools.debugger.ui.variables-only-enum-visible", false);
 pref("devtools.debugger.ui.variables-searchbox-visible", false);
 
 // Enable the Performance tools
 pref("devtools.performance.enabled", true);
@@ -1461,17 +1467,17 @@ pref("devtools.performance.profiler.samp
 pref("devtools.performance.ui.invert-call-tree", true);
 pref("devtools.performance.ui.invert-flame-graph", false);
 pref("devtools.performance.ui.flatten-tree-recursion", true);
 pref("devtools.performance.ui.show-platform-data", false);
 pref("devtools.performance.ui.show-idle-blocks", true);
 pref("devtools.performance.ui.enable-memory", false);
 pref("devtools.performance.ui.enable-allocations", false);
 pref("devtools.performance.ui.enable-framerate", true);
-pref("devtools.performance.ui.show-jit-optimizations", false);
+pref("devtools.performance.ui.enable-jit-optimizations", false);
 
 // Enable experimental options in the UI only in Nightly
 #if defined(NIGHTLY_BUILD)
 pref("devtools.performance.ui.experimental", true);
 #else
 pref("devtools.performance.ui.experimental", false);
 #endif
 
@@ -1487,22 +1493,24 @@ pref("devtools.netmonitor.enabled", true
 // The default Network Monitor UI settings
 pref("devtools.netmonitor.panes-network-details-width", 550);
 pref("devtools.netmonitor.panes-network-details-height", 450);
 pref("devtools.netmonitor.statistics", true);
 pref("devtools.netmonitor.filters", "[\"all\"]");
 
 // The default Network monitor HAR export setting
 pref("devtools.netmonitor.har.defaultLogDir", "");
-pref("devtools.netmonitor.har.defaultFileName", "archive");
+pref("devtools.netmonitor.har.defaultFileName", "Archive %y-%m-%d %H-%M-%S");
 pref("devtools.netmonitor.har.jsonp", false);
 pref("devtools.netmonitor.har.jsonpCallback", "");
 pref("devtools.netmonitor.har.includeResponseBodies", true);
 pref("devtools.netmonitor.har.compress", false);
 pref("devtools.netmonitor.har.forceExport", false);
+pref("devtools.netmonitor.har.pageLoadedTimeout", 1500);
+pref("devtools.netmonitor.har.enableAutoExportToFile", false);
 
 // Enable the Tilt inspector
 pref("devtools.tilt.enabled", true);
 pref("devtools.tilt.intro_transition", true);
 pref("devtools.tilt.outro_transition", true);
 
 // Scratchpad settings
 // - recentFileMax: The maximum number of recently-opened files
@@ -1828,16 +1836,19 @@ pref("identity.fxaccounts.remote.webchan
 pref("identity.fxaccounts.settings.uri", "https://accounts.firefox.com/settings");
 
 // The remote URL of the FxA Profile Server
 pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
 
 // The remote URL of the FxA OAuth Server
 pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
 
+// Whether we display profile images in the UI or not.
+pref("identity.fxaccounts.profile_image.enabled", true);
+
 // Migrate any existing Firefox Account data from the default profile to the
 // Developer Edition profile.
 #ifdef MOZ_DEV_EDITION
 pref("identity.fxaccounts.migrateToDevEdition", true);
 #else
 pref("identity.fxaccounts.migrateToDevEdition", false);
 #endif
 
@@ -1936,9 +1947,10 @@ pref("browser.pocket.oAuthConsumerKey", 
 pref("browser.pocket.useLocaleList", true);
 pref("browser.pocket.enabledLocales", "en-US de es-ES ja ja-JP-mac ru");
 
 pref("view_source.tab", true);
 
 // Enable Service Workers for desktop on non-release builds
 #ifndef RELEASE_BUILD
 pref("dom.serviceWorkers.enabled", true);
+pref("dom.serviceWorkers.interception.enabled", true);
 #endif
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -1,16 +1,14 @@
 # -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 var FullScreen = {
-  _XULNS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
-
   _MESSAGES: [
     "DOMFullscreen:Request",
     "DOMFullscreen:NewOrigin",
     "DOMFullscreen:Exit",
   ],
 
   init: function() {
     // called when we go into full screen, even if initiated by a web page script
@@ -58,43 +56,46 @@ var FullScreen = {
       this._fullScrToggler = document.getElementById("fullscr-toggler");
       this._fullScrToggler.addEventListener("mouseover", this._expandCallback, false);
       this._fullScrToggler.addEventListener("dragenter", this._expandCallback, false);
     }
 
     if (enterFS) {
       gNavToolbox.setAttribute("inFullscreen", true);
       document.documentElement.setAttribute("inFullscreen", true);
+      if (!document.mozFullScreen && this.useLionFullScreen)
+        document.documentElement.setAttribute("OSXLionFullscreen", true);
     } else {
       gNavToolbox.removeAttribute("inFullscreen");
       document.documentElement.removeAttribute("inFullscreen");
+      document.documentElement.removeAttribute("OSXLionFullscreen");
     }
 
-    // show/hide menubars, toolbars (except the full screen toolbar)
-    // On OS X Lion, we don't want to hide toolbars when entering
-    // fullscreen, unless we're entering DOM fullscreen.
-    if (document.mozFullScreen || !this.useLionFullScreen) {
-      this.showXULChrome("toolbar", !enterFS);
-    }
+    if (!document.mozFullScreen)
+      this._updateToolbars(enterFS);
 
     if (enterFS) {
       document.addEventListener("keypress", this._keyToggleCallback, false);
       document.addEventListener("popupshown", this._setPopupOpen, false);
       document.addEventListener("popuphidden", this._setPopupOpen, false);
-      this._shouldAnimate = true;
-      // We don't animate the toolbar collapse if in DOM full-screen mode,
-      // as the size of the content area would still be changing after the
-      // mozfullscreenchange event fired, which could confuse content script.
-      this.hideNavToolbox(document.mozFullScreen);
+      // In DOM fullscreen mode, we hide toolbars with CSS
+      if (!document.mozFullScreen)
+        this.hideNavToolbox(true);
     }
     else {
       this.showNavToolbox(false);
       // This is needed if they use the context menu to quit fullscreen
       this._isPopupOpen = false;
       this.cleanup();
+      // In TabsInTitlebar._update(), we cancel the appearance update on
+      // resize event for exiting fullscreen, since that happens before we
+      // change the UI here in the "fullscreen" event. Hence we need to
+      // call it here to ensure the appearance is properly updated. See
+      // TabsInTitlebar._update() and bug 1173768.
+      TabsInTitlebar.updateAppearance(true);
     }
   },
 
   exitDomFullScreen : function() {
     document.mozCancelFullScreen();
   },
 
   handleEvent: function (event) {
@@ -197,20 +198,16 @@ var FullScreen = {
     gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen);
     gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen);
 
     // Add listener to detect when the fullscreen window is re-focused.
     // If a fullscreen window loses focus, we show a warning when the
     // fullscreen window is refocused.
     window.addEventListener("activate", this);
 
-    // Cancel any "hide the toolbar" animation which is in progress, and make
-    // the toolbar hide immediately.
-    this.hideNavToolbox(true);
-    this._fullScrToggler.hidden = true;
     return true;
   },
 
   cleanup: function () {
     if (!window.fullScreen) {
       MousePosTracker.removeListener(this);
       document.removeEventListener("keypress", this._keyToggleCallback, false);
       document.removeEventListener("popupshown", this._setPopupOpen, false);
@@ -221,23 +218,16 @@ var FullScreen = {
   cleanupDomFullscreen: function () {
     this.cancelWarning();
     gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
     gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
     gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
     window.removeEventListener("activate", this);
 
     document.documentElement.removeAttribute("inDOMFullscreen");
-    this.showNavToolbox();
-    // If we are still in fullscreen mode, re-hide
-    // the toolbox with animation.
-    if (window.fullScreen) {
-      this._shouldAnimate = true;
-      this.hideNavToolbox();
-    }
 
     window.messageManager
           .broadcastAsyncMessage("DOMFullscreen:CleanUp");
   },
 
   _isRemoteBrowser: function (aBrowser) {
     return gMultiProcessBrowser && aBrowser.getAttribute("remote") == "true";
   },
@@ -261,50 +251,45 @@ var FullScreen = {
   {
     FullScreen.hideNavToolbox();
   },
   _keyToggleCallback: function(aEvent)
   {
     // if we can use the keyboard (eg Ctrl+L or Ctrl+E) to open the toolbars, we
     // should provide a way to collapse them too.
     if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
-      FullScreen.hideNavToolbox(true);
+      FullScreen.hideNavToolbox();
     }
     // F6 is another shortcut to the address bar, but its not covered in OpenLocation()
     else if (aEvent.keyCode == aEvent.DOM_VK_F6)
       FullScreen.showNavToolbox();
   },
 
   // Checks whether we are allowed to collapse the chrome
   _isPopupOpen: false,
   _isChromeCollapsed: false,
-  _safeToCollapse: function(forceHide)
-  {
+  _safeToCollapse: function () {
     if (!gPrefService.getBoolPref("browser.fullscreen.autohide"))
       return false;
 
-    if (!forceHide) {
-      // a popup menu is open in chrome: don't collapse chrome
-      if (this._isPopupOpen)
-        return false;
-      // On OS X Lion we don't want to hide toolbars.
-      if (this.useLionFullScreen)
-        return false;
-    }
+    // a popup menu is open in chrome: don't collapse chrome
+    if (this._isPopupOpen)
+      return false;
+
+    // On OS X Lion we don't want to hide toolbars.
+    if (this.useLionFullScreen)
+      return false;
 
     // a textbox in chrome is focused (location bar anyone?): don't collapse chrome
     if (document.commandDispatcher.focusedElement &&
         document.commandDispatcher.focusedElement.ownerDocument == document &&
         document.commandDispatcher.focusedElement.localName == "input") {
-      if (forceHide)
-        // hidden textboxes that still have focus are bad bad bad
-        document.commandDispatcher.focusedElement.blur();
-      else
-        return false;
+      return false;
     }
+
     return true;
   },
 
   _setPopupOpen: function(aEvent)
   {
     // Popups should only veto chrome collapsing if they were opened when the chrome was not collapsed.
     // Otherwise, they would not affect chrome and the user would expect the chrome to go away.
     // e.g. we wouldn't want the autoscroll icon firing this event, so when the user
@@ -322,19 +307,16 @@ var FullScreen = {
   {
     aItem.setAttribute("checked", gPrefService.getBoolPref("browser.fullscreen.autohide"));
   },
   setAutohide: function()
   {
     gPrefService.setBoolPref("browser.fullscreen.autohide", !gPrefService.getBoolPref("browser.fullscreen.autohide"));
   },
 
-  // Animate the toolbars disappearing
-  _shouldAnimate: true,
-
   cancelWarning: function(event) {
     if (!this.warningBox)
       return;
     this.warningBox.removeEventListener("transitionend", this);
     if (this.warningFadeOutTimeout) {
       clearTimeout(this.warningFadeOutTimeout);
       this.warningFadeOutTimeout = null;
     }
@@ -503,92 +485,60 @@ var FullScreen = {
         bottom: rect.bottom,
         left: rect.left,
         right: rect.right
       };
       MousePosTracker.addListener(this);
     }
   },
 
-  hideNavToolbox: function(forceHide = false) {
-    this._fullScrToggler.hidden = document.mozFullScreen;
-    if (this._isChromeCollapsed) {
-      if (forceHide) {
-        gNavToolbox.removeAttribute("fullscreenShouldAnimate");
-      }
+  hideNavToolbox: function (aAnimate = false) {
+    if (this._isChromeCollapsed || !this._safeToCollapse())
       return;
-    }
-    if (!this._safeToCollapse(forceHide)) {
-      this._fullScrToggler.hidden = true;
-      return;
-    }
 
-    // browser.fullscreen.animateUp
-    // 0 - never animate up
-    // 1 - animate only for first collapse after entering fullscreen (default for perf's sake)
-    // 2 - animate every time it collapses
-    let animateUp = gPrefService.getIntPref("browser.fullscreen.animateUp");
-    if (animateUp == 0) {
-      this._shouldAnimate = false;
-    } else if (animateUp == 2) {
-      this._shouldAnimate = true;
-    }
-    if (this._shouldAnimate && !forceHide) {
+    this._fullScrToggler.hidden = false;
+
+    if (aAnimate && gPrefService.getBoolPref("browser.fullscreen.animate")) {
       gNavToolbox.setAttribute("fullscreenShouldAnimate", true);
-      this._shouldAnimate = false;
       // Hide the fullscreen toggler until the transition ends.
       let listener = () => {
         gNavToolbox.removeEventListener("transitionend", listener, true);
         if (this._isChromeCollapsed)
           this._fullScrToggler.hidden = false;
       };
       gNavToolbox.addEventListener("transitionend", listener, true);
       this._fullScrToggler.hidden = true;
     }
 
     gNavToolbox.style.marginTop =
       -gNavToolbox.getBoundingClientRect().height + "px";
     this._isChromeCollapsed = true;
     MousePosTracker.removeListener(this);
   },
 
-  showXULChrome: function(aTag, aShow)
-  {
-    var els = document.getElementsByTagNameNS(this._XULNS, aTag);
-
-    for (let el of els) {
-      // XXX don't interfere with previously collapsed toolbars
-      if (el.getAttribute("fullscreentoolbar") == "true") {
-        if (!aShow) {
-          // Give the main nav bar and the tab bar the fullscreen context menu,
-          // otherwise remove context menu to prevent breakage
-          el.setAttribute("saved-context", el.getAttribute("context"));
-          if (el.id == "nav-bar" || el.id == "TabsToolbar")
-            el.setAttribute("context", "autohide-context");
-          else
-            el.removeAttribute("context");
+  _updateToolbars: function (aEnterFS) {
+    for (let el of document.querySelectorAll("toolbar[fullscreentoolbar=true]")) {
+      if (aEnterFS) {
+        // Give the main nav bar and the tab bar the fullscreen context menu,
+        // otherwise remove context menu to prevent breakage
+        el.setAttribute("saved-context", el.getAttribute("context"));
+        if (el.id == "nav-bar" || el.id == "TabsToolbar")
+          el.setAttribute("context", "autohide-context");
+        else
+          el.removeAttribute("context");
 
-          // Set the inFullscreen attribute to allow specific styling
-          // in fullscreen mode
-          el.setAttribute("inFullscreen", true);
+        // Set the inFullscreen attribute to allow specific styling
+        // in fullscreen mode
+        el.setAttribute("inFullscreen", true);
+      } else {
+        if (el.hasAttribute("saved-context")) {
+          el.setAttribute("context", el.getAttribute("saved-context"));
+          el.removeAttribute("saved-context");
         }
-        else {
-          if (el.hasAttribute("saved-context")) {
-            el.setAttribute("context", el.getAttribute("saved-context"));
-            el.removeAttribute("saved-context");
-          }
-          el.removeAttribute("inFullscreen");
-        }
-      } else {
-        // use moz-collapsed so it doesn't persist hidden/collapsed,
-        // so that new windows don't have missing toolbars
-        if (aShow)
-          el.removeAttribute("moz-collapsed");
-        else
-          el.setAttribute("moz-collapsed", "true");
+        el.removeAttribute("inFullscreen");
       }
     }
 
     ToolbarIconColor.inferFromText();
 
     // For Lion fullscreen, all fullscreen controls are hidden, don't
     // bother to touch them. If we don't stop here, the following code
     // could cause the native fullscreen button be shown unexpectedly.
@@ -603,17 +553,17 @@ var FullScreen = {
     if (fullscreenctls.parentNode == navbar && ctlsOnTabbar) {
       fullscreenctls.removeAttribute("flex");
       document.getElementById("TabsToolbar").appendChild(fullscreenctls);
     }
     else if (fullscreenctls.parentNode.id == "TabsToolbar" && !ctlsOnTabbar) {
       fullscreenctls.setAttribute("flex", "1");
       navbar.appendChild(fullscreenctls);
     }
-    fullscreenctls.hidden = aShow;
+    fullscreenctls.hidden = !aEnterFS;
   }
 };
 XPCOMUtils.defineLazyGetter(FullScreen, "useLionFullScreen", function() {
   // We'll only use OS X Lion full screen if we're
   // * on OS X
   // * on Lion or higher (Darwin 11+)
   // * have fullscreenbutton="true"
 #ifdef XP_MACOSX
--- a/browser/base/content/browser-safebrowsing.js
+++ b/browser/base/content/browser-safebrowsing.js
@@ -31,22 +31,12 @@ var gSafeBrowsing = {
   },
 
   /**
    * Used to report a phishing page or a false positive
    * @param name String One of "Phish", "Error", "Malware" or "MalwareError"
    * @return String the report phishing URL.
    */
   getReportURL: function(name) {
-    var reportUrl = SafeBrowsing.getReportURL(name);
-
-    var pageUri = gBrowser.currentURI.clone();
-
-    // Remove the query to avoid including potentially sensitive data
-    if (pageUri instanceof Ci.nsIURL)
-      pageUri.query = '';
-
-    reportUrl += "&url=" + encodeURIComponent(pageUri.asciiSpec);
-
-    return reportUrl;
+    return SafeBrowsing.getReportURL(name, gBrowser.currentURI);
   }
 }
 #endif
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -285,21 +285,24 @@ toolbar[customizing] > .overflow-button 
 %ifdef XP_WIN
 #main-window[sizemode="maximized"] #titlebar-buttonbox {
   -moz-appearance: -moz-window-button-box-maximized;
 }
 %endif
 
 %endif
 
+#main-window[inDOMFullscreen] #navigator-toolbox,
+#main-window[inDOMFullscreen] #fullscr-toggler,
 #main-window[inDOMFullscreen] #sidebar-box,
 #main-window[inDOMFullscreen] #sidebar-splitter {
   visibility: collapse;
 }
 
+#main-window[inFullscreen]:not([OSXLionFullscreen]) toolbar:not([fullscreentoolbar=true]),
 #main-window[inFullscreen] #global-notificationbox,
 #main-window[inFullscreen] #high-priority-global-notificationbox {
   visibility: collapse;
 }
 
 #navigator-toolbox[fullscreenShouldAnimate] {
   transition: 1.5s margin-top ease-out;
 }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3014,26 +3014,26 @@ let BrowserOnClick = {
 
     let title;
     if (reason === 'malware') {
       title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
       buttons[1] = {
         label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
         accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"),
         callback: function() {
-          openUILinkIn(gSafeBrowsing.getReportURL('MalwareError'), 'tab');
+          openUILinkIn(gSafeBrowsing.getReportURL('MalwareMistake'), 'tab');
         }
       };
     } else if (reason === 'phishing') {
       title = gNavigatorBundle.getString("safebrowsing.reportedWebForgery");
       buttons[1] = {
         label: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.label"),
         accessKey: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.accessKey"),
         callback: function() {
-          openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab');
+          openUILinkIn(gSafeBrowsing.getReportURL('PhishMistake'), 'tab');
         }
       };
     } else if (reason === 'unwanted') {
       title = gNavigatorBundle.getString("safebrowsing.reportedUnwantedSite");
       // There is no button for reporting errors since Google doesn't currently
       // provide a URL endpoint for these reports.
     }
 
@@ -5061,17 +5061,25 @@ var TabsInTitlebar = {
       // _update is called on resize events, because the window is not ready
       // after sizemode events. However, we only care about the event when the
       // sizemode is different from the last time we updated the appearance of
       // the tabs in the titlebar.
       let sizemode = document.documentElement.getAttribute("sizemode");
       if (this._lastSizeMode == sizemode) {
         return;
       }
+      let oldSizeMode = this._lastSizeMode;
       this._lastSizeMode = sizemode;
+      // Don't update right now if we are leaving fullscreen, since the UI is
+      // still changing in the consequent "fullscreen" event. Code there will
+      // call this function again when everything is ready.
+      // See browser-fullScreen.js: FullScreen.toggle and bug 1173768.
+      if (oldSizeMode == "fullscreen") {
+        return;
+      }
     }
 
     for (let something in this._disallowed) {
       allowed = false;
       break;
     }
 
     let titlebar = $("titlebar");
--- a/browser/base/content/newtab/customize.js
+++ b/browser/base/content/newtab/customize.js
@@ -17,80 +17,105 @@ let gCustomize = {
 
   _nodes: {},
 
   init: function() {
     for (let idSuffix of this._nodeIDSuffixes) {
       this._nodes[idSuffix] = document.getElementById("newtab-customize-" + idSuffix);
     }
 
-    this._nodes.button.addEventListener("click", e => this.showPanel());
-    this._nodes.blank.addEventListener("click", e => {
-      gAllPages.enabled = false;
-    });
-    this._nodes.classic.addEventListener("click", e => {
-      gAllPages.enabled = true;
-
-      if (this._nodes.enhanced.getAttribute("selected")) {
-        gAllPages.enhanced = true;
-      } else {
-        gAllPages.enhanced = false;
-      }
-    });
-    this._nodes.enhanced.addEventListener("click", e => {
-      if (!gAllPages.enabled) {
-        gAllPages.enabled = true;
-        return;
-      }
-      gAllPages.enhanced = !gAllPages.enhanced;
-    });
-    this._nodes.learn.addEventListener("click", e => {
-      window.open(TILES_INTRO_LINK,'new_window');
-      this._onHidden();
-    });
+    this._nodes.button.addEventListener("click", e => this.showPanel(e));
+    this._nodes.blank.addEventListener("click", this);
+    this._nodes.classic.addEventListener("click", this);
+    this._nodes.enhanced.addEventListener("click", this);
+    this._nodes.learn.addEventListener("click", this);
 
     this.updateSelected();
   },
 
-  _onHidden: function() {
-    let nodes = gCustomize._nodes;
-    nodes.overlay.addEventListener("transitionend", function onTransitionEnd() {
-      nodes.overlay.removeEventListener("transitionend", onTransitionEnd);
-      nodes.overlay.style.display = "none";
+  hidePanel: function() {
+    this._nodes.overlay.addEventListener("transitionend", function onTransitionEnd() {
+      gCustomize._nodes.overlay.removeEventListener("transitionend", onTransitionEnd);
+      gCustomize._nodes.overlay.style.display = "none";
     });
-    nodes.overlay.style.opacity = 0;
-    nodes.panel.removeEventListener("popuphidden", gCustomize._onHidden);
-    nodes.panel.hidden = true;
-    nodes.button.removeAttribute("active");
+    this._nodes.overlay.style.opacity = 0;
+    this._nodes.button.removeAttribute("active");
+    this._nodes.panel.removeAttribute("open");
+    document.removeEventListener("click", this);
+    document.removeEventListener("keydown", this);
   },
 
-  showPanel: function() {
-    this._nodes.overlay.style.display = "block";
+  showPanel: function(event) {
+    if (this._nodes.panel.getAttribute("open") == "true") {
+      return;
+    }
+
+    let {panel, button, overlay} = this._nodes;
+    overlay.style.display = "block";
+    panel.setAttribute("open", "true");
+    button.setAttribute("active", "true");
     setTimeout(() => {
       // Wait for display update to take place, then animate.
-      this._nodes.overlay.style.opacity = 0.8;
+      overlay.style.opacity = 0.8;
     }, 0);
 
-    let nodes = this._nodes;
-    let {button, panel} = nodes;
-    if (button.hasAttribute("active")) {
-      return Promise.resolve(nodes);
+    document.addEventListener("click", this);
+    document.addEventListener("keydown", this);
+
+    // Stop the event propogation to prevent panel from immediately closing
+    // via the document click event that we just added.
+    event.stopPropagation();
+  },
+
+  handleEvent: function(event) {
+    switch (event.type) {
+      case "click":
+        this.onClick(event);
+        break;
+      case "keydown":
+        this.onKeyDown(event);
+        break;
     }
+  },
 
-    panel.hidden = false;
-    panel.openPopup(button);
-    button.setAttribute("active", true);
-    panel.addEventListener("popuphidden", this._onHidden);
+  onClick: function(event) {
+    if (event.currentTarget == document) {
+      if (!this._nodes.panel.contains(event.target)) {
+        this.hidePanel();
+      }
+    }
+    switch (event.currentTarget.id) {
+      case "newtab-customize-blank":
+        sendAsyncMessage("NewTab:Customize", {enabled: false, enhanced: false});
+        break;
+      case "newtab-customize-classic":
+        if (this._nodes.enhanced.getAttribute("selected")){
+          sendAsyncMessage("NewTab:Customize", {enabled: true, enhanced: true});
+        } else {
+          sendAsyncMessage("NewTab:Customize", {enabled: true, enhanced: false});
+        }
+        break;
+      case "newtab-customize-enhanced":
+        sendAsyncMessage("NewTab:Customize", {enabled: true, enhanced: !gAllPages.enhanced});
+        break;
+      case "newtab-customize-learn":
+        this.showLearn();
+        break;
+    }
+  },
 
-    return new Promise(resolve => {
-      panel.addEventListener("popupshown", function onShown() {
-        panel.removeEventListener("popupshown", onShown);
-        resolve(nodes);
-      });
-    });
+  onKeyDown: function(event) {
+    if (event.keyCode == event.DOM_VK_ESCAPE) {
+      this.hidePanel();
+    }
+  },
+
+  showLearn: function() {
+    window.open(TILES_INTRO_LINK, 'new_window');
+    this.hidePanel();
   },
 
   updateSelected: function() {
     let {enabled, enhanced} = gAllPages;
     let selected = enabled ? enhanced ? "enhanced" : "classic" : "blank";
     ["enhanced", "classic", "blank"].forEach(id => {
       let node = this._nodes[id];
       if (id == selected) {
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -476,34 +476,73 @@ input[type=button] {
   width: 100%;
   height: 100%;
   background: #F9F9F9;
   z-index: 100;
   position: fixed;
   transition: opacity .07s linear;
 }
 
+.newtab-customize-panel-container {
+  position: absolute;
+  margin-right: 40px;
+}
+
 #newtab-customize-panel {
-  z-index: 101;
-  margin-top: -5px;
+  z-index: 999;
+  margin-top: 55px;
   min-width: 270px;
+  position: absolute;
+  top: 100%;
+  right: -25px;
+  background-color: white;
+  border-radius: 6px;
+  filter: drop-shadow(0 0 1px rgba(0,0,0,0.4)) drop-shadow(0 3px 4px rgba(0,0,0,0.4));
+  transition: all 200ms ease-in-out;
+  transform-origin: top right;
+  transform: translate(-30px, -20px) scale(0) translate(30px, 20px);
+}
+
+#newtab-customize-panel:-moz-locale-dir(rtl) {
+  transform-origin: 40px top 20px;
+}
+
+#newtab-customize-panel:-moz-locale-dir(rtl),
+#newtab-customize-panel-anchor:-moz-locale-dir(rtl) {
+  left: 15px;
+  right: auto;
+}
+
+#newtab-customize-panel[open="true"] {
+  transform: translate(-30px, -20px) scale(1) translate(30px, 20px);
+}
+
+#newtab-customize-panel-anchor {
+  width: 18px;
+  height: 18px;
+  background-color: white;
+  transform: rotate(45deg);
+  position: absolute;
+  top: -6px;
+  right: 15px;
 }
 
 #newtab-customize-title {
   color: #7A7A7A;
   font-size: 14px;
   background-color: #FFFFFF;
   line-height: 25px;
   padding: 15px;
   font-weight: 600;
   cursor: default;
   border-radius: 5px 5px 0px 0px;
   max-width: 300px;
   overflow: hidden;
   display: table-cell;
+  border-top: none;
 }
 
 #newtab-customize-title > label {
   cursor: default;
 }
 
 #newtab-customize-panel > .panel-arrowcontainer > .panel-arrowcontent,
 #newtab-search-panel > .panel-arrowcontainer > .panel-arrowcontent {
@@ -513,16 +552,17 @@ input[type=button] {
 .newtab-customize-panel-item,
 .newtab-search-panel-engine,
 #newtab-search-manage {
   line-height: 25px;
   padding: 15px;
   -moz-padding-start: 40px;
   font-size: 14px;
   cursor: pointer;
+  max-width: 300px;
 }
 
 .newtab-customize-panel-item:not(:first-child),
 .newtab-search-panel-engine {
   border-top: 1px solid threedshadow;
 }
 
 .newtab-search-panel-engine > image {
@@ -539,20 +579,18 @@ input[type=button] {
 .newtab-customize-complex-option {
   padding: 0;
   margin: 0;
   cursor: pointer;
 }
 
 .newtab-customize-panel-item,
 .newtab-customize-complex-option {
-  width: 100%;
   display: block;
   text-align: start;
-  max-width: 300px;
   background-color: #F9F9F9;
 }
 
 .newtab-customize-panel-item[selected]:-moz-locale-dir(rtl) {
   background-position: right 15px center;
 }
 
 .newtab-customize-complex-option:hover > .selectable:not([selected]):-moz-locale-dir(rtl),
@@ -610,16 +648,17 @@ input[type=button] {
   color: #333333;
 }
 
 .newtab-customize-panel-subitem {
   font-size: 12px;
   padding: 0px 15px 15px 15px;
   -moz-padding-start: 40px;
   display: block;
+  max-width: 300px;
 }
 
 .newtab-customize-panel-subitem > label {
   padding: 0px 10px;
   line-height: 20px;
   vertical-align: middle;
   max-width: 225px;
 }
@@ -783,28 +822,28 @@ input[type=button] {
   left: 0px;
   right: auto;
   float: left;
   margin-right: 40px;
 }
 
 .newtab-intro-image-customize {
   box-shadow: 3px 3px 5px #888;
-  margin: 0 !important;
+  margin-top: 0px;
   background-color: #FFF;
   float: left;
   z-index: 101;
   margin-top: -5px;
   min-width: 270px;
   padding: 0;
 }
 
 .newtab-intro-image-customize #newtab-customize-title {
   display: block;
-  max-height: 72px;
+  max-height: 40px;
 }
 
 .newtab-intro-image-customize .newtab-customize-panel-item:not([selected]):hover {
   background-color: inherit;
   color: #7A7A7A;
   background: none;
 }
 
--- a/browser/base/content/newtab/newTab.xul
+++ b/browser/base/content/newtab/newTab.xul
@@ -17,46 +17,49 @@
   <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
   %browserDTD;
 ]>
 
 <xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml"
             xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
             title="&newtab.pageTitle;">
 
-  <div id="newtab-customize-overlay"></div>
-
   <xul:panel id="newtab-search-panel" orient="vertical" type="arrow"
              noautohide="true" hidden="true">
     <xul:hbox id="newtab-search-manage">
       <xul:label>&changeSearchSettings.button;</xul:label>
     </xul:hbox>
   </xul:panel>
 
-  <xul:panel id="newtab-customize-panel" orient="vertical" type="arrow"
-             noautohide="true" hidden="true">
-    <xul:hbox id="newtab-customize-title" class="newtab-customize-panel-item">
-      <xul:label>&newtab.customize.cog.title2;</xul:label>
-    </xul:hbox>
-    <xul:vbox class="newtab-customize-complex-option">
-      <xul:hbox id="newtab-customize-classic" class="newtab-customize-panel-superitem newtab-customize-panel-item selectable">
-        <xul:label>&newtab.customize.classic;</xul:label>
-      </xul:hbox>
-      <xul:hbox id="newtab-customize-enhanced" class="newtab-customize-panel-subitem">
-        <xul:label class="checkbox"></xul:label>
-        <xul:label>&newtab.customize.cog.enhanced;</xul:label>
-      </xul:hbox>
-    </xul:vbox>
-    <xul:hbox id="newtab-customize-blank" class="newtab-customize-panel-item selectable">
-      <xul:label>&newtab.customize.blank2;</xul:label>
-    </xul:hbox>
-    <xul:hbox id="newtab-customize-learn" class="newtab-customize-panel-item">
-      <xul:label>&newtab.customize.cog.learn;</xul:label>
-    </xul:hbox>
-  </xul:panel>
+  <div class="newtab-customize-panel-container">
+    <div id="newtab-customize-panel" orient="vertical">
+        <div id="newtab-customize-panel-anchor"></div>
+        <div id="newtab-customize-title" class="newtab-customize-panel-item">
+            <label>&newtab.customize.cog.title2;</label>
+        </div>
+
+        <div class="newtab-customize-complex-option">
+            <div id="newtab-customize-classic" class="newtab-customize-panel-superitem newtab-customize-panel-item selectable">
+                <label>&newtab.customize.classic;</label>
+            </div>
+            <div id="newtab-customize-enhanced" class="newtab-customize-panel-subitem">
+                <label class="checkbox"></label>
+                <label>&newtab.customize.cog.enhanced;</label>
+            </div>
+        </div>
+        <div id="newtab-customize-blank" class="newtab-customize-panel-item selectable">
+            <label>&newtab.customize.blank2;</label>
+        </div>
+        <div id="newtab-customize-learn" class="newtab-customize-panel-item">
+            <label>&newtab.customize.cog.learn;</label>
+        </div>
+    </div>
+  </div>
+
+  <div id="newtab-customize-overlay"></div>
 
   <div id="newtab-intro-mask">
     <div id="newtab-intro-modal">
       <div id="newtab-intro-progress">
         <div id="newtab-intro-numerical-progress"/>
         <div id="newtab-intro-graphical-progress">
           <span id="indicator"/>
         </div>
@@ -121,17 +124,16 @@
         </div>
 
         <div class="newtab-side-margin"/>
       </div>
 
       <div id="newtab-margin-bottom"/>
 
     </div>
-
     <input id="newtab-customize-button" type="button" title="&newtab.customize.title;"/>
   </div>
 
   <xul:script type="text/javascript;version=1.8"
               src="chrome://browser/content/searchSuggestionUI.js"/>
   <xul:script type="text/javascript;version=1.8"
               src="chrome://browser/content/newtab/newTab.js"/>
 </xul:window>
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1283,26 +1283,30 @@ nsContextMenu.prototype = {
     timerCallback.prototype = {
       notify: function sLA_timer_notify(aTimer) {
         channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
         return;
       }
     }
 
     // setting up a new channel for 'right click - save link as ...'
-    // which should be treated the same way as a toplevel load, hence
-    // we use TYPE_DOCUMENT, see also bug: 1136055
+    // ideally we should use:
+    // * doc            - as the loadingNode, and/or
+    // * this.principal - as the loadingPrincipal
+    // for now lets use systemPrincipal to bypass mixedContentBlocker
+    // checks after redirects, see bug: 1136055
     var ioService = Cc["@mozilla.org/network/io-service;1"].
                     getService(Ci.nsIIOService);
+    var principal = Services.scriptSecurityManager.getSystemPrincipal();
     var channel = ioService.newChannelFromURI2(makeURI(linkURL),
                                                null, // aLoadingNode
-                                               this.principal, // aLoadingPrincipal
+                                               principal, // aLoadingPrincipal
                                                null, // aTriggeringPrincipal
                                                Ci.nsILoadInfo.SEC_NORMAL,
-                                               Ci.nsIContentPolicy.TYPE_DOCUMENT);
+                                               Ci.nsIContentPolicy.TYPE_OTHER);
     if (linkDownload)
       channel.contentDispositionFilename = linkDownload;
     if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
       let docIsPrivate = PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser);
       channel.setPrivate(docIsPrivate);
     }
     channel.notificationCallbacks = new callbacks();
 
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -201,16 +201,20 @@ function initIndexedDBRow()
 }
 
 function onIndexedDBClear()
 {
   Components.classes["@mozilla.org/dom/quota/manager;1"]
             .getService(nsIQuotaManager)
             .clearStoragesForURI(gPermURI);
 
+  Components.classes["@mozilla.org/serviceworkers/manager;1"]
+            .getService(Components.interfaces.nsIServiceWorkerManager)
+            .removeAndPropagate(gPermURI.host);
+
   SitePermissions.remove(gPermURI, "indexedDB");
   initIndexedDBRow();
 }
 
 function onIndexedDBUsageCallback(uri, usage, fileUsage)
 {
   if (!uri.equals(gPermURI)) {
     throw new Error("Callback received for bad URI: " + uri);
--- a/browser/base/content/report-phishing-overlay.xul
+++ b/browser/base/content/report-phishing-overlay.xul
@@ -24,12 +24,12 @@
               observes="reportPhishingBroadcaster"
               oncommand="openUILink(gSafeBrowsing.getReportURL('Phish'), event);"
               onclick="checkForMiddleClick(this, event);"/>
     <menuitem id="menu_HelpPopup_reportPhishingErrortoolmenu"
               label="&safeb.palm.notforgery.label2;"
               accesskey="&reportPhishSiteMenu.accesskey;"
               insertbefore="aboutSeparator"
               observes="reportPhishingErrorBroadcaster"
-              oncommand="openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab');"
+              oncommand="openUILinkIn(gSafeBrowsing.getReportURL('PhishMistake'), 'tab');"
               onclick="checkForMiddleClick(this, event);"/>
   </menupopup>
 </overlay>
--- a/browser/base/content/searchSuggestionUI.js
+++ b/browser/base/content/searchSuggestionUI.js
@@ -140,29 +140,28 @@ SearchSuggestionUIController.prototype =
   addInputValueToFormHistory: function () {
     this._sendMsg("AddFormHistoryEntry", this.input.value);
   },
 
   handleEvent: function (event) {
     this["_on" + event.type[0].toUpperCase() + event.type.substr(1)](event);
   },
 
-  _onInput: function () {
+  _onInput: function (event) {
     if (this._ignoreInputEvent) {
-      this._ignoreInputEvent = false;
       return;
     }
     if (this.input.value) {
       this._getSuggestions();
     }
     else {
       this._stickyInputValue = "";
       this._hideSuggestions();
     }
-    this.selectAndUpdateInput(-1);
+    this.selectedIndex = -1;
   },
 
   _onKeypress: function (event) {
     let selectedIndexDelta = 0;
     switch (event.keyCode) {
     case event.DOM_VK_UP:
       if (this.numSuggestions) {
         selectedIndexDelta = -1;
@@ -237,25 +236,22 @@ SearchSuggestionUIController.prototype =
   _onMousedown: function (event) {
     if (event.button == 2) {
       return;
     }
     let idx = this._indexOfTableRowOrDescendent(event.target);
     let suggestion = this.suggestionAtIndex(idx);
     this._stickyInputValue = suggestion;
 
-    // Commit composition string forcibly, because setting input value does not
-    // work if input has composition string (see bug 1115616 and bug 632744).
-    // Ignore input event for composition end to avoid getting suggestion again.
+    // Setting value commits composition string forcibly.  While IME commits
+    // composition, this needs to ignore input event at committed composition
+    // string which will be overwritten by the suggestion.
     this._ignoreInputEvent = true;
-    this.input.blur();
-    this.input.focus();
+    this.input.value = suggestion;
     this._ignoreInputEvent = false;
-
-    this.input.value = suggestion;
     this.input.setAttribute("selection-index", idx);
     this.input.setAttribute("selection-kind", "mouse");
     this._hideSuggestions();
     if (this.onClick) {
       this.onClick.call(null, event);
     }
   },
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3366,16 +3366,26 @@
             },
 
             // Called when the user asks to switch to a given tab.
             requestTab: function(tab) {
               if (tab === this.requestedTab) {
                 return;
               }
 
+              // Instrumentation to figure out bug 1166351 - if the binding
+              // on the browser we're switching to has gone away, try to find out
+              // why. We should remove this and the checkBrowserBindingAlive
+              // method once bug 1166351 has been closed.
+              if (this.tabbrowser.AppConstants.E10S_TESTING_ONLY &&
+                  !this.checkBrowserBindingAlive(tab)) {
+                Cu.reportError("Please report the above errors in bug 1166351.");
+                return;
+              }
+
               this.logState("requestTab " + this.tinfo(tab));
               this.startTabSwitch();
 
               this.requestedTab = tab;
 
               this.preActions();
 
               clearTimeout(this.unloadTimer);
@@ -3461,16 +3471,50 @@
                 result = Services.prefs.getBoolPref("browser.tabs.remote.logSwitchTiming");
               } catch (ex) {
               }
               this._shouldLog = result;
               this._logInit = true;
               return this._shouldLog;
             },
 
+            // Instrumentation for bug 1166351
+            checkBrowserBindingAlive: function(tab) {
+              let err = Cu.reportError;
+
+              if (!tab.linkedBrowser) {
+                err("Attempting to switch to tab that has no linkedBrowser.");
+                return false;
+              }
+
+              let b = tab.linkedBrowser;
+
+              if (!b.isRemoteBrowser) {
+                // non-remote browsers are not the problem.
+                return true;
+              }
+
+              if (!b._alive) {
+                // The browser binding has been removed. Dump a bunch of
+                // diagnostic information to the browser console.
+                err("The tabbrowser-remote-browser binding has been removed " +
+                    "from the tab being switched to.");
+                err("MozBinding is currently: " +
+                    window.getComputedStyle(b).MozBinding);
+                if (!b.parentNode) {
+                  err("Browser was removed from the DOM.");
+                } else {
+                  err("Parent is: " + b.parentNode.outerHTML);
+                }
+                return false;
+              }
+
+              return true;
+            },
+
             tinfo: function(tab) {
               if (tab) {
                 return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")";
               } else {
                 return "null";
               }
             },
 
@@ -6112,16 +6156,18 @@
         </body>
       </method>
     </implementation>
   </binding>
 
   <binding id="tabbrowser-remote-browser"
            extends="chrome://global/content/bindings/remote-browser.xml#remote-browser">
     <implementation>
+      <!-- This will go away if the binding has been removed for some reason. -->
+      <field name="_alive">true</field>
       <!-- throws exception for unknown schemes -->
       <method name="loadURIWithFlags">
         <parameter name="aURI"/>
         <parameter name="aFlags"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <parameter name="aPostData"/>
         <body>
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -332,30 +332,31 @@ skip-if = buildapp == 'mulet' || e10s # 
 [browser_overflowScroll.js]
 [browser_pageInfo.js]
 skip-if = buildapp == 'mulet'
 [browser_page_style_menu.js]
 
 [browser_parsable_css.js]
 skip-if = e10s
 [browser_parsable_script.js]
-skip-if = asan # Disabled because it takes a long time (see test for more information)
+skip-if = asan || (os == 'linux' && !debug && (bits == 32)) # disabled on asan because of timeouts, and bug 1172468 for the linux 32-bit pgo issue.
 
 [browser_pinnedTabs.js]
 [browser_plainTextLinks.js]
 [browser_popupUI.js]
 skip-if = buildapp == 'mulet'
 [browser_popup_blocker.js]
 skip-if = (os == 'linux') || (e10s && debug) # Frequent bug 1081925 and bug 1125520 failures
 [browser_printpreview.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1101973 - breaks the next test in e10s, and may be responsible for later timeout after logging "Error: Channel closing: too late to send/recv, messages will be lost"
 [browser_private_browsing_window.js]
 skip-if = buildapp == 'mulet'
 [browser_private_no_prompt.js]
 skip-if = buildapp == 'mulet'
+[browser_PageMetaData_pushstate.js]
 [browser_relatedTabs.js]
 [browser_remoteTroubleshoot.js]
 support-files =
   test_remoteTroubleshoot.html
 [browser_removeTabsToTheEnd.js]
 [browser_removeUnsafeProtocolsFromURLBarPaste.js]
 [browser_restore_isAppTab.js]
 [browser_sanitize-passwordDisabledHosts.js]
@@ -413,17 +414,21 @@ skip-if = e10s
 support-files =
   close_beforeunload_opens_second_tab.html
   close_beforeunload.html
 [browser_tabs_isActive.js]
 skip-if = e10s # Bug 1100664 - test relies on linkedBrowser.docShell
 [browser_tabs_owner.js]
 [browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js]
 run-if = e10s
-[browser_trackingUI.js]
+[browser_trackingUI_1.js]
+support-files =
+  trackingPage.html
+  benignPage.html
+[browser_trackingUI_2.js]
 support-files =
   trackingPage.html
   benignPage.html
 [browser_typeAheadFind.js]
 skip-if = buildapp == 'mulet'
 [browser_unknownContentType_title.js]
 [browser_unloaddialogs.js]
 skip-if = e10s # Bug 1100700 - test relies on unload event firing on closed tabs, which it doesn't
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_PageMetaData_pushstate.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+add_task(function* () {
+  let rooturi = "https://example.com/browser/toolkit/modules/tests/browser/";
+  yield BrowserTestUtils.openNewForegroundTab(gBrowser, rooturi + "metadata_simple.html");
+  let result = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    return PageMetadata.getData(content.document);
+  });
+  // result should have description
+  is(result.url, rooturi + "metadata_simple.html", "metadata url is correct");
+  is(result.title, "Test Title", "metadata title is correct");
+  is(result.description, "A very simple test page", "description is correct");
+
+  result = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    content.history.pushState({}, "2", "2.html");
+    return PageMetadata.getData(content.document);
+  });
+  // result should not have description
+  is(result.url, rooturi + "2.html", "metadata url is correct");
+  is(result.title, "Test Title", "metadata title is correct");
+  ok(!result.description, "description is undefined");
+
+  let documentURI = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    return content.document.documentURI;
+  });
+  is(gBrowser.currentURI.spec, rooturi + "2.html", "gBrowser has correct url");
+  is(documentURI, rooturi + "2.html", "content.document has correct url");
+
+  gBrowser.removeTab(gBrowser.selectedTab);
+});
rename from browser/base/content/test/general/browser_trackingUI.js
rename to browser/base/content/test/general/browser_trackingUI_1.js
--- a/browser/base/content/test/general/browser_trackingUI.js
+++ b/browser/base/content/test/general/browser_trackingUI_1.js
@@ -1,98 +1,69 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Test that the Tracking Protection Doorhanger appears
 // and has the correct state when tracking content is blocked (Bug 1043801)
 
 var PREF = "privacy.trackingprotection.enabled";
-var TABLE = "urlclassifier.trackingTable";
-
-// Update tracking database
-function doUpdate() {
-  // Add some URLs to the tracking database (to be blocked)
-  var testData = "tracking.example.com/";
-  var testUpdate =
-    "n:1000\ni:test-track-simple\nad:1\n" +
-    "a:524:32:" + testData.length + "\n" +
-    testData;
-
-  var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
-                  .getService(Ci.nsIUrlClassifierDBService);
-
-  let deferred = Promise.defer();
-
-  var listener = {
-    QueryInterface: function(iid)
-    {
-      if (iid.equals(Ci.nsISupports) ||
-          iid.equals(Ci.nsIUrlClassifierUpdateObserver))
-        return this;
-
-      throw Cr.NS_ERROR_NO_INTERFACE;
-    },
-    updateUrlRequested: function(url) { },
-    streamFinished: function(status) { },
-    updateError: function(errorCode) {
-      ok(false, "Couldn't update classifier.");
-      deferred.resolve();
-    },
-    updateSuccess: function(requestedTimeout) {
-      deferred.resolve();
-    }
-  };
-
-  dbService.beginUpdate(listener, "test-track-simple", "");
-  dbService.beginStream("", "");
-  dbService.updateStream(testUpdate);
-  dbService.finishStream();
-  dbService.finishUpdate();
-
-  return deferred.promise;
-}
+var BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+var TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
 
 function testBenignPage(gTestBrowser)
 {
   // Make sure the doorhanger does NOT appear
   var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
   is(notification, null, "Tracking Content Doorhanger did NOT appear when protection was ON and tracking was NOT present");
 }
 
-function testTrackingPage(gTestBrowser)
+function* testTrackingPage(gTestBrowser)
 {
   // Make sure the doorhanger appears
   var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
   isnot(notification, null, "Tracking Content Doorhanger did appear when protection was ON and tracking was present");
   notification.reshow();
+  var notificationElement = PopupNotifications.panel.firstChild;
+
+  // Wait for the method to be attached after showing the popup
+  yield promiseWaitForCondition(() => {
+    return notificationElement.disableTrackingContentProtection;
+  });
+
   // Make sure the state of the doorhanger includes blocking tracking elements
-  isnot(PopupNotifications.panel.firstChild.isTrackingContentBlocked, 0,
-    "Tracking Content is being blocked");
+  ok(notificationElement.isTrackingContentBlocked,
+     "Tracking Content is being blocked");
 
   // Make sure the notification has no trackingblockdisabled attribute
-  ok(!PopupNotifications.panel.firstChild.hasAttribute("trackingblockdisabled"),
+  ok(!notificationElement.hasAttribute("trackingblockdisabled"),
     "Doorhanger must have no trackingblockdisabled attribute");
-
-  // Disable Tracking Content Protection for the page (which reloads the page)
-  PopupNotifications.panel.firstChild.disableTrackingContentProtection();
 }
 
-function testTrackingPageWhitelisted(gTestBrowser)
+function* testTrackingPageWhitelisted(gTestBrowser)
 {
   // Make sure the doorhanger appears
   var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
   isnot(notification, null, "Tracking Content Doorhanger did appear when protection was ON and tracking was present but white-listed");
   notification.reshow();
+  var notificationElement = PopupNotifications.panel.firstChild;
+
+  // Wait for the method to be attached after showing the popup
+  yield promiseWaitForCondition(() => {
+    return notificationElement.disableTrackingContentProtection;
+  });
+
+  var notificationElement = PopupNotifications.panel.firstChild;
+
   // Make sure the state of the doorhanger does NOT include blocking tracking elements
-  is(PopupNotifications.panel.firstChild.isTrackingContentBlocked, 0,
+  ok(!notificationElement.isTrackingContentBlocked,
     "Tracking Content is NOT being blocked");
 
   // Make sure the notification has the trackingblockdisabled attribute set to true
-  is(PopupNotifications.panel.firstChild.getAttribute("trackingblockdisabled"), "true",
+  is(notificationElement.getAttribute("trackingblockdisabled"), "true",
     "Doorhanger must have [trackingblockdisabled='true'] attribute");
 }
 
 function testTrackingPageOFF(gTestBrowser)
 {
   // Make sure the doorhanger does NOT appear
   var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
   is(notification, null, "Tracking Content Doorhanger did NOT appear when protection was OFF and tracking was present");
@@ -103,45 +74,46 @@ function testBenignPageOFF(gTestBrowser)
   // Make sure the doorhanger does NOT appear
   var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
   is(notification, null, "Tracking Content Doorhanger did NOT appear when protection was OFF and tracking was NOT present");
 }
 
 add_task(function* () {
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref(PREF);
-    Services.prefs.clearUserPref(TABLE);
     gBrowser.removeCurrentTab();
   });
 
-  // Populate and use 'test-track-simple' for tracking protection lookups
-  Services.prefs.setCharPref(TABLE, "test-track-simple");
-  yield doUpdate();
+  yield updateTrackingProtectionDatabase();
 
   let tab = gBrowser.selectedTab = gBrowser.addTab();
 
   // Enable Tracking Protection
   Services.prefs.setBoolPref(PREF, true);
 
   // Point tab to a test page NOT containing tracking elements
-  yield promiseTabLoadEvent(tab, "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html");
+  yield promiseTabLoadEvent(tab, BENIGN_PAGE);
   testBenignPage(gBrowser.getBrowserForTab(tab));
 
   // Point tab to a test page containing tracking elements
-  yield promiseTabLoadEvent(tab, "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html");
-  testTrackingPage(gBrowser.getBrowserForTab(tab));
+  yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+
+  // Tracking content must be blocked
+  yield testTrackingPage(gBrowser.getBrowserForTab(tab));
+
+  // Disable Tracking Content Protection for the page (which reloads the page)
+  PopupNotifications.panel.firstChild.disableTrackingContentProtection();
 
   // Wait for tab to reload following tracking-protection page white-listing
   yield promiseTabLoadEvent(tab);
-  // Tracking content must be white-listed (NOT blocked)
-  testTrackingPageWhitelisted(gBrowser.getBrowserForTab(tab));
 
-  // Disable Tracking Protection
-  Services.prefs.setBoolPref(PREF, false);
+  // Tracking content must be white-listed (NOT blocked)
+  yield testTrackingPageWhitelisted(gBrowser.getBrowserForTab(tab));
+
+  // Re-enable Tracking Content Protection for the page (which reloads the page)
+  PopupNotifications.panel.firstChild.enableTrackingContentProtection();
 
-  // Point tab to a test page containing tracking elements
-  yield promiseTabLoadEvent(tab, "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html");
-  testTrackingPageOFF(gBrowser.getBrowserForTab(tab));
+  // Wait for tab to reload following tracking-protection page white-listing
+  yield promiseTabLoadEvent(tab);
 
-  // Point tab to a test page NOT containing tracking elements
-  yield promiseTabLoadEvent(tab, "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html");
-  testBenignPageOFF(gBrowser.getBrowserForTab(tab));
+  // Tracking content must be blocked
+  yield testTrackingPage(gBrowser.getBrowserForTab(tab));
 });
copy from browser/base/content/test/general/browser_trackingUI.js
copy to browser/base/content/test/general/browser_trackingUI_2.js
--- a/browser/base/content/test/general/browser_trackingUI.js
+++ b/browser/base/content/test/general/browser_trackingUI_2.js
@@ -1,100 +1,18 @@
 /* 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/. */
 
-// Test that the Tracking Protection Doorhanger appears
-// and has the correct state when tracking content is blocked (Bug 1043801)
+// Test that the Tracking Protection Doorhanger does not ever appear
+// when the feature is off (Bug 1043801)
 
 var PREF = "privacy.trackingprotection.enabled";
-var TABLE = "urlclassifier.trackingTable";
-
-// Update tracking database
-function doUpdate() {
-  // Add some URLs to the tracking database (to be blocked)
-  var testData = "tracking.example.com/";
-  var testUpdate =
-    "n:1000\ni:test-track-simple\nad:1\n" +
-    "a:524:32:" + testData.length + "\n" +
-    testData;
-
-  var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
-                  .getService(Ci.nsIUrlClassifierDBService);
-
-  let deferred = Promise.defer();
-
-  var listener = {
-    QueryInterface: function(iid)
-    {
-      if (iid.equals(Ci.nsISupports) ||
-          iid.equals(Ci.nsIUrlClassifierUpdateObserver))
-        return this;
-
-      throw Cr.NS_ERROR_NO_INTERFACE;
-    },
-    updateUrlRequested: function(url) { },
-    streamFinished: function(status) { },
-    updateError: function(errorCode) {
-      ok(false, "Couldn't update classifier.");
-      deferred.resolve();
-    },
-    updateSuccess: function(requestedTimeout) {
-      deferred.resolve();
-    }
-  };
-
-  dbService.beginUpdate(listener, "test-track-simple", "");
-  dbService.beginStream("", "");
-  dbService.updateStream(testUpdate);
-  dbService.finishStream();
-  dbService.finishUpdate();
-
-  return deferred.promise;
-}
-
-function testBenignPage(gTestBrowser)
-{
-  // Make sure the doorhanger does NOT appear
-  var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
-  is(notification, null, "Tracking Content Doorhanger did NOT appear when protection was ON and tracking was NOT present");
-}
-
-function testTrackingPage(gTestBrowser)
-{
-  // Make sure the doorhanger appears
-  var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
-  isnot(notification, null, "Tracking Content Doorhanger did appear when protection was ON and tracking was present");
-  notification.reshow();
-  // Make sure the state of the doorhanger includes blocking tracking elements
-  isnot(PopupNotifications.panel.firstChild.isTrackingContentBlocked, 0,
-    "Tracking Content is being blocked");
-
-  // Make sure the notification has no trackingblockdisabled attribute
-  ok(!PopupNotifications.panel.firstChild.hasAttribute("trackingblockdisabled"),
-    "Doorhanger must have no trackingblockdisabled attribute");
-
-  // Disable Tracking Content Protection for the page (which reloads the page)
-  PopupNotifications.panel.firstChild.disableTrackingContentProtection();
-}
-
-function testTrackingPageWhitelisted(gTestBrowser)
-{
-  // Make sure the doorhanger appears
-  var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
-  isnot(notification, null, "Tracking Content Doorhanger did appear when protection was ON and tracking was present but white-listed");
-  notification.reshow();
-  // Make sure the state of the doorhanger does NOT include blocking tracking elements
-  is(PopupNotifications.panel.firstChild.isTrackingContentBlocked, 0,
-    "Tracking Content is NOT being blocked");
-
-  // Make sure the notification has the trackingblockdisabled attribute set to true
-  is(PopupNotifications.panel.firstChild.getAttribute("trackingblockdisabled"), "true",
-    "Doorhanger must have [trackingblockdisabled='true'] attribute");
-}
+var BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+var TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
 
 function testTrackingPageOFF(gTestBrowser)
 {
   // Make sure the doorhanger does NOT appear
   var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
   is(notification, null, "Tracking Content Doorhanger did NOT appear when protection was OFF and tracking was present");
 }
 
@@ -103,45 +21,26 @@ function testBenignPageOFF(gTestBrowser)
   // Make sure the doorhanger does NOT appear
   var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
   is(notification, null, "Tracking Content Doorhanger did NOT appear when protection was OFF and tracking was NOT present");
 }
 
 add_task(function* () {
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref(PREF);
-    Services.prefs.clearUserPref(TABLE);
     gBrowser.removeCurrentTab();
   });
 
-  // Populate and use 'test-track-simple' for tracking protection lookups
-  Services.prefs.setCharPref(TABLE, "test-track-simple");
-  yield doUpdate();
+  yield updateTrackingProtectionDatabase();
 
   let tab = gBrowser.selectedTab = gBrowser.addTab();
 
-  // Enable Tracking Protection
-  Services.prefs.setBoolPref(PREF, true);
-
-  // Point tab to a test page NOT containing tracking elements
-  yield promiseTabLoadEvent(tab, "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html");
-  testBenignPage(gBrowser.getBrowserForTab(tab));
-
-  // Point tab to a test page containing tracking elements
-  yield promiseTabLoadEvent(tab, "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html");
-  testTrackingPage(gBrowser.getBrowserForTab(tab));
-
-  // Wait for tab to reload following tracking-protection page white-listing
-  yield promiseTabLoadEvent(tab);
-  // Tracking content must be white-listed (NOT blocked)
-  testTrackingPageWhitelisted(gBrowser.getBrowserForTab(tab));
-
   // Disable Tracking Protection
   Services.prefs.setBoolPref(PREF, false);
 
   // Point tab to a test page containing tracking elements
-  yield promiseTabLoadEvent(tab, "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html");
+  yield promiseTabLoadEvent(tab, TRACKING_PAGE);
   testTrackingPageOFF(gBrowser.getBrowserForTab(tab));
 
   // Point tab to a test page NOT containing tracking elements
-  yield promiseTabLoadEvent(tab, "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html");
+  yield promiseTabLoadEvent(tab, BENIGN_PAGE);
   testBenignPageOFF(gBrowser.getBrowserForTab(tab));
 });
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -655,16 +655,65 @@ function promiseWindow(url) {
 function promiseIndicatorWindow() {
   // We don't show the indicator window on Mac.
   if ("nsISystemStatusBar" in Ci)
     return Promise.resolve();
 
   return promiseWindow("chrome://browser/content/webrtcIndicator.xul");
 }
 
+/**
+ * Add some entries to a test tracking protection database, and reset
+ * back to the default database after the test ends.
+ */
+function updateTrackingProtectionDatabase() {
+  let TABLE = "urlclassifier.trackingTable";
+  Services.prefs.setCharPref(TABLE, "test-track-simple");
+
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref(TABLE);
+  });
+
+  // Add some URLs to the tracking database (to be blocked)
+  let testData = "tracking.example.com/";
+  let testUpdate =
+    "n:1000\ni:test-track-simple\nad:1\n" +
+    "a:524:32:" + testData.length + "\n" +
+    testData;
+
+  return new Promise((resolve, reject) => {
+    let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
+                    .getService(Ci.nsIUrlClassifierDBService);
+    let listener = {
+      QueryInterface: iid => {
+        if (iid.equals(Ci.nsISupports) ||
+            iid.equals(Ci.nsIUrlClassifierUpdateObserver))
+          return listener;
+
+        throw Cr.NS_ERROR_NO_INTERFACE;
+      },
+      updateUrlRequested: url => { },
+      streamFinished: status => { },
+      updateError: errorCode => {
+        ok(false, "Couldn't update classifier.");
+        resolve();
+      },
+      updateSuccess: requestedTimeout => {
+        resolve();
+      }
+    };
+
+    dbService.beginUpdate(listener, "test-track-simple", "");
+    dbService.beginStream("", "");
+    dbService.updateStream(testUpdate);
+    dbService.finishStream();
+    dbService.finishUpdate();
+  });
+}
+
 function assertWebRTCIndicatorStatus(expected) {
   let ui = Cu.import("resource:///modules/webrtcUI.jsm", {}).webrtcUI;
   let expectedState = expected ? "visible" : "hidden";
   let msg = "WebRTC indicator " + expectedState;
   if (!expected && ui.showGlobalIndicator) {
     // It seems the global indicator is not always removed synchronously
     // in some cases.
     info("waiting for the global indicator to be hidden");
--- a/browser/base/content/test/newtab/browser_newtab_enhanced.js
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -3,17 +3,17 @@
 
 const PRELOAD_PREF = "browser.newtab.preload";
 
 let suggestedLink = {
   url: "http://example1.com/2",
   imageURI: "data:image/png;base64,helloWORLD3",
   title: "title2",
   type: "affiliate",
-  frecent_sites: ["classroom.google.com", "codecademy.com", "elearning.ut.ac.id", "khanacademy.org", "learn.jquery.com", "teamtreehouse.com", "tutorialspoint.com", "udacity.com", "w3cschool.cc", "w3schools.com"]
+  frecent_sites: ["classroom.google.com", "codeacademy.org", "codecademy.com", "codeschool.com", "codeyear.com", "elearning.ut.ac.id", "how-to-build-websites.com", "htmlcodetutorial.com", "htmldog.com", "htmlplayground.com", "learn.jquery.com", "quackit.com", "roseindia.net", "teamtreehouse.com", "tizag.com", "tutorialspoint.com", "udacity.com", "w3schools.com", "webdevelopersnotes.com"]
 };
 
 gDirectorySource = "data:application/json," + JSON.stringify({
   "enhanced": [{
     url: "http://example.com/",
     enhancedImageURI: "data:image/png;base64,helloWORLD",
     title: "title",
     type: "organic",
@@ -134,17 +134,17 @@ function runTests() {
   yield addNewTabPageTab();
   yield customizeNewTabPage("enhanced");
 
   // Suggested link was not enhanced by directory link with same domain
   ({type, enhanced, title, suggested} = getData(0));
   is(type, "affiliate", "suggested link is affiliate");
   is(enhanced, "", "suggested link has no enhanced image");
   is(title, "title2");
-  ok(suggested.indexOf("Suggested for <strong> Web Education </strong> visitors") > -1, "Suggested for 'Web Education'");
+  ok(suggested.indexOf("Suggested for <strong> webdev education </strong> visitors") > -1, "Suggested for 'webdev education'");
 
   // Enhanced history link shows up second
   ({type, enhanced, title, suggested} = getData(1));
   is(type, "enhanced", "pinned history link is enhanced");
   isnot(enhanced, "", "pinned history link has enhanced image");
   is(title, "title");
   is(suggested, "", "There is no suggested explanation");
 
@@ -171,19 +171,45 @@ function runTests() {
   yield watchLinksChangeOnce().then(TestRunner.next);
 
   yield addNewTabPageTab();
   ({type, enhanced, title, suggested} = getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Suggested for <strong> Technology </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'Technology' enthusiasts");
 
 
-    // Test server provided explanation string without category override.
+  // Test server provided explanation string without category override.
   delete suggestedLink.adgroup_name;
   Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
     "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
   yield watchLinksChangeOnce().then(TestRunner.next);
 
   yield addNewTabPageTab();
   ({type, enhanced, title, suggested} = getData(0));
   Cu.reportError("SUGGEST " + suggested);
-  ok(suggested.indexOf("Suggested for <strong> Web Education </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'Web Education' enthusiasts");
+  ok(suggested.indexOf("Suggested for <strong> webdev education </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'webdev education' enthusiasts");
+
+
+
+  // Test with xml entities in category name
+  suggestedLink.url = "http://example1.com/3";
+  suggestedLink.adgroup_name = ">angles< & \"quotes\'";
+  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
+    "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
+  yield watchLinksChangeOnce().then(TestRunner.next);
+
+  yield addNewTabPageTab();
+  ({type, enhanced, title, suggested} = getData(0));
+  Cu.reportError("SUGGEST " + suggested);
+  ok(suggested.indexOf("Suggested for <strong> &gt;angles&lt; &amp; \"quotes\' </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'xml entities' enthusiasts");
+
+
+  // Test with xml entities in explanation.
+  suggestedLink.explanation = "Testing junk explanation &<>\"'";
+  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
+    "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
+  yield watchLinksChangeOnce().then(TestRunner.next);
+
+  yield addNewTabPageTab();
+  ({type, enhanced, title, suggested} = getData(0));
+  Cu.reportError("SUGGEST " + suggested);
+  ok(suggested.indexOf("Testing junk explanation &amp;&lt;&gt;\"'") > -1, "Junk test");
 }
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -737,31 +737,41 @@ function whenSearchInitDone() {
 
 /**
  * Changes the newtab customization option and waits for the panel to open and close
  *
  * @param {string} aTheme
  *        Can be any of("blank"|"classic"|"enhanced")
  */
 function customizeNewTabPage(aTheme) {
-  let document = getContentDocument();
-  let panel = document.getElementById("newtab-customize-panel");
-  let customizeButton = document.getElementById("newtab-customize-button");
+  let promise = ContentTask.spawn(gBrowser.selectedBrowser, aTheme, function*(aTheme) {
+
+    let document = content.document;
+    let panel = document.getElementById("newtab-customize-panel");
+    let customizeButton = document.getElementById("newtab-customize-button");
 
-  // Attache onShown the listener on panel
-  panel.addEventListener("popupshown", function onShown() {
-    panel.removeEventListener("popupshown", onShown);
+    function panelOpened(opened) {
+      return new Promise( (resolve) => {
+        let options = {attributes: true, oldValue: true};
+        let observer = new content.MutationObserver(function(mutations) {
+          mutations.forEach(function(mutation) {
+            document.getElementById("newtab-customize-" + aTheme).click();
+            observer.disconnect();
+            if (opened == panel.hasAttribute("open")) {
+              resolve();
+            }
+          });
+        });
+        observer.observe(panel, options);
+      });
+    }
 
-    // Get the element for the specific option and click on it,
-    // then trigger an escape to close the panel
-    document.getElementById("newtab-customize-" + aTheme).click();
-    executeSoon(() => { panel.hidePopup(); });
+    let opened = panelOpened(true);
+    customizeButton.click();
+    yield opened;
+
+    let closed = panelOpened(false);
+    customizeButton.click();
+    yield closed;
   });
 
-  // Attache the listener for panel closing, this will resolve the promise
-  panel.addEventListener("popuphidden", function onHidden() {
-    panel.removeEventListener("popuphidden", onHidden);
-    executeSoon(TestRunner.next);
-  });
-
-  // Click on the customize button to display the panel
-  customizeButton.click();
+  promise.then(TestRunner.next);
 }
--- a/browser/base/content/test/referrer/browser.ini
+++ b/browser/base/content/test/referrer/browser.ini
@@ -1,11 +1,13 @@
 [DEFAULT]
 support-files =
   file_referrer_policyserver.sjs
   file_referrer_testserver.sjs
   head.js
 
 [browser_referrer_middle_click.js]
 [browser_referrer_open_link_in_private.js]
+skip-if = os == 'linux' # Bug 1145199
 [browser_referrer_open_link_in_tab.js]
 [browser_referrer_open_link_in_window.js]
+skip-if = os == 'linux' # Bug 1145199
 [browser_referrer_simple_click.js]
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -346,17 +346,18 @@ file, You can obtain one at http://mozil
               Cu.reportError(ex);
             }
 
             let loadCurrent = () => {
               openUILinkIn(url, "current", {
                 allowThirdPartyFixup: true,
                 disallowInheritPrincipal: !mayInheritPrincipal,
                 allowPinnedTabHostChange: true,
-                postData: postData
+                postData: postData,
+                allowPopups: url.startsWith("javascript:"),
               });
               // Ensure the start of the URL is visible for UX reasons:
               this.selectionStart = this.selectionEnd = 0;
             };
 
             // Focus the content area before triggering loads, since if the load
             // occurs in a new tab, we want focus to be restored to the content
             // area when the current tab is re-selected.
@@ -2297,18 +2298,18 @@ file, You can obtain one at http://mozil
           "trackingContentAction.block");
       </field>
       <field name="_trackingContentHelpLink">
         document.getAnonymousElementByAttribute(this, "anonid",
           "trackingContent.helplink")
       </field>
       <property name="isTrackingContentBlocked" readonly="true">
         <getter><![CDATA[
-          return this.notification.options.state &
-            Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT;
+          return !!(this.notification.options.state &
+            Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT);
         ]]></getter>
       </property>
       <constructor><![CDATA[
         // default title
         _doorhangerTitle.value =
           gNavigatorBundle.getFormattedString(
             "badContentBlocked.notblocked.message", [this._brandShortName]);
         if (this.notification.options.state &
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -183,16 +183,17 @@ function whereToOpenLink( e, ignoreButto
  * Instead of aAllowThirdPartyFixup, you may also pass an object with any of
  * these properties:
  *   allowThirdPartyFixup (boolean)
  *   postData             (nsIInputStream)
  *   referrerURI          (nsIURI)
  *   relatedToCurrent     (boolean)
  *   skipTabAnimation     (boolean)
  *   allowPinnedTabHostChange (boolean)
+ *   allowPopups          (boolean)
  */
 function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI) {
   var params;
 
   if (arguments.length == 3 && typeof arguments[2] == "object") {
     params = aAllowThirdPartyFixup;
   } else {
     params = {
@@ -225,16 +226,17 @@ function openLinkIn(url, where, params) 
   var aAllowMixedContent    = params.allowMixedContent;
   var aInBackground         = params.inBackground;
   var aDisallowInheritPrincipal = params.disallowInheritPrincipal;
   var aInitiatingDoc        = params.initiatingDoc;
   var aIsPrivate            = params.private;
   var aSkipTabAnimation     = params.skipTabAnimation;
   var aAllowPinnedTabHostChange = !!params.allowPinnedTabHostChange;
   var aNoReferrer           = params.noReferrer;
+  var aAllowPopups          = !!params.allowPopups;
 
   if (where == "save") {
     if (!aInitiatingDoc) {
       Components.utils.reportError("openUILink/openLinkIn was called with " +
         "where == 'save' but without initiatingDoc.  See bug 814264.");
       return;
     }
     // TODO(1073187): propagate referrerPolicy.
@@ -337,18 +339,23 @@ function openLinkIn(url, where, params) 
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
     }
 
     // LOAD_FLAGS_DISALLOW_INHERIT_OWNER isn't supported for javascript URIs,
     // i.e. it causes them not to load at all. Callers should strip
     // "javascript:" from pasted strings to protect users from malicious URIs
     // (see stripUnsafeProtocolOnPaste).
-    if (aDisallowInheritPrincipal && !(uriObj && uriObj.schemeIs("javascript")))
+    if (aDisallowInheritPrincipal && !(uriObj && uriObj.schemeIs("javascript"))) {
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
+    }
+
+    if (aAllowPopups) {
+      flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_POPUPS;
+    }
 
     w.gBrowser.loadURIWithFlags(url, {
       flags: flags,
       referrerURI: aNoReferrer ? null : aReferrerURI,
       referrerPolicy: aReferrerPolicy,
       postData: aPostData,
     });
     break;
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -118,16 +118,28 @@ static RedirEntry kRedirMap[] = {
     // Shares an IndexedDB origin with about:loopconversation.
     "loopconversation" },
   { "reader", "chrome://global/content/reader/aboutReader.html",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
     nsIAboutModule::MAKE_UNLINKABLE |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT },
+  {
+    "pocket-saved", "chrome://browser/content/pocket/panels/saved.html",
+    nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+    nsIAboutModule::ALLOW_SCRIPT |
+    nsIAboutModule::HIDE_FROM_ABOUTABOUT |
+    nsIAboutModule::MAKE_UNLINKABLE },
+  {
+    "pocket-signup", "chrome://browser/content/pocket/panels/signup.html",
+    nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+    nsIAboutModule::ALLOW_SCRIPT |
+    nsIAboutModule::HIDE_FROM_ABOUTABOUT |
+    nsIAboutModule::MAKE_UNLINKABLE },
 };
 static const int kRedirTotal = ArrayLength(kRedirMap);
 
 static nsAutoCString
 GetAboutModuleName(nsIURI *aURI)
 {
   nsAutoCString path;
   aURI->GetPath(path);
--- a/browser/components/build/nsModule.cpp
+++ b/browser/components/build/nsModule.cpp
@@ -110,16 +110,18 @@ static const mozilla::Module::ContractID
 #ifdef MOZ_SERVICES_HEALTHREPORT
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
 #endif
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "app-manager", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "customizing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "looppanel", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "loopconversation", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "reader", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+    { NS_ABOUT_MODULE_CONTRACTID_PREFIX "pocket-saved", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+    { NS_ABOUT_MODULE_CONTRACTID_PREFIX "pocket-signup", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
 #if defined(XP_WIN)
     { NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },
 #elif defined(XP_MACOSX)
     { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
 #endif
     { nullptr }
 };
 
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -1638,17 +1638,16 @@ CustomizeMode.prototype = {
         dragOverItem = targetIsToolbar ? this._findVisiblePreviousSiblingNode(targetNode.lastChild) :
                                          targetParent.lastChild;
         dragValue = "after";
       } else {
         dragOverItem = targetParent.children[position];
         if (!targetIsToolbar) {
           dragValue = "before";
         } else {
-          dragOverItem = this._findVisiblePreviousSiblingNode(targetParent.children[position]);
           // Check if the aDraggedItem is hovered past the first half of dragOverItem
           let window = dragOverItem.ownerDocument.defaultView;
           let direction = window.getComputedStyle(dragOverItem, null).direction;
           let itemRect = dragOverItem.getBoundingClientRect();
           let dropTargetCenter = itemRect.left + (itemRect.width / 2);
           let existingDir = dragOverItem.getAttribute("dragover");
           if ((existingDir == "before") == (direction == "ltr")) {
             dropTargetCenter += (parseInt(dragOverItem.style.borderLeftWidth) || 0) / 2;
--- a/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js
+++ b/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js
@@ -49,34 +49,8 @@ add_task(function() {
 
   yield endCustomizing();
 
   is(downloadsButton.previousSibling.id, lastVisible.id,
      "The downloads button should be placed after the last visible item.");
 
   yield resetCustomization();
 });
-
-// When we drag an item onto a target that has a hidden element before it, we should
-// instead place the new item before the hidden elements.
-add_task(function() {
-  ok(CustomizableUI.inDefaultState, "Should be in the default state");
-
-  let hidden1 = createDummyXULButton(kHidden1Id, "You can't see me");
-  hidden1.hidden = true;
-
-  let homeButton = document.getElementById("home-button");
-  CustomizableUI.addWidgetToArea(kHidden1Id, CustomizableUI.AREA_NAVBAR,
-                                 CustomizableUI.getPlacementOfWidget(homeButton.id).position);
-
-  hidden1 = document.getElementById(kHidden1Id);
-  is(hidden1.nextSibling.id, homeButton.id, "The hidden item should be before the home button");
-
-  yield startCustomizing();
-  let downloadsButton = document.getElementById("downloads-button");
-  simulateItemDrag(downloadsButton.parentNode, homeButton.parentNode);
-  yield endCustomizing();
-
-  is(hidden1.nextSibling.id, homeButton.id, "The hidden item should still be before the home button");
-  is(downloadsButton.nextSibling.id, hidden1.id, "The downloads button should now be before the hidden button");
-
-  yield resetCustomization();
-});
--- a/browser/components/loop/.eslintrc
+++ b/browser/components/loop/.eslintrc
@@ -46,17 +46,16 @@
     "no-console": 0,              // Leave as 0. We use console logging in content code.
     "no-empty": 0,                // TODO: Remove (use default)
     "no-extra-bind": 0,           // Leave as 0
     "no-extra-boolean-cast": 0,   // TODO: Remove (use default)
     "no-multi-spaces": 0,         // TBD.
     "no-new": 0,                  // TODO: Remove (use default)
     "no-redeclare": 0,            // TODO: Remove (use default)
     "no-return-assign": 0,        // TODO: Remove (use default)
-    "no-shadow": 0,               // TODO: Remove (use default)
     "no-underscore-dangle": 0,    // Leave as 0. Commonly used for private variables.
     "no-unneeded-ternary": 2,
     "no-unused-expressions": 0,   // TODO: Remove (use default)
     "no-unused-vars": 0,          // TODO: Remove (use default)
     "no-use-before-define": 0,    // TODO: Remove (use default)
     "quotes": [2, "double", "avoid-escape"],
     "strict": 0,                  // [2, "function"],
     // eslint-plugin-react rules. These are documented at
--- a/browser/components/loop/content/css/panel.css
+++ b/browser/components/loop/content/css/panel.css
@@ -242,50 +242,16 @@ body {
   background-image: url("../shared/img/check.svg#check-blue");
 }
 
 .new-room-view > .context > .checkbox-wrapper > label {
   color: #3c3c3c;
   font-weight: 700;
 }
 
-.new-room-view > .context > .context-content {
-  border: 1px solid #0096dd;
-  border-radius: 3px;
-  background: #fff;
-  padding: .8em;
-  display: flex;
-  flex-flow: row nowrap;
-  line-height: 1.1em;
-}
-
-.new-room-view > .context > .context-content > .context-preview {
-  float: left;
-  width: 16px;
-  max-height: 16px;
-  -moz-margin-end: .8em;
-  flex: 0 1 auto;
-}
-
-html[dir="rtl"] .new-room-view > .context > .context-content > .context-preview {
-  float: left;
-}
-
-.new-room-view > .context > .context-content > .context-description {
-  flex: 0 1 auto;
-  display: block;
-}
-
-.new-room-view > .context > .context-content > .context-description > .context-url {
-  display: block;
-  color: #59A1D7;
-  font-weight: 700;
-  clear: both;
-}
-
 .new-room-view > .btn {
   display: block;
   font-size: 1rem;
   margin: 0 auto .5rem;
   width: 100%;
   padding: .5rem 1rem;
   border-radius: 0 0 3px 3px;
 }
--- a/browser/components/loop/content/js/client.js
+++ b/browser/components/loop/content/js/client.js
@@ -101,19 +101,19 @@ loop.Client = (function($) {
 
           try {
             var postData = JSON.parse(responseText);
 
             var outgoingCallData = this._validate(postData,
               expectedPostCallProperties);
 
             cb(null, outgoingCallData);
-          } catch (err) {
-            console.log("Error requesting call info", err);
-            cb(err);
+          } catch (ex) {
+            console.log("Error requesting call info", ex);
+            cb(ex);
           }
         }.bind(this)
       );
     }
   };
 
   return Client;
 })(jQuery);
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -480,19 +480,19 @@ loop.contacts = (function(_, mozL10n) {
         case "edit":
           this.props.startForm("contacts_edit", contact);
           break;
         case "remove":
           navigator.mozLoop.confirm({
             message: mozL10n.get("confirm_delete_contact_alert"),
             okButton: mozL10n.get("confirm_delete_contact_remove_button"),
             cancelButton: mozL10n.get("confirm_delete_contact_cancel_button")
-          }, (err, result) => {
-            if (err) {
-              throw err;
+          }, (error, result) => {
+            if (error) {
+              throw error;
             }
 
             if (!result) {
               return;
             }
 
             navigator.mozLoop.contacts.remove(contact._guid, err => {
               if (err) {
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -480,19 +480,19 @@ loop.contacts = (function(_, mozL10n) {
         case "edit":
           this.props.startForm("contacts_edit", contact);
           break;
         case "remove":
           navigator.mozLoop.confirm({
             message: mozL10n.get("confirm_delete_contact_alert"),
             okButton: mozL10n.get("confirm_delete_contact_remove_button"),
             cancelButton: mozL10n.get("confirm_delete_contact_cancel_button")
-          }, (err, result) => {
-            if (err) {
-              throw err;
+          }, (error, result) => {
+            if (error) {
+              throw error;
             }
 
             if (!result) {
               return;
             }
 
             navigator.mozLoop.contacts.remove(contact._guid, err => {
               if (err) {
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -715,20 +715,27 @@ loop.panel = (function(_, mozL10n) {
 
     onDocumentVisible: function() {
       // We would use onDocumentHidden to null out the data ready for the next
       // opening. However, this seems to cause an awkward glitch in the display
       // when opening the panel, and it seems cleaner just to update the data
       // even if there's a small delay.
 
       this.props.mozLoop.getSelectedTabMetadata(function callback(metadata) {
+        // Bail out when the component is not mounted (anymore).
+        // This occurs during test runs. See bug 1174611 for more info.
+        if (!this.isMounted()) {
+          return;
+        }
+
         var previewImage = metadata.favicon || "";
         var description = metadata.title || metadata.description;
         var url = metadata.url;
         this.setState({
+          checked: false,
           previewImage: previewImage,
           description: description,
           url: url
         });
       }.bind(this));
     },
 
     onCheckboxChange: function(newState) {
@@ -760,30 +767,30 @@ loop.panel = (function(_, mozL10n) {
         // Empty catch - if there's an error, then we won't show the context.
       }
 
       var contextClasses = React.addons.classSet({
         context: true,
         hide: !hostname ||
           !this.props.mozLoop.getLoopPref("contextInConversations.enabled")
       });
-      var thumbnail = this.state.previewImage || "loop/shared/img/icons-16x16.svg#globe";
 
       return (
         React.createElement("div", {className: "new-room-view"}, 
           React.createElement("div", {className: contextClasses}, 
-            React.createElement(Checkbox, {label: mozL10n.get("context_inroom_label"), 
+            React.createElement(Checkbox, {checked: this.state.checked, 
+                      label: mozL10n.get("context_inroom_label"), 
                       onChange: this.onCheckboxChange}), 
-            React.createElement("div", {className: "context-content"}, 
-              React.createElement("img", {className: "context-preview", src: thumbnail}), 
-              React.createElement("span", {className: "context-description"}, 
-                this.state.description, 
-                React.createElement("span", {className: "context-url"}, hostname)
-              )
-            )
+            React.createElement(sharedViews.ContextUrlView, {
+              allowClick: false, 
+              description: this.state.description, 
+              showContextTitle: false, 
+              thumbnail: this.state.previewImage, 
+              url: this.state.url, 
+              useDesktopPaths: true})
           ), 
           React.createElement("button", {className: "btn btn-info new-room-button", 
                   onClick: this.handleCreateButtonClick, 
                   disabled: this.props.pendingOperation}, 
             mozL10n.get("rooms_new_room_button_label")
           )
         )
       );
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -715,20 +715,27 @@ loop.panel = (function(_, mozL10n) {
 
     onDocumentVisible: function() {
       // We would use onDocumentHidden to null out the data ready for the next
       // opening. However, this seems to cause an awkward glitch in the display
       // when opening the panel, and it seems cleaner just to update the data
       // even if there's a small delay.
 
       this.props.mozLoop.getSelectedTabMetadata(function callback(metadata) {
+        // Bail out when the component is not mounted (anymore).
+        // This occurs during test runs. See bug 1174611 for more info.
+        if (!this.isMounted()) {
+          return;
+        }
+
         var previewImage = metadata.favicon || "";
         var description = metadata.title || metadata.description;
         var url = metadata.url;
         this.setState({
+          checked: false,
           previewImage: previewImage,
           description: description,
           url: url
         });
       }.bind(this));
     },
 
     onCheckboxChange: function(newState) {
@@ -760,30 +767,30 @@ loop.panel = (function(_, mozL10n) {
         // Empty catch - if there's an error, then we won't show the context.
       }
 
       var contextClasses = React.addons.classSet({
         context: true,
         hide: !hostname ||
           !this.props.mozLoop.getLoopPref("contextInConversations.enabled")
       });
-      var thumbnail = this.state.previewImage || "loop/shared/img/icons-16x16.svg#globe";
 
       return (
         <div className="new-room-view">
           <div className={contextClasses}>
-            <Checkbox label={mozL10n.get("context_inroom_label")}
+            <Checkbox checked={this.state.checked}
+                      label={mozL10n.get("context_inroom_label")}
                       onChange={this.onCheckboxChange} />
-            <div className="context-content">
-              <img className="context-preview" src={thumbnail} />
-              <span className="context-description">
-                {this.state.description}
-                <span className="context-url">{hostname}</span>
-              </span>
-            </div>
+            <sharedViews.ContextUrlView
+              allowClick={false}
+              description={this.state.description}
+              showContextTitle={false}
+              thumbnail={this.state.previewImage}
+              url={this.state.url}
+              useDesktopPaths={true} />
           </div>
           <button className="btn btn-info new-room-button"
                   onClick={this.handleCreateButtonClick}
                   disabled={this.props.pendingOperation}>
             {mozL10n.get("rooms_new_room_button_label")}
           </button>
         </div>
       );
--- a/browser/components/loop/content/js/roomStore.js
+++ b/browser/components/loop/content/js/roomStore.js
@@ -518,19 +518,19 @@ loop.store = loop.store || {};
           setTimeout(function() {
             this.dispatchAction(new sharedActions.UpdateRoomContextDone());
           }.bind(this), 0);
           return;
         }
 
         this.setStoreState({error: null});
         this._mozLoop.rooms.update(actionData.roomToken, roomData,
-          function(err, data) {
-            var action = err ?
-              new sharedActions.UpdateRoomContextError({ error: err }) :
+          function(error, data) {
+            var action = error ?
+              new sharedActions.UpdateRoomContextError({ error: error }) :
               new sharedActions.UpdateRoomContextDone();
             this.dispatchAction(action);
           }.bind(this));
       }.bind(this));
     },
 
     /**
      * Handles the updateRoomContextDone action.
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -87,19 +87,20 @@ loop.roomViews = (function(mozL10n) {
 
       this.props.dispatcher.dispatch(new sharedActions.AddSocialShareProvider());
     },
 
     handleProviderClick: function(event) {
       event.preventDefault();
 
       var origin = event.currentTarget.dataset.provider;
-      var provider = this.props.socialShareProviders.filter(function(provider) {
-        return provider.origin == origin;
-      })[0];
+      var provider = this.props.socialShareProviders
+                         .filter(function(socialProvider) {
+                           return socialProvider.origin == origin;
+                         })[0];
 
       this.props.dispatcher.dispatch(new sharedActions.ShareRoomUrl({
         provider: provider,
         roomUrl: this.props.roomUrl,
         previews: []
       }));
     },
 
@@ -299,22 +300,22 @@ loop.roomViews = (function(mozL10n) {
         newState.editMode = nextProps.editMode;
         // If we're switching to edit mode, fetch the metadata of the current tab.
         // But _only_ if there's no context currently attached to the room; the
         // checkbox will be disabled in that case.
         if (nextProps.editMode) {
           this.props.mozLoop.getSelectedTabMetadata(function(metadata) {
             var previewImage = metadata.favicon || "";
             var description = metadata.title || metadata.description;
-            var url = metadata.url;
+            var metaUrl = metadata.url;
             this.setState({
               availableContext: {
                 previewImage: previewImage,
                 description: description,
-                url: url
+                url: metaUrl
               }
            });
           }.bind(this));
         }
       }
       // When we receive an update for the `roomData` property, make sure that
       // the current form fields reflect reality. This is necessary, because the
       // form state is maintained in the components' state.
@@ -642,45 +643,80 @@ loop.roomViews = (function(mozL10n) {
       );
     },
 
     /**
      * Works out if remote video should be rended or not, depending on the
      * room state and other flags.
      *
      * @return {Boolean} True if remote video should be rended.
+     *
+     * XXX Refactor shouldRenderRemoteVideo & shouldRenderLoading into one fn
+     *     that returns an enum
      */
     shouldRenderRemoteVideo: function() {
       switch(this.state.roomState) {
         case ROOM_STATES.HAS_PARTICIPANTS:
           if (this.state.remoteVideoEnabled) {
             return true;
           }
 
           if (this.state.mediaConnected) {
             // since the remoteVideo hasn't yet been enabled, if the
             // media is connected, then we should be displaying an avatar.
             return false;
           }
 
           return true;
 
+        case ROOM_STATES.READY:
+        case ROOM_STATES.INIT:
+        case ROOM_STATES.JOINING:
         case ROOM_STATES.SESSION_CONNECTED:
         case ROOM_STATES.JOINED:
+        case ROOM_STATES.MEDIA_WAIT:
           // this case is so that we don't show an avatar while waiting for
           // the other party to connect
           return true;
 
+        case ROOM_STATES.CLOSING:
+          return true;
+
         default:
-          console.warn("StandaloneRoomView.shouldRenderRemoteVideo:" +
+          console.warn("DesktopRoomConversationView.shouldRenderRemoteVideo:" +
             " unexpected roomState: ", this.state.roomState);
           return true;
       }
     },
 
+    /**
+     * Should we render a visual cue to the user (e.g. a spinner) that a local
+     * stream is on its way from the camera?
+     *
+     * @returns {boolean}
+     * @private
+     */
+    _shouldRenderLocalLoading: function () {
+      return this.state.roomState === ROOM_STATES.MEDIA_WAIT &&
+             !this.state.localSrcVideoObject;
+    },
+
+    /**
+     * Should we render a visual cue to the user (e.g. a spinner) that a remote
+     * stream is on its way from the other user?
+     *
+     * @returns {boolean}
+     * @private
+     */
+    _shouldRenderRemoteLoading: function() {
+      return this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
+             !this.state.remoteSrcVideoObject &&
+             !this.state.mediaConnected;
+    },
+
     render: function() {
       if (this.state.roomName) {
         this.setTitle(this.state.roomName);
       }
 
       var localStreamClasses = React.addons.classSet({
         local: true,
         "local-stream": true,
@@ -729,23 +765,25 @@ loop.roomViews = (function(mozL10n) {
                 socialShareProviders: this.state.socialShareProviders}), 
               React.createElement("div", {className: "video-layout-wrapper"}, 
                 React.createElement("div", {className: "conversation room-conversation"}, 
                   React.createElement("div", {className: "media nested"}, 
                     React.createElement("div", {className: "video_wrapper remote_wrapper"}, 
                       React.createElement("div", {className: "video_inner remote focus-stream"}, 
                         React.createElement(sharedViews.MediaView, {displayAvatar: !this.shouldRenderRemoteVideo(), 
                           posterUrl: this.props.remotePosterUrl, 
+                          isLoading: this._shouldRenderRemoteLoading(), 
                           mediaType: "remote", 
                           srcVideoObject: this.state.remoteSrcVideoObject})
                       )
                     ), 
                     React.createElement("div", {className: localStreamClasses}, 
                       React.createElement(sharedViews.MediaView, {displayAvatar: this.state.videoMuted, 
                         posterUrl: this.props.localPosterUrl, 
+                        isLoading: this._shouldRenderLocalLoading(), 
                         mediaType: "local", 
                         srcVideoObject: this.state.localSrcVideoObject})
                     )
                   ), 
                   React.createElement(sharedViews.ConversationToolbar, {
                     dispatcher: this.props.dispatcher, 
                     video: {enabled: !this.state.videoMuted, visible: true}, 
                     audio: {enabled: !this.state.audioMuted, visible: true}, 
@@ -756,17 +794,20 @@ loop.roomViews = (function(mozL10n) {
               ), 
               React.createElement(DesktopRoomContextView, {
                 dispatcher: this.props.dispatcher, 
                 error: this.state.error, 
                 savingContext: this.state.savingContext, 
                 mozLoop: this.props.mozLoop, 
                 roomData: roomData, 
                 show: !shouldRenderInvitationOverlay && shouldRenderContextView}), 
-              React.createElement(sharedViews.TextChatView, {dispatcher: this.props.dispatcher})
+              React.createElement(sharedViews.TextChatView, {
+                dispatcher: this.props.dispatcher, 
+                showAlways: false, 
+                showRoomName: false})
             )
           );
         }
       }
     }
   });
 
   return {
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -87,19 +87,20 @@ loop.roomViews = (function(mozL10n) {
 
       this.props.dispatcher.dispatch(new sharedActions.AddSocialShareProvider());
     },
 
     handleProviderClick: function(event) {
       event.preventDefault();
 
       var origin = event.currentTarget.dataset.provider;
-      var provider = this.props.socialShareProviders.filter(function(provider) {
-        return provider.origin == origin;
-      })[0];
+      var provider = this.props.socialShareProviders
+                         .filter(function(socialProvider) {
+                           return socialProvider.origin == origin;
+                         })[0];
 
       this.props.dispatcher.dispatch(new sharedActions.ShareRoomUrl({
         provider: provider,
         roomUrl: this.props.roomUrl,
         previews: []
       }));
     },
 
@@ -299,22 +300,22 @@ loop.roomViews = (function(mozL10n) {
         newState.editMode = nextProps.editMode;
         // If we're switching to edit mode, fetch the metadata of the current tab.
         // But _only_ if there's no context currently attached to the room; the
         // checkbox will be disabled in that case.
         if (nextProps.editMode) {
           this.props.mozLoop.getSelectedTabMetadata(function(metadata) {
             var previewImage = metadata.favicon || "";
             var description = metadata.title || metadata.description;
-            var url = metadata.url;
+            var metaUrl = metadata.url;
             this.setState({
               availableContext: {
                 previewImage: previewImage,
                 description: description,
-                url: url
+                url: metaUrl
               }
            });
           }.bind(this));
         }
       }
       // When we receive an update for the `roomData` property, make sure that
       // the current form fields reflect reality. This is necessary, because the
       // form state is maintained in the components' state.
@@ -642,45 +643,80 @@ loop.roomViews = (function(mozL10n) {
       );
     },
 
     /**
      * Works out if remote video should be rended or not, depending on the
      * room state and other flags.
      *
      * @return {Boolean} True if remote video should be rended.
+     *
+     * XXX Refactor shouldRenderRemoteVideo & shouldRenderLoading into one fn
+     *     that returns an enum
      */
     shouldRenderRemoteVideo: function() {
       switch(this.state.roomState) {
         case ROOM_STATES.HAS_PARTICIPANTS:
           if (this.state.remoteVideoEnabled) {
             return true;
           }
 
           if (this.state.mediaConnected) {
             // since the remoteVideo hasn't yet been enabled, if the
             // media is connected, then we should be displaying an avatar.
             return false;
           }
 
           return true;
 
+        case ROOM_STATES.READY:
+        case ROOM_STATES.INIT:
+        case ROOM_STATES.JOINING:
         case ROOM_STATES.SESSION_CONNECTED:
         case ROOM_STATES.JOINED:
+        case ROOM_STATES.MEDIA_WAIT:
           // this case is so that we don't show an avatar while waiting for
           // the other party to connect
           return true;
 
+        case ROOM_STATES.CLOSING:
+          return true;
+
         default:
-          console.warn("StandaloneRoomView.shouldRenderRemoteVideo:" +
+          console.warn("DesktopRoomConversationView.shouldRenderRemoteVideo:" +
             " unexpected roomState: ", this.state.roomState);
           return true;
       }
     },
 
+    /**
+     * Should we render a visual cue to the user (e.g. a spinner) that a local
+     * stream is on its way from the camera?
+     *
+     * @returns {boolean}
+     * @private
+     */
+    _shouldRenderLocalLoading: function () {
+      return this.state.roomState === ROOM_STATES.MEDIA_WAIT &&
+             !this.state.localSrcVideoObject;
+    },
+
+    /**
+     * Should we render a visual cue to the user (e.g. a spinner) that a remote
+     * stream is on its way from the other user?
+     *
+     * @returns {boolean}
+     * @private
+     */
+    _shouldRenderRemoteLoading: function() {
+      return this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
+             !this.state.remoteSrcVideoObject &&
+             !this.state.mediaConnected;
+    },
+
     render: function() {
       if (this.state.roomName) {
         this.setTitle(this.state.roomName);
       }
 
       var localStreamClasses = React.addons.classSet({
         local: true,
         "local-stream": true,
@@ -729,23 +765,25 @@ loop.roomViews = (function(mozL10n) {
                 socialShareProviders={this.state.socialShareProviders} />
               <div className="video-layout-wrapper">
                 <div className="conversation room-conversation">
                   <div className="media nested">
                     <div className="video_wrapper remote_wrapper">
                       <div className="video_inner remote focus-stream">
                         <sharedViews.MediaView displayAvatar={!this.shouldRenderRemoteVideo()}
                           posterUrl={this.props.remotePosterUrl}
+                          isLoading={this._shouldRenderRemoteLoading()}
                           mediaType="remote"
                           srcVideoObject={this.state.remoteSrcVideoObject} />
                       </div>
                     </div>
                     <div className={localStreamClasses}>
                       <sharedViews.MediaView displayAvatar={this.state.videoMuted}
                         posterUrl={this.props.localPosterUrl}
+                        isLoading={this._shouldRenderLocalLoading()}
                         mediaType="local"
                         srcVideoObject={this.state.localSrcVideoObject} />
                     </div>
                   </div>
                   <sharedViews.ConversationToolbar
                     dispatcher={this.props.dispatcher}
                     video={{enabled: !this.state.videoMuted, visible: true}}
                     audio={{enabled: !this.state.audioMuted, visible: true}}
@@ -756,17 +794,20 @@ loop.roomViews = (function(mozL10n) {
               </div>
               <DesktopRoomContextView
                 dispatcher={this.props.dispatcher}
                 error={this.state.error}
                 savingContext={this.state.savingContext}
                 mozLoop={this.props.mozLoop}
                 roomData={roomData}
                 show={!shouldRenderInvitationOverlay && shouldRenderContextView} />
-              <sharedViews.TextChatView dispatcher={this.props.dispatcher} />
+              <sharedViews.TextChatView
+                dispatcher={this.props.dispatcher}
+                showAlways={false}
+                showRoomName={false} />
             </div>
           );
         }
       }
     }
   });
 
   return {
--- a/browser/components/loop/content/shared/css/common.css
+++ b/browser/components/loop/content/shared/css/common.css
@@ -496,8 +496,64 @@ html[dir="rtl"] .checkbox {
 
 .checkbox.disabled {
   border: 1px solid #909090;
 }
 
 .checkbox.checked.disabled {
   background-image: url("../img/check.svg#check-disabled");
 }
+
+/* ContextUrlView classes */
+
+.context-content {
+  color: black;
+  text-align: left;
+}
+
+html[dir="rtl"] .context-content {
+  text-align: right;
+}
+
+.context-content > p {
+  font-weight: bold;
+  margin-bottom: .8em;
+  margin-top: 0;
+}
+
+.context-wrapper {
+  border: 1px solid #0096dd;
+  border-radius: 3px;
+  background: #fff;
+  padding: .8em;
+  /* Use the flex row mode to position the elements next to each other. */
+  display: flex;
+  flex-flow: row nowrap;
+  line-height: 1.1em;
+}
+
+.context-wrapper > .context-preview {
+  float: left;
+  /* 16px is standard height/width for a favicon */
+  width: 16px;
+  max-height: 16px;
+  margin-right: .8em;
+  flex: 0 1 auto;
+}
+
+html[dir="rtl"] .context-wrapper > .context-preview {
+  float: left;
+  margin-left: .8em;
+  margin-right: 0;
+}
+
+.context-wrapper > .context-description {
+  flex: 0 1 auto;
+  display: block;
+  color: black;
+}
+
+.context-wrapper > .context-description > .context-url {
+  display: block;
+  color: #59A1D7;
+  font-weight: 700;
+  clear: both;
+}
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -255,27 +255,21 @@
 }
 
 .fx-embedded .no-video {
   background: black none repeat scroll 0% 0%;
   height: 100%;
   width: 100%;
 }
 
-.standalone .local-stream,
-.standalone .remote-inset-stream {
+.standalone .local-stream {
   /* required to have it superimposed to the control toolbar */
   z-index: 1001;
 }
 
-.standalone .room-conversation .local-stream,
-.standalone .room-conversation .remote-inset-stream {
-  box-shadow: none;
-}
-
 /* Side by side video elements */
 
 .conversation .media.side-by-side .focus-stream {
   width: 50%;
   float: left;
 }
 
 .conversation .media.side-by-side .local-stream,
@@ -554,17 +548,50 @@
   /*
    * Expand to fill the available space, since there is no video any
    * intrinsic width. XXX should really change to an <img> for clarity
    */
   height: 100%;
   width: 100%;
 }
 
-.local .avatar {
+/*
+ * Used to center the loading spinner
+ */
+.focus-stream, .remote {
+  position: relative;
+}
+
+.loading-stream {
+  /* vertical and horizontal center */
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  margin-left: -50px;
+  margin-top: -50px;
+  width: 100px;
+  height: 100px;
+
+  /* place the animation */
+  background-image: url("../img/spinner.svg");
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: 40%;
+
+  /* 12 is the number of lines in the spinner image */
+  animation: rotate-spinner 1s steps(12, end) infinite;
+}
+
+@keyframes rotate-spinner {
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+.conversation .local .avatar {
   position: absolute;
   z-index: 1;
 }
 
 .remote .avatar {
   /* make visually distinct from local avatar */
   opacity: 0.25;
 }
@@ -647,26 +674,25 @@
  * */
 html, .fx-embedded, #main,
 .video-layout-wrapper,
 .conversation {
   height: 100%;
 }
 
 /* We use 641px rather than 640, as min-width and max-width are inclusive */
-@media screen and (min-width:641px) {
-  .standalone .conversation-toolbar {
+@media screen and (min-width: 641px) {
+  .standalone .conversation .conversation-toolbar {
     position: absolute;
     bottom: 0;
     left: 0;
     right: 0;
   }
 
-  .standalone .local-stream,
-  .standalone .remote-inset-stream {
+  .standalone .conversation .local-stream {
     position: absolute;
     right: 15px;
     bottom: 15px;
     width: 20%;
     height: 20%;
     max-width: 400px;
     max-height: 300px;
   }
@@ -701,26 +727,21 @@ html, .fx-embedded, #main,
   .standalone .media {
     height: 90%;
   }
 
   .standalone .media.nested {
     min-height: 500px;
   }
 
-  .standalone .remote-inset-stream {
-    display: none;
-  }
-
-  .standalone .local-stream {
+  .standalone .conversation .local-stream {
     flex: 1;
     min-width: 120px;
     min-height: 150px;
     width: 100%;
-    box-shadow: none;
   }
 
   /* Nested video elements */
   .standalone .conversation .media.nested {
     display: flex;
     flex-direction: column;
     align-items: center;
     justify-content: center;
@@ -754,39 +775,52 @@ html, .fx-embedded, #main,
   position: relative;
   height: 100%;
 }
 
 .room-conversation-wrapper header {
   background: #000;
   height: 50px;
   text-align: left;
-  width: 75%;
+  margin: 0 10px;
+}
+
+html[dir="rtl"] .room-conversation-wrapper header {
+  text-align: right;
 }
 
 .room-conversation-wrapper header h1 {
   font-size: 1.5em;
   color: #fff;
   line-height: 50px;
-  text-indent: 60px;
+  text-indent: 40px;
   background-image: url("../img/firefox-logo.png");
   background-size: 30px;
-  background-position: 20px;
+  background-position: 0 center;
   background-repeat: no-repeat;
   display: inline-block;
+  margin: 0 10px;
+}
+
+html[dir="rtl"] .room-conversation-wrapper header h1 {
+  background-position: 100% center;
 }
 
 .room-conversation-wrapper header a {
   float: right;
 }
 
+html[dir="rtl"] .room-conversation-wrapper header a {
+  float: left;
+}
+
 .room-conversation-wrapper header .icon-help {
   display: inline-block;
   background-size: contain;
-  margin-top: 20px;
+  margin-top: 15px;
   width: 20px;
   height: 20px;
   background: transparent url("../img/svg/glyph-help-16x16.svg") no-repeat;
 }
 
 .fx-embedded .room-conversation .conversation-toolbar .btn-hangup {
   background-image: url("../img/icons-16x16.svg#leave");
 }
@@ -882,16 +916,24 @@ body[platform="win"] .share-service-drop
 .dropdown-menu-item:hover > .icon-add-share-service {
   background-image: url("../img/icons-16x16.svg#add-hover");
 }
 
 .dropdown-menu-item:hover:active > .icon-add-share-service {
   background-image: url("../img/icons-16x16.svg#add-active");
 }
 
+.context-url-view-wrapper {
+  padding-left: 1em;
+  padding-right: 1em;
+  padding-bottom: 0.5em;
+  margin-bottom: 0.5em;
+  background-color: #E8F6FE;
+}
+
 .room-context {
   background: rgba(0,0,0,.6);
   border-top: 2px solid #444;
   border-bottom: 2px solid #444;
   padding: .5rem;
   max-height: 400px;
   position: absolute;
   left: 0;
@@ -1074,16 +1116,223 @@ html[dir="rtl"] .room-context-btn-edit {
   right: auto;
   left: 8px;
 }
 
 html[dir="rtl"] .room-context-btn-edit {
   left: 20px;
 }
 
+.media-layout {
+  /* 50px is the header, 3em is the footer. */
+  height: calc(100% - 50px - 3em);
+}
+
+.media-layout > .media-wrapper {
+  display: flex;
+  flex-flow: column wrap;
+  /* 64px for .conversation-toolbar */
+  height: calc(100% - 64px);
+  margin: 0 10px;
+}
+
+.media-wrapper > .focus-stream {
+  /* We want this to be the width, minus 200px which is for the right-side text
+     chat and video displays. */
+  width: calc(100% - 200px);
+  /* 100% height to fill up media-layout, thus forcing other elements into the
+     second column that's 200px wide */
+  height: 100%;
+  background-color: #4E4E4E;
+}
+
+.media-wrapper > .remote {
+  /* Works around an issue with object-fit: cover in Google Chrome - it doesn't
+     currently crop but overlaps the surrounding elements.
+     https://code.google.com/p/chromium/issues/detail?id=400829 */
+  overflow: hidden;
+}
+
+.media-wrapper > .remote > .remote-video {
+  object-fit: cover;
+}
+
+/* Note: we can't use "display: flex;" for the text-chat-view itself,
+   as this lets it overflow the expected column heights, and we can't
+   fix its height. */
+.media-wrapper > .text-chat-view {
+  flex: 0 0 auto;
+  /* Text chat is a fixed 200px width for normal displays. */
+  width: 200px;
+  height: 100%;
+}
+
+.media-wrapper.showing-local-streams > .text-chat-view {
+  /* When we're displaying the local streams, then we need to make the text
+     chat view a bit shorter to give room. */
+  height: calc(100% - 150px);
+}
+
+.media-wrapper.showing-local-streams.receiving-screen-share > .text-chat-view {
+  /* When we're displaying the local streams, then we need to make the text
+     chat view a bit shorter to give room. */
+  height: calc(100% - 300px);
+}
+
+/* Temporarily slaved from .media-wrapper until we use it in more places
+   to avoid affecting the conversation window on desktop. */
+.media-wrapper > .text-chat-view > .text-chat-entries {
+  /* 40px is the height of .text-chat-box. */
+  height: calc(100% - 40px);
+}
+
+.media-wrapper > .text-chat-disabled > .text-chat-entries {
+  /* When text chat is disabled, the entries box should be 100% height. */
+  height: 100%;
+}
+
+.media-wrapper > .local {
+  flex: 0 1 auto;
+  width: 200px;
+  height: 150px;
+}
+
+.media-wrapper.receiving-screen-share > .screen {
+  order: 1;
+}
+
+.media-wrapper.receiving-screen-share > .text-chat-view {
+  order: 2;
+}
+
+.media-wrapper.receiving-screen-share > .remote {
+  order: 3;
+  flex: 0 1 auto;
+  width: 200px;
+  height: 150px;
+}
+
+.media-wrapper.receiving-screen-share > .local {
+  order: 4;
+}
+
+@media screen and (max-width:640px) {
+  .media-layout {
+    /* 50px is height of header, 25px is height of footer. */
+    height: calc(100% - 50px - 25px);
+  }
+
+  .media-layout > .media-wrapper {
+    flex-direction: row;
+    margin: 0;
+    width: 100%;
+    /* conversation toolbar is 38px in narrow mode */
+    height: calc(100% - 38px);
+  }
+
+  .media-wrapper > .focus-stream {
+    width: 100%;
+    /* A reasonable height */
+    height: 70%;
+  }
+
+  .media-wrapper.receiving-screen-share > .focus-stream {
+    height: 50%;
+  }
+
+  /* Temporarily slaved from .media-wrapper until we use it in more places
+     to avoid affecting the conversation window on desktop. */
+  .media-wrapper > .text-chat-view > .text-chat-entries {
+    /* 40px is the height of .text-chat-box. */
+    height: calc(100% - 40px);
+    width: 100%;
+  }
+
+  .media-wrapper > .text-chat-disabled > .text-chat-entries {
+    /* When text chat is disabled, the entries box should be 100% height. */
+    height: 100%;
+  }
+
+  .media-wrapper > .local {
+    /* Position over the remote video */
+    position: absolute;
+    /* Make sure its on top */
+    z-index: 1001;
+    margin: 3px;
+    right: 0;
+    /* 29px is (30% of 50px high header) + (height toolbar (38px) +
+       height footer (25px) - height header (50px)) */
+    bottom: calc(30% + 29px);
+    width: 120px;
+    height: 120px;
+  }
+
+  html[dir="rtl"] .media-wrapper > .local {
+    right: auto;
+    left: 0;
+  }
+
+  .media-wrapper > .text-chat-view {
+    order: 3;
+    flex: 1 1 auto;
+    width: 100%;
+  }
+
+  .media-wrapper > .text-chat-view,
+  .media-wrapper.showing-local-streams > .text-chat-view,
+  .media-wrapper.showing-local-streams.receiving-screen-share > .text-chat-view {
+    /* The remaining 30% that the .focus-stream doesn't use. */
+    height: 30%;
+  }
+
+  .media-wrapper.receiving-screen-share > .screen {
+    order: 1;
+  }
+
+  .media-wrapper.receiving-screen-share > .remote {
+    /* Screen shares have remote & local video side-by-side on narrow screens */
+    order: 2;
+    flex: 1 1 auto;
+    height: 20%;
+    /* Ensure no previously specified widths take effect, and we take up no more
+       than half the width. */
+    width: auto;
+    max-width: 50%;
+  }
+
+  .media-wrapper.receiving-screen-share > .remote > .remote-video {
+      /* Reset the object-fit for this. */
+    object-fit: contain;
+  }
+
+  .media-wrapper.receiving-screen-share > .local {
+    /* Screen shares have remote & local video side-by-side on narrow screens */
+    order: 3;
+    flex: 1 1 auto;
+    height: 20%;
+    /* Ensure no previously specified widths take effect, and we take up no more
+       than half the width. */
+    width: auto;
+    max-width: 50%;
+    /* This cancels out the absolute positioning when it's just remote video. */
+    position: relative;
+    bottom: auto;
+    right: auto;
+    margin: 0;
+  }
+
+  .media-wrapper.receiving-screen-share > .text-chat-view {
+    order: 4;
+  }
+}
+
+.standalone > #main > .room-conversation-wrapper > .media-layout > .conversation-toolbar {
+  border: none;
+}
+
 /* Standalone rooms */
 
 .standalone .room-conversation-wrapper {
   position: relative;
   height: 100%;
   background: #000;
 }
 
@@ -1102,16 +1351,21 @@ html[dir="rtl"] .room-context-btn-edit {
   left: 25%;
   z-index: 1000;
   /* `width` here is specified by the design spec. */
   width: 250px;
   color: #fff;
   box-sizing: content-box;
 }
 
+html[dir="rtl"] .standalone .room-conversation-wrapper .room-inner-info-area {
+  right: 25%;
+  left: auto;
+}
+
 .standalone .prompt-media-message {
   padding-top: 136px; /* Fallback for browsers that don't support calc() */
   /* 122px is 2x the intrinsic height of the background-image, and
      1rem puts one line of margin between the background-image and
      supporting text. */
   padding-top: calc(122px + 1rem);
   color: #000;
   background-color: #fff;
@@ -1147,33 +1401,16 @@ html[dir="rtl"] .room-context-btn-edit {
 
 .standalone .room-conversation-wrapper .room-inner-info-area a.btn {
   padding: .5em 3em .3em 3em;
   border-radius: 3px;
   font-weight: normal;
   max-width: 400px;
 }
 
-.standalone-room-info {
-  position: absolute;
-  display: block;
-  top: 0;
-  right: 10px;
-  /* 20px is 10px for left and right margins. */
-  width: calc(25% - 20px);
-  z-index: 2000000;
-  font-size: 1.2em;
-  padding: .4em;
-  height: 100%;
-}
-
-.standalone-room-info > h2 {
-  color: #fff;
-}
-
 .standalone-context-url {
   color: #fff;
   /* Try and keep clear of local video */
   height: 40%;
 }
 
 .standalone-context-url.screen-share-active {
   /* Try and keep clear of remote video when screensharing */
@@ -1221,108 +1458,115 @@ html[dir="rtl"] .room-context-btn-edit {
   flex-flow: column nowrap;
 }
 
 .fx-embedded .video-layout-wrapper {
   flex: 1 1 auto;
 }
 
 .text-chat-view {
-  background: #fff;
+  background: white;
 }
 
 .fx-embedded .text-chat-view {
   flex: 1 0 auto;
   display: flex;
   flex-flow: column nowrap;
 }
 
 .fx-embedded .text-chat-entries {
   flex: 1 1 auto;
   max-height: 120px;
   min-height: 60px;
   padding: .7em .5em 0;
 }
 
-.fx-embedded .text-chat-box {
+.text-chat-box {
   flex: 0 0 auto;
   max-height: 40px;
   min-height: 40px;
   width: 100%;
 }
 
 .text-chat-entries {
-  overflow: scroll;
+  overflow: auto;
 }
 
 .text-chat-entry {
   text-align: end;
   margin-bottom: 1.5em;
 }
 
-.text-chat-entry > span {
+.text-chat-entry > p {
   border-width: 1px;
   border-style: solid;
   border-color: #0095dd;
   border-radius: 10000px;
   padding: .5em 1em;
+  /* Drop the default margins from the 'p' element. */
+  margin: 0;
+  /* inline-block stops the elements taking 100% of the text-chat-view width */
+  display: inline-block;
+  /* Split really long strings with no spaces appropriately, whilst limiting the
+     width to 100%. */
+  max-width: 100%;
+  word-wrap: break-word;
 }
 
 .text-chat-entry.received {
   text-align: start;
 }
 
-.text-chat-entry.received > span {
+.text-chat-entry.received > p {
   border-color: #d8d8d8;
 }
 
+.text-chat-entry.special > p {
+  border: none;
+}
+
+.text-chat-entry.special.room-name {
+  color: black;
+  font-weight: bold;
+  text-align: start;
+  background-color: #E8F6FE;
+  padding-bottom: 0;
+  margin-bottom: 0;
+}
+
 .text-chat-box {
   margin: auto;
 }
 
 .text-chat-box > form > input {
   width: 100%;
   height: 40px;
   padding: 0 .5em .5em;
   font-size: 1.1em;
-}
-
-.fx-embedded .text-chat-box > form > input {
   border: 0;
   border-top: 1px solid #999;
 }
 
-@media screen and (max-width:640px) {
-  .standalone-room-info {
-    /* This isn't perfect, we just center the heading for now. Bug 1141493
-       should fix this. */
-    position: absolute;
-    width: 100%;
-    right: 0px;
+/* turn the visible border blue as a visual indicator of focus */
+.text-chat-box > form > input:focus {
+  border-top: 1px solid #66c9f2;
+}
 
-    /* Override the 100% specified in the .standalone-room-info selector
-       block so that this div doesn't take over the _whole_ screen and
-       transparently occlude UI widgetry (like the Join button), making
-       it unusable. */
-    height: auto;
-  }
-
+@media screen and (max-width:640px) {
   .standalone-context-url {
     /* XXX We haven't got UX for standalone yet, so temporarily not displaying
        on narrow window widths. See bug 1153827. */
     display: none;
   }
 
   /* Rooms specific responsive styling */
   .standalone .room-conversation {
     background: #000;
   }
-  .room-conversation-wrapper header {
-    width: 100%;
-  }
+
   .standalone .room-conversation-wrapper .room-inner-info-area {
     right: 0;
     margin: auto;
     width: 100%;
     left: 0;
   }
   .standalone .room-conversation-wrapper .video-layout-wrapper {
     /* 50px: header's height; 25px: footer's height */
@@ -1334,17 +1578,17 @@ html[dir="rtl"] .room-context-btn-edit {
   .standalone .room-conversation .video_wrapper.remote_wrapper.not-joined {
     width: 100%;
   }
 
   .standalone .conversation-toolbar {
     height: 38px;
     padding: 8px;
   }
-  .standalone .focus-stream {
+  .standalone .conversation .focus-stream {
     /* Set at maximum height, minus height of conversation toolbar */
     height: 100%;
   }
 
   .standalone .media.nested {
     /* This forces the remote video stream to fit within wrapper's height */
     min-height: 0px;
   }
@@ -1395,16 +1639,22 @@ html[dir="rtl"] .room-context-btn-edit {
      convention in video conferencing systems. */
   transform: scale(-1, 1);
   transform-origin: 50% 50% 0;
 }
 
 .remote-video {
   width: 100%;
   height: 100%;
-  display: block;
-  position: absolute;
 }
 
 .screen-share-video {
   width: 100%;
   height: 100%;
 }
+
+/* Make sure the loading spinner always gets the same background */
+.loading-background {
+  background: black;
+  position: absolute;
+  width: 100%;
+  height: 100%;
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/img/spinner.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="171px" height="171px" viewBox="0 0 171 171" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+    <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
+    <title>FX_Hello-glyph-spinner-16x16</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="spinner" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+        <g id="hello-spinner" sketch:type="MSLayerGroup">
+            <g id="Group" sketch:type="MSShapeGroup">
+                <path d="M94.05,34.24275 C94.05,38.966625 90.223875,42.79275 85.5,42.79275 L85.5,42.79275 C80.776125,42.79275 76.95,38.966625 76.95,34.24275 L76.95,8.55 C76.95,3.826125 80.776125,0 85.5,0 L85.5,0 C90.223875,0 94.05,3.826125 94.05,8.55 L94.05,34.24275 L94.05,34.24275 Z" id="Shape" fill="#A4A4A4"></path>
+                <path d="M94.05,162.45 C94.05,167.173875 90.223875,171 85.5,171 L85.5,171 C80.776125,171 76.95,167.173875 76.95,162.45 L76.95,136.75725 C76.95,132.033375 80.776125,128.20725 85.5,128.20725 L85.5,128.20725 C90.223875,128.20725 94.05,132.033375 94.05,136.75725 L94.05,162.45 L94.05,162.45 Z" id="Shape" fill="#FFFFFF"></path>
+                <path d="M34.24275,76.95 C38.966625,76.95 42.79275,80.776125 42.79275,85.5 L42.79275,85.5 C42.79275,90.223875 38.966625,94.05 34.24275,94.05 L8.55,94.05 C3.826125,94.05 0,90.223875 0,85.5 L0,85.5 C0,80.776125 3.826125,76.95 8.55,76.95 L34.24275,76.95 L34.24275,76.95 Z" id="Shape" fill="#616161"></path>
+                <path d="M162.45,76.95 C167.173875,76.95 171,80.776125 171,85.5 L171,85.5 C171,90.223875 167.173875,94.05 162.45,94.05 L136.75725,94.05 C132.033375,94.05 128.20725,90.223875 128.20725,85.5 L128.20725,85.5 C128.20725,80.776125 132.033375,76.95 136.75725,76.95 L162.45,76.95 L162.45,76.95 Z" id="Shape" fill="#C5C5C5"></path>
+                <path d="M36.8398125,103.743562 C40.933125,101.381625 46.1593125,102.781688 48.52125,106.864312 L48.52125,106.864312 C50.8831875,110.957625 49.483125,116.183813 45.4005,118.54575 L23.149125,131.402812 C19.0558125,133.76475 13.829625,132.364688 11.4676875,128.271375 L11.4676875,128.271375 C9.10575,124.18875 10.5058125,118.951875 14.5884375,116.600625 L36.8398125,103.743562 L36.8398125,103.743562 Z" id="Shape" fill="#4F4F4F"></path>
+                <path d="M147.861562,39.607875 C151.944187,37.2459375 157.181062,38.646 159.543,42.728625 L159.543,42.728625 C161.904938,46.81125 160.504875,52.048125 156.42225,54.399375 L134.170875,67.2564375 C130.077563,69.618375 124.851375,68.2183125 122.489438,64.1356875 L122.489438,64.1356875 C120.1275,60.0530625 121.527563,54.8161875 125.610188,52.4649375 L147.861562,39.607875 L147.861562,39.607875 Z" id="Shape" fill="#BABABA"></path>
+                <path d="M103.732875,134.160188 C101.370938,130.077563 102.771,124.840688 106.853625,122.489438 L106.853625,122.489438 C110.93625,120.1275 116.173125,121.527563 118.535063,125.620875 L131.392125,147.87225 C133.754063,151.954875 132.354,157.19175 128.271375,159.543 L128.271375,159.543 C124.18875,161.904938 118.951875,160.504875 116.589938,156.42225 L103.732875,134.160188 L103.732875,134.160188 Z" id="Shape" fill="#E0E0E0"></path>
+                <path d="M39.607875,23.149125 C37.2459375,19.0665 38.646,13.829625 42.728625,11.4676875 L42.728625,11.4676875 C46.81125,9.10575 52.048125,10.5058125 54.4100625,14.599125 L67.267125,36.8505 C69.618375,40.933125 68.2183125,46.1593125 64.1356875,48.52125 L64.1356875,48.52125 C60.042375,50.8831875 54.8161875,49.483125 52.45425,45.4005 L39.607875,23.149125 L39.607875,23.149125 Z" id="Shape" fill="#989898"></path>
+                <path d="M52.5076875,125.652938 C54.8589375,121.559625 60.085125,120.159563 64.1784375,122.510813 L64.1784375,122.510813 C68.27175,124.862063 69.6718125,130.08825 67.3205625,134.181563 L54.4955625,156.454313 C52.1443125,160.547625 46.9074375,161.947688 42.8248125,159.596438 L42.8248125,159.596438 C38.7315,157.245187 37.3314375,152.019 39.6826875,147.925688 L52.5076875,125.652938 L52.5076875,125.652938 Z" id="Shape" fill="#3E3E3E"></path>
+                <path d="M116.504437,14.556375 C118.866375,10.4630625 124.081875,9.063 128.175187,11.41425 L128.175187,11.41425 C132.2685,13.7655 133.668563,19.002375 131.317313,23.085 L118.492313,45.35775 C116.130375,49.4510625 110.904188,50.851125 106.821563,48.499875 L106.821563,48.499875 C102.72825,46.148625 101.328188,40.9224375 103.679438,36.829125 L116.504437,14.556375 L116.504437,14.556375 Z" id="Shape" fill="#B0B0B0"></path>
+                <path d="M125.64225,118.503 C121.548937,116.141062 120.148875,110.914875 122.500125,106.83225 L122.500125,106.83225 C124.851375,102.738937 130.077562,101.338875 134.170875,103.690125 L156.432937,116.515125 C160.52625,118.877062 161.926312,124.10325 159.575063,128.185875 L159.575063,128.185875 C157.223812,132.279187 151.997625,133.67925 147.904313,131.328 L125.64225,118.503 L125.64225,118.503 Z" id="Shape" fill="#CECECE"></path>
+                <path d="M14.556375,54.4955625 C10.4630625,52.1443125 9.063,46.918125 11.41425,42.8248125 L11.41425,42.8248125 C13.7761875,38.7315 19.002375,37.3314375 23.085,39.6826875 L45.35775,52.5076875 C49.4510625,54.869625 50.851125,60.0958125 48.499875,64.1784375 L48.499875,64.1784375 C46.1379375,68.2824375 40.91175,69.6825 36.8184375,67.33125 L14.556375,54.4955625 L14.556375,54.4955625 Z" id="Shape" fill="#7C7C7C"></path>
+            </g>
+        </g>
+    </g>
+</svg>
--- a/browser/components/loop/content/shared/js/actions.js
+++ b/browser/components/loop/content/shared/js/actions.js
@@ -164,16 +164,17 @@ loop.shared.actions = (function() {
      */
     RemotePeerConnected: Action.define("remotePeerConnected", {
     }),
 
     /**
      * Used to notify that the session has a data channel available.
      */
     DataChannelsAvailable: Action.define("dataChannelsAvailable", {
+      available: Boolean
     }),
 
     /**
      * Used to send a message to the other peer.
      */
     SendTextChatMessage: Action.define("sendTextChatMessage", {
       contentType: String,
       message: String
@@ -284,18 +285,17 @@ loop.shared.actions = (function() {
     ScreenSharingState: Action.define("screenSharingState", {
       // One of loop.shared.utils.SCREEN_SHARE_STATES.
       state: String
     }),
 
     /**
      * Used to notify that a shared screen is being received (or not).
      *
-     * XXX this is going to need to be split into two actions so when
-     * can display a spinner when the screen share is pending (bug 1171933)
+     * XXX this should be split into multiple actions to make the code clearer.
      */
     ReceivingScreenShare: Action.define("receivingScreenShare", {
       receiving: Boolean
       // srcVideoObject: Object (only present if receiving is true)
     }),
 
     /**
      * Creates a new room.
--- a/browser/components/loop/content/shared/js/activeRoomStore.js
+++ b/browser/components/loop/content/shared/js/activeRoomStore.js
@@ -315,17 +315,17 @@ loop.store.ActiveRoomStore = (function()
               .then(function(decryptedResult) {
           var realResult = JSON.parse(decryptedResult);
 
           roomInfoData.description = realResult.description;
           roomInfoData.urls = realResult.urls;
           roomInfoData.roomName = realResult.roomName;
 
           dispatcher.dispatch(roomInfoData);
-        }, function(err) {
+        }, function(error) {
           roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.DECRYPT_FAILED;
           dispatcher.dispatch(roomInfoData);
         });
       }.bind(this));
     },
 
     /**
      * Handles the setupRoomInfo action. Sets up the initial room data and
@@ -607,18 +607,17 @@ loop.store.ActiveRoomStore = (function()
       this._mozLoop.setScreenShareState(
         this.getStoreState().windowId,
         actionData.state === SCREEN_SHARE_STATES.ACTIVE);
     },
 
     /**
      * Used to note the current state of receiving screenshare data.
      *
-     * XXX this is going to need to be split into two actions so when
-     * can display a spinner when the screen share is pending (bug 1171933)
+     * XXX this should be split into multiple actions to make the code clearer.
      */
     receivingScreenShare: function(actionData) {
       if (!actionData.receiving &&
           this.getStoreState().remoteVideoDimensions.screen) {
         // Remove the remote video dimensions for type screen as we're not
         // getting the share anymore.
         var newDimensions = _.extend(this.getStoreState().remoteVideoDimensions);
         delete newDimensions.screen;
--- a/browser/components/loop/content/shared/js/mixins.js
+++ b/browser/components/loop/content/shared/js/mixins.js
@@ -282,26 +282,18 @@ loop.shared.mixins = (function() {
     }
   };
 
   /**
    * Media setup mixin. Provides a common location for settings for the media
    * elements and handling updates of the media containers.
    */
   var MediaSetupMixin = {
-
     componentDidMount: function() {
       this.resetDimensionsCache();
-      rootObject.addEventListener("orientationchange", this.updateVideoContainer);
-      rootObject.addEventListener("resize", this.updateVideoContainer);
-    },
-
-    componentWillUnmount: function() {
-      rootObject.removeEventListener("orientationchange", this.updateVideoContainer);
-      rootObject.removeEventListener("resize", this.updateVideoContainer);
     },
 
     /**
      * Resets the dimensions cache, e.g. for when the session is ended, and
      * before a new session, so that we always ensure we see an update when a
      * new session is started.
      */
     resetDimensionsCache: function() {
--- a/browser/components/loop/content/shared/js/otSdkDriver.js
+++ b/browser/components/loop/content/shared/js/otSdkDriver.js
@@ -269,16 +269,20 @@ loop.OTSdkDriver = (function() {
     },
 
     /**
      * Disconnects the sdk session.
      */
     disconnectSession: function() {
       this.endScreenShare();
 
+      this.dispatcher.dispatch(new sharedActions.DataChannelsAvailable({
+        available: false
+      }));
+
       if (this.session) {
         this.session.off("sessionDisconnected streamCreated streamDestroyed connectionCreated connectionDestroyed streamPropertyChanged");
         this.session.disconnect();
         delete this.session;
 
         this._notifyMetricsEvent("Session.connectionDestroyed", "local");
       }
       if (this.publisher) {
@@ -290,16 +294,18 @@ loop.OTSdkDriver = (function() {
       this._noteConnectionLengthIfNeeded(this._getTwoWayMediaStartTime(), performance.now());
 
       // Also, tidy these variables ready for next time.
       delete this._sessionConnected;
       delete this._publisherReady;
       delete this._publishedLocalStream;
       delete this._subscribedRemoteStream;
       delete this._mockPublisherEl;
+      delete this._publisherChannel;
+      delete this._subscriberChannel;
       this.connections = {};
       this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_UNINITIALIZED);
     },
 
     /**
      * Oust all users from an ongoing session. This is typically done when a room
      * owner deletes the room.
      *
@@ -501,26 +507,19 @@ loop.OTSdkDriver = (function() {
      * the stream, and notifying the stores that a share is being
      * received.
      *
      * @param {Stream} stream The SDK Stream:
      * https://tokbox.com/opentok/libraries/client/js/reference/Stream.html
      */
     _handleRemoteScreenShareCreated: function(stream) {
       // Let the stores know first so they can update the display.
-      // XXX We do want to do this - we want them to start re-arranging the
-      // display so that we can a) indicate connecting, b) be ready for
-      // when we get the stream. However, we're currently limited by the fact
-      // the view calculations require the remote (aka screen share) element to
-      // be present and laid out. Hence, we need to drop this for the time being,
-      // and let the client know via _onScreenShareSubscribeCompleted.
-      // Bug 1171933 is going to look at fixing this.
-      // this.dispatcher.dispatch(new sharedActions.ReceivingScreenShare({
-      //  receiving: true
-      // }));
+      this.dispatcher.dispatch(new sharedActions.ReceivingScreenShare({
+        receiving: true
+      }));
 
       // There's no audio for screen shares so we don't need to worry about mute.
       this._mockScreenShareEl = document.createElement("div");
       this.session.subscribe(stream, this._mockScreenShareEl,
         this._getCopyPublisherConfig,
         this._onScreenShareSubscribeCompleted.bind(this));
     },
 
@@ -675,60 +674,62 @@ loop.OTSdkDriver = (function() {
         if (err) {
           console.error(err);
           return;
         }
 
         this._publisherChannel = channel;
 
         channel.on({
-          close: function(event) {
+          close: function(e) {
             // XXX We probably want to dispatch and handle this somehow.
             console.log("Published data channel closed!");
           }
         });
 
         this._checkDataChannelsAvailable();
       }.bind(this));
 
       this.subscriber._.getDataChannel("text", {}, function(err, channel) {
         // Sends will queue until the channel is fully open.
         if (err) {
           console.error(err);
           return;
         }
 
         channel.on({
-          message: function(event) {
+          message: function(ev) {
             try {
               this.dispatcher.dispatch(
-                new sharedActions.ReceivedTextChatMessage(JSON.parse(event.data)));
+                new sharedActions.ReceivedTextChatMessage(JSON.parse(ev.data)));
             } catch (ex) {
               console.error("Failed to process incoming chat message", ex);
             }
           }.bind(this),
 
-          close: function(event) {
+          close: function(e) {
             // XXX We probably want to dispatch and handle this somehow.
             console.log("Subscribed data channel closed!");
           }
         });
 
         this._subscriberChannel = channel;
         this._checkDataChannelsAvailable();
       }.bind(this));
     },
 
     /**
      * Checks to see if all channels have been obtained, and if so it dispatches
      * a notification to the stores to inform them.
      */
     _checkDataChannelsAvailable: function() {
       if (this._publisherChannel && this._subscriberChannel) {
-        this.dispatcher.dispatch(new sharedActions.DataChannelsAvailable());
+        this.dispatcher.dispatch(new sharedActions.DataChannelsAvailable({
+          available: true
+        }));
       }
     },
 
     /**
      * Sends a text chat message on the data channel.
      *
      * @param {String} message The message to send.
      */
@@ -816,16 +817,20 @@ loop.OTSdkDriver = (function() {
      *
      * @param {StreamEvent} event The event details:
      * https://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
      */
     _onRemoteStreamDestroyed: function(event) {
       this._notifyMetricsEvent("Session.streamDestroyed");
 
       if (event.stream.videoType !== "screen") {
+        this.dispatcher.dispatch(new sharedActions.DataChannelsAvailable({
+          available: false
+        }));
+        delete this._subscriberChannel;
         delete this._mockSubscribeEl;
         return;
       }
 
       // All we need to do is notify the store we're no longer receiving,
       // the sdk should do the rest.
       this.dispatcher.dispatch(new sharedActions.ReceivingScreenShare({
         receiving: false
@@ -834,16 +839,20 @@ loop.OTSdkDriver = (function() {
       delete this._mockScreenShareEl;
     },
 
     /**
      * Handles the event when the remote stream is destroyed.
      */
     _onLocalStreamDestroyed: function() {
       this._notifyMetricsEvent("Publisher.streamDestroyed");
+      this.dispatcher.dispatch(new sharedActions.DataChannelsAvailable({
+        available: false
+      }));
+      delete this._publisherChannel;
       delete this._mockPublisherEl;
     },
 
     /**
      * Called from the sdk when the media access dialog is opened.
      * Prevents the default action, to prevent the SDK's "allow access"
      * dialog from being shown.
      *
--- a/browser/components/loop/content/shared/js/textChatStore.js
+++ b/browser/components/loop/content/shared/js/textChatStore.js
@@ -7,32 +7,36 @@ loop.store = loop.store || {};
 
 loop.store.TextChatStore = (function() {
   "use strict";
 
   var sharedActions = loop.shared.actions;
 
   var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES = {
     RECEIVED: "recv",
-    SENT: "sent"
+    SENT: "sent",
+    SPECIAL: "special"
   };
 
   var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES = {
-    TEXT: "chat-text"
+    CONTEXT: "chat-context",
+    TEXT: "chat-text",
+    ROOM_NAME: "room-name"
   };
 
   /**
    * A store to handle text chats. The store has a message list that may
    * contain different types of messages and data.
    */
   var TextChatStore = loop.store.createStore({
     actions: [
       "dataChannelsAvailable",
       "receivedTextChatMessage",
-      "sendTextChatMessage"
+      "sendTextChatMessage",
+      "updateRoomInfo"
     ],
 
     /**
      * Initializes the store.
      *
      * @param  {Object} options An object containing options for this store.
      *                          It should consist of:
      *                          - sdkDriver: The sdkDriver to use for sending
@@ -60,40 +64,53 @@ loop.store.TextChatStore = (function() {
         messageList: [],
         length: 0
       };
     },
 
     /**
      * Handles information for when data channels are available - enables
      * text chat.
+     *
+     * @param {sharedActions.DataChannelsAvailable} actionData
      */
-    dataChannelsAvailable: function() {
-      this.setStoreState({ textChatEnabled: true });
-      window.dispatchEvent(new CustomEvent("LoopChatEnabled"));
+    dataChannelsAvailable: function(actionData) {
+      this.setStoreState({ textChatEnabled: actionData.available });
+
+      if (actionData.available) {
+        window.dispatchEvent(new CustomEvent("LoopChatEnabled"));
+      }
     },
 
     /**
      * Appends a message to the store, which may be of type 'sent' or 'received'.
      *
-     * @param {String} type
-     * @param {sharedActions.ReceivedTextChatMessage|sharedActions.SendTextChatMessage} actionData
+     * @param {CHAT_MESSAGE_TYPES} type
+     * @param {Object} messageData Data for this message. Options are:
+     * - {CHAT_CONTENT_TYPES} contentType
+     * - {String}             message     The message detail.
+     * - {Object}             extraData   Extra data associated with the message.
      */
-    _appendTextChatMessage: function(type, actionData) {
+    _appendTextChatMessage: function(type, messageData) {
       // We create a new list to avoid updating the store's state directly,
       // which confuses the views.
       var message = {
         type: type,
-        contentType: actionData.contentType,
-        message: actionData.message
+        contentType: messageData.contentType,
+        message: messageData.message,
+        extraData: messageData.extraData
       };
       var newList = this._storeState.messageList.concat(message);
       this.setStoreState({ messageList: newList });
 
-      window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
+      // Notify MozLoopService if appropriate that a message has been appended
+      // and it should therefore check if we need a different sized window or not.
+      if (type != CHAT_MESSAGE_TYPES.SPECIAL) {
+        window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
+      }
     },
 
     /**
      * Handles received text chat messages.
      *
      * @param {sharedActions.ReceivedTextChatMessage} actionData
      */
     receivedTextChatMessage: function(actionData) {
@@ -109,13 +126,45 @@ loop.store.TextChatStore = (function() {
     /**
      * Handles sending of a chat message.
      *
      * @param {sharedActions.SendTextChatMessage} actionData
      */
     sendTextChatMessage: function(actionData) {
       this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SENT, actionData);
       this._sdkDriver.sendTextChatMessage(actionData);
+    },
+
+    /**
+     * Handles receiving information about the room - specifically the room name
+     * so it can be added to the list.
+     *
+     * @param  {sharedActions.UpdateRoomInfo} actionData
+     */
+    updateRoomInfo: function(actionData) {
+      // XXX When we add special messages to desktop, we'll need to not post
+      // multiple changes of room name, only the first. Bug 1171940 should fix this.
+      if (actionData.roomName) {
+        this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SPECIAL, {
+          contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
+          message: actionData.roomName
+        });
+      }
+
+      // Append the context if we have any.
+      if (("urls" in actionData) && actionData.urls && actionData.urls.length) {
+        // We only support the first url at the moment.
+        var urlData = actionData.urls[0];
+
+        this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SPECIAL, {
+          contentType: CHAT_CONTENT_TYPES.CONTEXT,
+          message: urlData.description,
+          extraData: {
+            location: urlData.location,
+            thumbnail: urlData.thumbnail
+          }
+        });
+      }
     }
   });
 
   return TextChatStore;
 })();
--- a/browser/components/loop/content/shared/js/textChatView.js
+++ b/browser/components/loop/content/shared/js/textChatView.js
@@ -1,117 +1,172 @@
 /* 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/. */
 
 var loop = loop || {};
 loop.shared = loop.shared || {};
 loop.shared.views = loop.shared.views || {};
-loop.shared.views.TextChatView = (function(mozl10n) {
+loop.shared.views.TextChatView = (function(mozL10n) {
   var sharedActions = loop.shared.actions;
+  var sharedViews = loop.shared.views;
   var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
   var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
 
   /**
    * Renders an individual entry for the text chat entries view.
    */
   var TextChatEntry = React.createClass({displayName: "TextChatEntry",
     mixins: [React.addons.PureRenderMixin],
 
     propTypes: {
+      contentType: React.PropTypes.string.isRequired,
       message: React.PropTypes.string.isRequired,
       type: React.PropTypes.string.isRequired
     },
 
     render: function() {
       var classes = React.addons.classSet({
         "text-chat-entry": true,
-        "received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED
+        "received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED,
+        "special": this.props.type === CHAT_MESSAGE_TYPES.SPECIAL,
+        "room-name": this.props.contentType === CHAT_CONTENT_TYPES.ROOM_NAME
       });
 
       return (
         React.createElement("div", {className: classes}, 
-          React.createElement("span", null, this.props.message)
+          React.createElement("p", null, this.props.message)
+        )
+      );
+    }
+  });
+
+  var TextChatRoomName = React.createClass({displayName: "TextChatRoomName",
+    mixins: [React.addons.PureRenderMixin],
+
+    propTypes: {
+      message: React.PropTypes.string.isRequired
+    },
+
+    render: function() {
+      return (
+        React.createElement("div", {className: "text-chat-entry special room-name"}, 
+          React.createElement("p", null, mozL10n.get("rooms_welcome_title", {conversationName: this.props.message}))
         )
       );
     }
   });
 
   /**
    * Manages the text entries in the chat entries view. This is split out from
    * TextChatView so that scrolling can be managed more efficiently - this
    * component only updates when the message list is changed.
    */
   var TextChatEntriesView = React.createClass({displayName: "TextChatEntriesView",
     mixins: [React.addons.PureRenderMixin],
 
     propTypes: {
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       messageList: React.PropTypes.array.isRequired
     },
 
     componentWillUpdate: function() {
       var node = this.getDOMNode();
       if (!node) {
         return;
       }
       // Scroll only if we're right at the bottom of the display.
       this.shouldScroll = node.scrollHeight === node.scrollTop + node.clientHeight;
     },
 
     componentDidUpdate: function() {
       if (this.shouldScroll) {
         // This ensures the paint is complete.
         window.requestAnimationFrame(function() {
-          var node = this.getDOMNode();
-          node.scrollTop = node.scrollHeight - node.clientHeight;
+          try {
+            var node = this.getDOMNode();
+            node.scrollTop = node.scrollHeight - node.clientHeight;
+          } catch (ex) {
+            console.error("TextChatEntriesView.componentDidUpdate exception", ex);
+          }
         }.bind(this));
       }
     },
 
     render: function() {
       if (!this.props.messageList.length) {
         return null;
       }
 
       return (
         React.createElement("div", {className: "text-chat-entries"}, 
           React.createElement("div", {className: "text-chat-scroller"}, 
             
               this.props.messageList.map(function(entry, i) {
+                if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
+                  switch (entry.contentType) {
+                    case CHAT_CONTENT_TYPES.ROOM_NAME:
+                      return React.createElement(TextChatRoomName, {key: i, message: entry.message});
+                    case CHAT_CONTENT_TYPES.CONTEXT:
+                      return (
+                        React.createElement("div", {key: i, className: "context-url-view-wrapper"}, 
+                          React.createElement(sharedViews.ContextUrlView, {
+                            allowClick: true, 
+                            description: entry.message, 
+                            dispatcher: this.props.dispatcher, 
+                            showContextTitle: true, 
+                            thumbnail: entry.extraData.thumbnail, 
+                            url: entry.extraData.location, 
+                            useDesktopPaths: false})
+                        )
+                      );
+                    default:
+                      console.error("Unsupported contentType", entry.contentType);
+                      return null;
+                  }
+                }
+
                 return (
                   React.createElement(TextChatEntry, {key: i, 
+                                 contentType: entry.contentType, 
                                  message: entry.message, 
                                  type: entry.type})
                 );
               }, this)
             
           )
         )
       );
     }
   });
 
   /**
-   * Displays the text chat view. This includes the text chat messages as well
-   * as a field for entering new messages.
+   * Displays a text chat entry input box for sending messages.
+   *
+   * @property {loop.Dispatcher} dispatcher
+   * @property {Boolean} showPlaceholder    Set to true to show the placeholder message.
+   * @property {Boolean} textChatEnabled    Set to true to enable the box. If false, the
+   *                                        text chat box won't be displayed.
    */
-  var TextChatView = React.createClass({displayName: "TextChatView",
+  var TextChatInputView = React.createClass({displayName: "TextChatInputView",
     mixins: [
       React.addons.LinkedStateMixin,
-      loop.store.StoreMixin("textChatStore")
+      React.addons.PureRenderMixin
     ],
 
     propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
+      showPlaceholder: React.PropTypes.bool.isRequired,
+      textChatEnabled: React.PropTypes.bool.isRequired
     },
 
     getInitialState: function() {
-      return _.extend({
+      return {
         messageDetail: ""
-      }, this.getStoreState());
+      };
     },
 
     /**
      * Handles a key being pressed - looking for the return key for submitting
      * the form.
      *
      * @param {Object} event The DOM event.
      */
@@ -124,42 +179,109 @@ loop.shared.views.TextChatView = (functi
     /**
      * Handles submitting of the form - dispatches a send text chat message.
      *
      * @param {Object} event The DOM event.
      */
     handleFormSubmit: function(event) {
       event.preventDefault();
 
+      // Don't send empty messages.
+      if (!this.state.messageDetail) {
+        return;
+      }
+
       this.props.dispatcher.dispatch(new sharedActions.SendTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
         message: this.state.messageDetail
       }));
 
       // Reset the form to empty, ready for the next message.
       this.setState({ messageDetail: "" });
     },
 
     render: function() {
-      if (!this.state.textChatEnabled) {
+      if (!this.props.textChatEnabled) {
         return null;
       }
 
-      var messageList = this.state.messageList;
+      return (
+        React.createElement("div", {className: "text-chat-box"}, 
+          React.createElement("form", {onSubmit: this.handleFormSubmit}, 
+            React.createElement("input", {type: "text", 
+                   placeholder: this.props.showPlaceholder ? mozL10n.get("chat_textbox_placeholder") : "", 
+                   onKeyDown: this.handleKeyDown, 
+                   valueLink: this.linkState("messageDetail")})
+          )
+        )
+      );
+    }
+  });
+
+  /**
+   * Displays the text chat view. This includes the text chat messages as well
+   * as a field for entering new messages.
+   *
+   * @property {loop.Dispatcher} dispatcher
+   * @property {Boolean} showAlways         If false, the view will not be rendered
+   *                                        if text chat is not enabled and the
+   *                                        message list is empty.
+   * @property {Boolean} showRoomName       Set to true to show the room name special
+   *                                        list item.
+   */
+  var TextChatView = React.createClass({displayName: "TextChatView",
+    mixins: [
+      React.addons.LinkedStateMixin,
+      loop.store.StoreMixin("textChatStore")
+    ],
+
+    propTypes: {
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
+      showAlways: React.PropTypes.bool.isRequired,
+      showRoomName: React.PropTypes.bool.isRequired
+    },
+
+    getInitialState: function() {
+      return this.getStoreState();
+    },
+
+    render: function() {
+      var messageList;
+      var hasNonSpecialMessages;
+
+      if (this.props.showRoomName) {
+        messageList = this.state.messageList;
+        hasNonSpecialMessages = messageList.some(function(item) {
+          return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
+        });
+      } else {
+        // XXX Desktop should be showing the initial context here (bug 1171940).
+        messageList = this.state.messageList.filter(function(item) {
+          return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
+        });
+        hasNonSpecialMessages = !!messageList.length;
+      }
+
+      if (!this.props.showAlways && !this.state.textChatEnabled && !messageList.length) {
+        return null;
+      }
+
+      var textChatViewClasses = React.addons.classSet({
+        "text-chat-view": true,
+        "text-chat-disabled": !this.state.textChatEnabled
+      });
 
       return (
-        React.createElement("div", {className: "text-chat-view"}, 
-          React.createElement(TextChatEntriesView, {messageList: messageList}), 
-          React.createElement("div", {className: "text-chat-box"}, 
-            React.createElement("form", {onSubmit: this.handleFormSubmit}, 
-              React.createElement("input", {type: "text", 
-                     placeholder: messageList.length ? "" : mozl10n.get("chat_textbox_placeholder"), 
-                     onKeyDown: this.handleKeyDown, 
-                     valueLink: this.linkState("messageDetail")})
-            )
-          )
+        React.createElement("div", {className: textChatViewClasses}, 
+          React.createElement(TextChatEntriesView, {
+            dispatcher: this.props.dispatcher, 
+            messageList: messageList}), 
+          React.createElement(TextChatInputView, {
+            dispatcher: this.props.dispatcher, 
+            showPlaceholder: !hasNonSpecialMessages, 
+            textChatEnabled: this.state.textChatEnabled})
         )
       );
     }
   });
 
   return TextChatView;
 })(navigator.mozL10n || document.mozL10n);
--- a/browser/components/loop/content/shared/js/textChatView.jsx
+++ b/browser/components/loop/content/shared/js/textChatView.jsx
@@ -1,117 +1,172 @@
 /* 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/. */
 
 var loop = loop || {};
 loop.shared = loop.shared || {};
 loop.shared.views = loop.shared.views || {};
-loop.shared.views.TextChatView = (function(mozl10n) {
+loop.shared.views.TextChatView = (function(mozL10n) {
   var sharedActions = loop.shared.actions;
+  var sharedViews = loop.shared.views;
   var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
   var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
 
   /**
    * Renders an individual entry for the text chat entries view.
    */
   var TextChatEntry = React.createClass({
     mixins: [React.addons.PureRenderMixin],
 
     propTypes: {
+      contentType: React.PropTypes.string.isRequired,
       message: React.PropTypes.string.isRequired,
       type: React.PropTypes.string.isRequired
     },
 
     render: function() {
       var classes = React.addons.classSet({
         "text-chat-entry": true,
-        "received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED
+        "received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED,
+        "special": this.props.type === CHAT_MESSAGE_TYPES.SPECIAL,
+        "room-name": this.props.contentType === CHAT_CONTENT_TYPES.ROOM_NAME
       });
 
       return (
         <div className={classes}>
-          <span>{this.props.message}</span>
+          <p>{this.props.message}</p>
+        </div>
+      );
+    }
+  });
+
+  var TextChatRoomName = React.createClass({
+    mixins: [React.addons.PureRenderMixin],
+
+    propTypes: {
+      message: React.PropTypes.string.isRequired
+    },
+
+    render: function() {
+      return (
+        <div className="text-chat-entry special room-name">
+          <p>{mozL10n.get("rooms_welcome_title", {conversationName: this.props.message})}</p>
         </div>
       );
     }
   });
 
   /**
    * Manages the text entries in the chat entries view. This is split out from
    * TextChatView so that scrolling can be managed more efficiently - this
    * component only updates when the message list is changed.
    */
   var TextChatEntriesView = React.createClass({
     mixins: [React.addons.PureRenderMixin],
 
     propTypes: {
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       messageList: React.PropTypes.array.isRequired
     },
 
     componentWillUpdate: function() {
       var node = this.getDOMNode();
       if (!node) {
         return;
       }
       // Scroll only if we're right at the bottom of the display.
       this.shouldScroll = node.scrollHeight === node.scrollTop + node.clientHeight;
     },
 
     componentDidUpdate: function() {
       if (this.shouldScroll) {
         // This ensures the paint is complete.
         window.requestAnimationFrame(function() {
-          var node = this.getDOMNode();
-          node.scrollTop = node.scrollHeight - node.clientHeight;
+          try {
+            var node = this.getDOMNode();
+            node.scrollTop = node.scrollHeight - node.clientHeight;
+          } catch (ex) {
+            console.error("TextChatEntriesView.componentDidUpdate exception", ex);
+          }
         }.bind(this));
       }
     },
 
     render: function() {
       if (!this.props.messageList.length) {
         return null;
       }
 
       return (
         <div className="text-chat-entries">
           <div className="text-chat-scroller">
             {
               this.props.messageList.map(function(entry, i) {
+                if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
+                  switch (entry.contentType) {
+                    case CHAT_CONTENT_TYPES.ROOM_NAME:
+                      return <TextChatRoomName key={i} message={entry.message}/>;
+                    case CHAT_CONTENT_TYPES.CONTEXT:
+                      return (
+                        <div key={i} className="context-url-view-wrapper">
+                          <sharedViews.ContextUrlView
+                            allowClick={true}
+                            description={entry.message}
+                            dispatcher={this.props.dispatcher}
+                            showContextTitle={true}
+                            thumbnail={entry.extraData.thumbnail}
+                            url={entry.extraData.location}
+                            useDesktopPaths={false} />
+                        </div>
+                      );
+                    default:
+                      console.error("Unsupported contentType", entry.contentType);
+                      return null;
+                  }
+                }
+
                 return (
                   <TextChatEntry key={i}
+                                 contentType={entry.contentType}
                                  message={entry.message}
                                  type={entry.type} />
                 );
               }, this)
             }
           </div>
         </div>
       );
     }
   });
 
   /**
-   * Displays the text chat view. This includes the text chat messages as well
-   * as a field for entering new messages.
+   * Displays a text chat entry input box for sending messages.
+   *
+   * @property {loop.Dispatcher} dispatcher
+   * @property {Boolean} showPlaceholder    Set to true to show the placeholder message.
+   * @property {Boolean} textChatEnabled    Set to true to enable the box. If false, the
+   *                                        text chat box won't be displayed.
    */
-  var TextChatView = React.createClass({
+  var TextChatInputView = React.createClass({
     mixins: [
       React.addons.LinkedStateMixin,
-      loop.store.StoreMixin("textChatStore")
+      React.addons.PureRenderMixin
     ],
 
     propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
+      showPlaceholder: React.PropTypes.bool.isRequired,
+      textChatEnabled: React.PropTypes.bool.isRequired
     },
 
     getInitialState: function() {
-      return _.extend({
+      return {
         messageDetail: ""
-      }, this.getStoreState());
+      };
     },
 
     /**
      * Handles a key being pressed - looking for the return key for submitting
      * the form.
      *
      * @param {Object} event The DOM event.
      */
@@ -124,42 +179,109 @@ loop.shared.views.TextChatView = (functi
     /**
      * Handles submitting of the form - dispatches a send text chat message.
      *
      * @param {Object} event The DOM event.
      */
     handleFormSubmit: function(event) {
       event.preventDefault();
 
+      // Don't send empty messages.
+      if (!this.state.messageDetail) {
+        return;
+      }
+
       this.props.dispatcher.dispatch(new sharedActions.SendTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
         message: this.state.messageDetail
       }));
 
       // Reset the form to empty, ready for the next message.
       this.setState({ messageDetail: "" });
     },
 
     render: function() {
-      if (!this.state.textChatEnabled) {
+      if (!this.props.textChatEnabled) {
         return null;
       }
 
-      var messageList = this.state.messageList;
+      return (
+        <div className="text-chat-box">
+          <form onSubmit={this.handleFormSubmit}>
+            <input type="text"
+                   placeholder={this.props.showPlaceholder ? mozL10n.get("chat_textbox_placeholder") : ""}
+                   onKeyDown={this.handleKeyDown}
+                   valueLink={this.linkState("messageDetail")} />
+          </form>
+        </div>
+      );
+    }
+  });
+
+  /**
+   * Displays the text chat view. This includes the text chat messages as well
+   * as a field for entering new messages.
+   *
+   * @property {loop.Dispatcher} dispatcher
+   * @property {Boolean} showAlways         If false, the view will not be rendered
+   *                                        if text chat is not enabled and the
+   *                                        message list is empty.
+   * @property {Boolean} showRoomName       Set to true to show the room name special
+   *                                        list item.
+   */
+  var TextChatView = React.createClass({
+    mixins: [
+      React.addons.LinkedStateMixin,
+      loop.store.StoreMixin("textChatStore")
+    ],
+
+    propTypes: {
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
+      showAlways: React.PropTypes.bool.isRequired,
+      showRoomName: React.PropTypes.bool.isRequired
+    },
+
+    getInitialState: function() {
+      return this.getStoreState();
+    },
+
+    render: function() {
+      var messageList;
+      var hasNonSpecialMessages;
+
+      if (this.props.showRoomName) {
+        messageList = this.state.messageList;
+        hasNonSpecialMessages = messageList.some(function(item) {
+          return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
+        });
+      } else {
+        // XXX Desktop should be showing the initial context here (bug 1171940).
+        messageList = this.state.messageList.filter(function(item) {
+          return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
+        });
+        hasNonSpecialMessages = !!messageList.length;
+      }
+
+      if (!this.props.showAlways && !this.state.textChatEnabled && !messageList.length) {
+        return null;
+      }
+
+      var textChatViewClasses = React.addons.classSet({
+        "text-chat-view": true,
+        "text-chat-disabled": !this.state.textChatEnabled
+      });
 
       return (
-        <div className="text-chat-view">
-          <TextChatEntriesView messageList={messageList} />
-          <div className="text-chat-box">
-            <form onSubmit={this.handleFormSubmit}>
-              <input type="text"
-                     placeholder={messageList.length ? "" : mozl10n.get("chat_textbox_placeholder")}
-                     onKeyDown={this.handleKeyDown}
-                     valueLink={this.linkState("messageDetail")} />
-            </form>
-          </div>
+        <div className={textChatViewClasses}>
+          <TextChatEntriesView
+            dispatcher={this.props.dispatcher}
+            messageList={messageList} />
+          <TextChatInputView
+            dispatcher={this.props.dispatcher}
+            showPlaceholder={!hasNonSpecialMessages}
+            textChatEnabled={this.state.textChatEnabled} />
         </div>
       );
     }
   });
 
   return TextChatView;
 })(navigator.mozL10n || document.mozL10n);
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -386,24 +386,24 @@ loop.shared.views = (function(_, l10n) {
       var outgoing = this.getDOMNode().querySelector(".local");
 
       // XXX move this into its StreamingVideo component?
       this.publisher = this.props.sdk.initPublisher(
         outgoing, this.getDefaultPublisherConfig({publishVideo: this.props.video.enabled}));
 
       // Suppress OT GuM custom dialog, see bug 1018875
       this.listenTo(this.publisher, "accessDialogOpened accessDenied",
-                    function(event) {
-                      event.preventDefault();
+                    function(ev) {
+                      ev.preventDefault();
                     });
 
-      this.listenTo(this.publisher, "streamCreated", function(event) {
+      this.listenTo(this.publisher, "streamCreated", function(ev) {
         this.setState({
-          audio: {enabled: event.stream.hasAudio},
-          video: {enabled: event.stream.hasVideo}
+          audio: {enabled: ev.stream.hasAudio},
+          video: {enabled: ev.stream.hasVideo}
         });
       }.bind(this));
 
       this.listenTo(this.publisher, "streamDestroyed", function() {
         this.setState({
           audio: {enabled: false},
           video: {enabled: false}
         });
@@ -628,16 +628,25 @@ loop.shared.views = (function(_, l10n) {
         additionalClass: "",
         checked: false,
         disabled: false,
         label: null,
         value: ""
       };
     },
 
+    componentWillReceiveProps: function(nextProps) {
+      // Only change the state if the prop has changed, and if it is also
+      // different from the state.
+      if (this.props.checked !== nextProps.checked &&
+          this.state.checked !== nextProps.checked) {
+        this.setState({ checked: nextProps.checked });
+      }
+    },
+
     getInitialState: function() {
       return {
         checked: this.props.checked,
         value: this.props.checked ? this.props.value : ""
       };
     },
 
     _handleClick: function(event) {
@@ -685,26 +694,131 @@ loop.shared.views = (function(_, l10n) {
     mixins: [React.addons.PureRenderMixin],
 
     render: function() {
         return React.createElement("div", {className: "avatar"});
     }
   });
 
   /**
+   * Renders a loading spinner for when video content is not yet available.
+   */
+  var LoadingView = React.createClass({displayName: "LoadingView",
+    mixins: [React.addons.PureRenderMixin],
+
+    render: function() {
+        return (
+          React.createElement("div", {className: "loading-background"}, 
+            React.createElement("div", {className: "loading-stream"})
+          )
+        );
+    }
+  });
+
+  /**
+   * Renders a url that's part of context on the display.
+   *
+   * @property {Boolean} allowClick         Set to true to allow the url to be clicked. If this
+   *                                        is specified, then 'dispatcher' is also required.
+   * @property {String}  description        The description for the context url.
+   * @property {loop.Dispatcher} dispatcher
+   * @property {Boolean} showContextTitle   Whether or not to show the "Let's talk about" title.
+   * @property {String}  thumbnail          The thumbnail url (expected to be a data url) to
+   *                                        display. If not specified, a fallback url will be
+   *                                        shown.
+   * @property {String}  url                The url to be displayed. If not present or invalid,
+   *                                        then this view won't be displayed.
+   * @property {Boolean} useDesktopPaths    Whether or not to use the desktop paths for for the
+   *                                        fallback url.
+   */
+  var ContextUrlView = React.createClass({displayName: "ContextUrlView",
+    mixins: [React.addons.PureRenderMixin],
+
+    PropTypes: {
+      allowClick: React.PropTypes.bool.isRequired,
+      description: React.PropTypes.string.isRequired,
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
+      showContextTitle: React.PropTypes.bool.isRequired,
+      thumbnail: React.PropTypes.string,
+      url: React.PropTypes.string,
+      useDesktopPaths: React.PropTypes.bool.isRequired
+    },
+
+    /**
+     * Dispatches an action to record when the link is clicked.
+     */
+    handleLinkClick: function() {
+      if (!this.props.allowClick) {
+        return;
+      }
+
+      this.props.dispatcher.dispatch(new sharedActions.RecordClick({
+        linkInfo: "Shared URL"
+      }));
+    },
+
+    /**
+     * Renders the context title ("Let's talk about") if necessary.
+     */
+    renderContextTitle: function() {
+      if (!this.props.showContextTitle) {
+        return null;
+      }
+
+      return React.createElement("p", null, l10n.get("context_inroom_label"));
+    },
+
+    render: function() {
+      var hostname;
+
+      try {
+        hostname = new URL(this.props.url).hostname;
+      } catch (ex) {
+        return null;
+      }
+
+      var thumbnail = this.props.thumbnail;
+
+      if (!thumbnail) {
+        thumbnail = this.props.useDesktopPaths ?
+          "loop/shared/img/icons-16x16.svg#globe" :
+          "shared/img/icons-16x16.svg#globe";
+      }
+
+      return (
+        React.createElement("div", {className: "context-content"}, 
+          this.renderContextTitle(), 
+          React.createElement("div", {className: "context-wrapper"}, 
+            React.createElement("img", {className: "context-preview", src: thumbnail}), 
+            React.createElement("span", {className: "context-description"}, 
+              this.props.description, 
+              React.createElement("a", {className: "context-url", 
+                 onClick: this.handleLinkClick, 
+                 href: this.props.allowClick ? this.props.url : null, 
+                 rel: "noreferrer", 
+                 target: "_blank"}, hostname)
+            )
+          )
+        )
+      );
+    }
+  });
+
+  /**
    * Renders a media element for display. This also handles displaying an avatar
    * instead of the video, and attaching a video stream to the video element.
    */
   var MediaView = React.createClass({displayName: "MediaView",
     // srcVideoObject should be ok for a shallow comparison, so we are safe
     // to use the pure render mixin here.
     mixins: [React.addons.PureRenderMixin],
 
     PropTypes: {
       displayAvatar: React.PropTypes.bool.isRequired,
+      isLoading: React.PropTypes.bool.isRequired,
       posterUrl: React.PropTypes.string,
       // Expecting "local" or "remote".
       mediaType: React.PropTypes.string.isRequired,
       srcVideoObject: React.PropTypes.object
     },
 
     componentDidMount: function() {
       if (!this.props.displayAvatar) {
@@ -748,28 +862,33 @@ loop.shared.views = (function(_, l10n) {
         attrName = "srcObject";
       } else if ("mozSrcObject" in videoElement) {
         // mozSrcObject is for Firefox
         attrName = "mozSrcObject";
       } else if ("src" in videoElement) {
         // src is for Chrome.
         attrName = "src";
       } else {
-        console.error("Error attaching stream to element - no supported attribute found");
+        console.error("Error attaching stream to element - no supported" +
+                      "attribute found");
         return;
       }
 
       // If the object hasn't changed it, then don't reattach it.
       if (videoElement[attrName] !== srcVideoObject[attrName]) {
         videoElement[attrName] = srcVideoObject[attrName];
       }
       videoElement.play();
     },
 
     render: function() {
+      if (this.props.isLoading) {
+        return React.createElement(LoadingView, null);
+      }
+
       if (this.props.displayAvatar) {
         return React.createElement(AvatarView, null);
       }
 
       if (!this.props.srcVideoObject && !this.props.posterUrl) {
         return React.createElement("div", {className: "no-video"});
       }
 
@@ -795,16 +914,18 @@ loop.shared.views = (function(_, l10n) {
     }
   });
 
   return {
     AvatarView: AvatarView,
     Button: Button,
     ButtonGroup: ButtonGroup,
     Checkbox: Checkbox,
+    ContextUrlView: ContextUrlView,
     ConversationView: ConversationView,
     ConversationToolbar: ConversationToolbar,
     MediaControlButton: MediaControlButton,
     MediaView: MediaView,
+    LoadingView: LoadingView,
     ScreenShareControlButton: ScreenShareControlButton,
     NotificationListView: NotificationListView
   };
 })(_, navigator.mozL10n || document.mozL10n);
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -386,24 +386,24 @@ loop.shared.views = (function(_, l10n) {
       var outgoing = this.getDOMNode().querySelector(".local");
 
       // XXX move this into its StreamingVideo component?
       this.publisher = this.props.sdk.initPublisher(
         outgoing, this.getDefaultPublisherConfig({publishVideo: this.props.video.enabled}));
 
       // Suppress OT GuM custom dialog, see bug 1018875
       this.listenTo(this.publisher, "accessDialogOpened accessDenied",
-                    function(event) {
-                      event.preventDefault();
+                    function(ev) {
+                      ev.preventDefault();
                     });
 
-      this.listenTo(this.publisher, "streamCreated", function(event) {
+      this.listenTo(this.publisher, "streamCreated", function(ev) {
         this.setState({
-          audio: {enabled: event.stream.hasAudio},
-          video: {enabled: event.stream.hasVideo}
+          audio: {enabled: ev.stream.hasAudio},
+          video: {enabled: ev.stream.hasVideo}
         });
       }.bind(this));
 
       this.listenTo(this.publisher, "streamDestroyed", function() {
         this.setState({
           audio: {enabled: false},
           video: {enabled: false}
         });
@@ -628,16 +628,25 @@ loop.shared.views = (function(_, l10n) {
         additionalClass: "",
         checked: false,
         disabled: false,
         label: null,
         value: ""
       };
     },
 
+    componentWillReceiveProps: function(nextProps) {
+      // Only change the state if the prop has changed, and if it is also
+      // different from the state.
+      if (this.props.checked !== nextProps.checked &&
+          this.state.checked !== nextProps.checked) {
+        this.setState({ checked: nextProps.checked });
+      }
+    },
+
     getInitialState: function() {
       return {
         checked: this.props.checked,
         value: this.props.checked ? this.props.value : ""
       };
     },
 
     _handleClick: function(event) {
@@ -685,26 +694,131 @@ loop.shared.views = (function(_, l10n) {
     mixins: [React.addons.PureRenderMixin],
 
     render: function() {
         return <div className="avatar"/>;
     }
   });
 
   /**
+   * Renders a loading spinner for when video content is not yet available.
+   */
+  var LoadingView = React.createClass({
+    mixins: [React.addons.PureRenderMixin],
+
+    render: function() {
+        return (
+          <div className="loading-background">
+            <div className="loading-stream"/>
+          </div>
+        );
+    }
+  });
+
+  /**
+   * Renders a url that's part of context on the display.
+   *
+   * @property {Boolean} allowClick         Set to true to allow the url to be clicked. If this
+   *                                        is specified, then 'dispatcher' is also required.
+   * @property {String}  description        The description for the context url.
+   * @property {loop.Dispatcher} dispatcher
+   * @property {Boolean} showContextTitle   Whether or not to show the "Let's talk about" title.
+   * @property {String}  thumbnail          The thumbnail url (expected to be a data url) to
+   *                                        display. If not specified, a fallback url will be
+   *                                        shown.
+   * @property {String}  url                The url to be displayed. If not present or invalid,
+   *                                        then this view won't be displayed.
+   * @property {Boolean} useDesktopPaths    Whether or not to use the desktop paths for for the
+   *                                        fallback url.
+   */
+  var ContextUrlView = React.createClass({
+    mixins: [React.addons.PureRenderMixin],
+
+    PropTypes: {
+      allowClick: React.PropTypes.bool.isRequired,
+      description: React.PropTypes.string.isRequired,
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
+      showContextTitle: React.PropTypes.bool.isRequired,
+      thumbnail: React.PropTypes.string,
+      url: React.PropTypes.string,
+      useDesktopPaths: React.PropTypes.bool.isRequired
+    },
+
+    /**
+     * Dispatches an action to record when the link is clicked.
+     */
+    handleLinkClick: function() {
+      if (!this.props.allowClick) {
+        return;
+      }
+
+      this.props.dispatcher.dispatch(new sharedActions.RecordClick({
+        linkInfo: "Shared URL"
+      }));
+    },
+
+    /**
+     * Renders the context title ("Let's talk about") if necessary.
+     */
+    renderContextTitle: function() {
+      if (!this.props.showContextTitle) {
+        return null;
+      }
+
+      return <p>{l10n.get("context_inroom_label")}</p>;
+    },
+
+    render: function() {
+      var hostname;
+
+      try {
+        hostname = new URL(this.props.url).hostname;
+      } catch (ex) {
+        return null;
+      }
+
+      var thumbnail = this.props.thumbnail;
+
+      if (!thumbnail) {
+        thumbnail = this.props.useDesktopPaths ?
+          "loop/shared/img/icons-16x16.svg#globe" :
+          "shared/img/icons-16x16.svg#globe";
+      }
+
+      return (
+        <div className="context-content">
+          {this.renderContextTitle()}
+          <div className="context-wrapper">
+            <img className="context-preview" src={thumbnail} />
+            <span className="context-description">
+              {this.props.description}
+              <a className="context-url"
+                 onClick={this.handleLinkClick}
+                 href={this.props.allowClick ? this.props.url : null}
+                 rel="noreferrer"
+                 target="_blank">{hostname}</a>
+            </span>
+          </div>
+        </div>
+      );
+    }
+  });
+
+  /**
    * Renders a media element for display. This also handles displaying an avatar
    * instead of the video, and attaching a video stream to the video element.
    */
   var MediaView = React.createClass({
     // srcVideoObject should be ok for a shallow comparison, so we are safe
     // to use the pure render mixin here.
     mixins: [React.addons.PureRenderMixin],
 
     PropTypes: {
       displayAvatar: React.PropTypes.bool.isRequired,
+      isLoading: React.PropTypes.bool.isRequired,
       posterUrl: React.PropTypes.string,
       // Expecting "local" or "remote".
       mediaType: React.PropTypes.string.isRequired,
       srcVideoObject: React.PropTypes.object
     },
 
     componentDidMount: function() {
       if (!this.props.displayAvatar) {
@@ -748,28 +862,33 @@ loop.shared.views = (function(_, l10n) {
         attrName = "srcObject";
       } else if ("mozSrcObject" in videoElement) {
         // mozSrcObject is for Firefox
         attrName = "mozSrcObject";
       } else if ("src" in videoElement) {
         // src is for Chrome.
         attrName = "src";
       } else {
-        console.error("Error attaching stream to element - no supported attribute found");
+        console.error("Error attaching stream to element - no supported" +
+                      "attribute found");
         return;
       }
 
       // If the object hasn't changed it, then don't reattach it.
       if (videoElement[attrName] !== srcVideoObject[attrName]) {
         videoElement[attrName] = srcVideoObject[attrName];
       }
       videoElement.play();
     },
 
     render: function() {
+      if (this.props.isLoading) {
+        return <LoadingView />;
+      }
+
       if (this.props.displayAvatar) {
         return <AvatarView />;
       }
 
       if (!this.props.srcVideoObject && !this.props.posterUrl) {
         return <div className="no-video"/>;
       }
 
@@ -795,16 +914,18 @@ loop.shared.views = (function(_, l10n) {
     }
   });
 
   return {
     AvatarView: AvatarView,
     Button: Button,
     ButtonGroup: ButtonGroup,
     Checkbox: Checkbox,
+    ContextUrlView: ContextUrlView,
     ConversationView: ConversationView,
     ConversationToolbar: ConversationToolbar,
     MediaControlButton: MediaControlButton,
     MediaView: MediaView,
+    LoadingView: LoadingView,
     ScreenShareControlButton: ScreenShareControlButton,
     NotificationListView: NotificationListView
   };
 })(_, navigator.mozL10n || document.mozL10n);
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -30,16 +30,19 @@ browser.jar:
   content/browser/loop/shared/css/common.css        (content/shared/css/common.css)
   content/browser/loop/shared/css/conversation.css  (content/shared/css/conversation.css)
 
   # Shared images
   content/browser/loop/shared/img/happy.png                     (content/shared/img/happy.png)
   content/browser/loop/shared/img/sad.png                       (content/shared/img/sad.png)
   content/browser/loop/shared/img/icon_32.png                   (content/shared/img/icon_32.png)
   content/browser/loop/shared/img/icon_64.png                   (content/shared/img/icon_64.png)
+  content/browser/loop/shared/img/spinner.svg                   (content/shared/img/spinner.svg)
+  # XXX could get rid of the png spinner usages and replace them with the svg
+  # one?
   content/browser/loop/shared/img/spinner.png                   (content/shared/img/spinner.png)
   content/browser/loop/shared/img/spinner@2x.png                (content/shared/img/spinner@2x.png)
   content/browser/loop/shared/img/audio-inverse-14x14.png       (content/shared/img/audio-inverse-14x14.png)
   content/browser/loop/shared/img/audio-inverse-14x14@2x.png    (content/shared/img/audio-inverse-14x14@2x.png)
   content/browser/loop/shared/img/facemute-14x14.png            (content/shared/img/facemute-14x14.png)
   content/browser/loop/shared/img/facemute-14x14@2x.png         (content/shared/img/facemute-14x14@2x.png)
   content/browser/loop/shared/img/hangup-inverse-14x14.png      (content/shared/img/hangup-inverse-14x14.png)
   content/browser/loop/shared/img/hangup-inverse-14x14@2x.png   (content/shared/img/hangup-inverse-14x14@2x.png)
--- a/browser/components/loop/manifest.ini
+++ b/browser/components/loop/manifest.ini
@@ -13,11 +13,12 @@
 ; seems to work.
 
 [DEFAULT]
 b2g = false
 browser = true
 qemu = false
 
 [test/shared/test_shared_all.py]
+skip-if = os == "win" # Bug 1149955
 [test/desktop-local/test_desktop_all.py]
 [test/standalone/test_standalone_all.py]
 [test/ui-showcase/test_ui-showcase.py]
--- a/browser/components/loop/modules/LoopContacts.jsm
+++ b/browser/components/loop/modules/LoopContacts.jsm
@@ -421,19 +421,19 @@ let LoopContactsInternal = Object.freeze
    */
   remove: function(guid, callback) {
     this.get(guid, (err, contact) => {
       if (err) {
         callback(err);
         return;
       }
 
-      LoopStorage.getStore(kObjectStoreName, (err, store) => {
-        if (err) {
-          callback(err);
+      LoopStorage.getStore(kObjectStoreName, (error, store) => {
+        if (error) {
+          callback(error);
           return;
         }
 
         let request;
         try {
           request = store.delete(guid);
         } catch (ex) {
           callback(ex);
@@ -679,19 +679,19 @@ let LoopContactsInternal = Object.freeze
       }
 
       if (!contact) {
         callback(new Error("Contact with " + kKeyPath + " '" +
                            guid + "' could not be found"));
         return;
       }
 
-      LoopStorage.getStore(kObjectStoreName, (err, store) => {
-        if (err) {
-          callback(err);
+      LoopStorage.getStore(kObjectStoreName, (error, store) => {
+        if (error) {
+          callback(error);
           return;
         }
 
         let previous = extend({}, contact);
         // Update the contact with properties provided by `details`.
         extend(contact, details);
 
         details._date_lch = Date.now();
--- a/browser/components/loop/modules/MozLoopAPI.jsm
+++ b/browser/components/loop/modules/MozLoopAPI.jsm
@@ -119,16 +119,35 @@ const cloneValueInto = function(value, t
     MozLoopService.log.debug("Failed to clone value:", value);
     throw ex;
   }
 
   return clone;
 };
 
 /**
+ * Guarded callback invocation that reports when a callback function doesn't
+ * exist anymore.
+ *
+ * @param {Function} callback Callback function to be invoked.
+ * @param {...mixed} args     Rest param of callback function arguments.
+ */
+const invokeCallback = function(callback, ...args) {
+  if (typeof callback != "function") {
+    // We log an error, because it will have a stack trace attached which will
+    // be helpful whilst debugging.
+    MozLoopService.log.error.apply(MozLoopService.log,
+      [new Error("Callback function was lost!"), ...args]);
+    return;
+  }
+
+  return callback.apply(null, args);
+};
+
+/**
  * Get the two-digit hexadecimal code for a byte
  *
  * @param {byte} charCode
  */
 const toHexString = function(charCode) {
   return ("0" + charCode.toString(16)).slice(-2);
 };
 
@@ -429,17 +448,17 @@ function injectLoopAPI(targetWindow) {
      *                            `Error` object or `null`. The second argument will
      *                            be the result of the operation, if successfull.
      */
     startImport: {
       enumerable: true,
       writable: true,
       value: function(options, callback) {
         LoopContacts.startImport(options, getChromeWindow(targetWindow), function(...results) {
-          callback(...[cloneValueInto(r, targetWindow) for (r of results)]);
+          invokeCallback(callback, ...[cloneValueInto(r, targetWindow) for (r of results)]);
         });
       }
     },
 
     /**
      * Returns translated strings associated with an element. Designed
      * for use with l10n.js
      *
@@ -489,27 +508,27 @@ function injectLoopAPI(targetWindow) {
         let buttonFlags;
         if (options.okButton && options.cancelButton) {
           buttonFlags =
             (Ci.nsIPrompt.BUTTON_POS_0 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING) +
             (Ci.nsIPrompt.BUTTON_POS_1 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING);
         } else if (!options.okButton && !options.cancelButton) {
           buttonFlags = Services.prompt.STD_YES_NO_BUTTONS;
         } else {
-          callback(cloneValueInto(new Error("confirm: missing button options"), targetWindow));
+          invokeCallback(callback, cloneValueInto(new Error("confirm: missing button options"), targetWindow));
         }
 
         try {
           let chosenButton = Services.prompt.confirmEx(null, "",
             options.message, buttonFlags, options.okButton, options.cancelButton,
             null, null, {});
 
-          callback(null, chosenButton == 0);
+          invokeCallback(callback, null, chosenButton == 0);
         } catch (ex) {
-          callback(cloneValueInto(ex, targetWindow));
+          invokeCallback(callback, cloneValueInto(ex, targetWindow));
         }
       }
     },
 
     /**
      * Set any preference under "loop."
      *
      * @param {String} prefName The name of the pref without the preceding "loop."
@@ -612,30 +631,23 @@ function injectLoopAPI(targetWindow) {
      *                            transmitted with the request.
      * @param {Function} callback Called when the request completes.
      */
     hawkRequest: {
       enumerable: true,
       writable: true,
       value: function(sessionType, path, method, payloadObj, callback) {
         // XXX Should really return a DOM promise here.
-        let callbackIsFunction = (typeof callback == "function");
         MozLoopService.hawkRequest(sessionType, path, method, payloadObj).then((response) => {
-          callback(null, response.body);
+          invokeCallback(callback, null, response.body);
         }, hawkError => {
-          // When the function was garbage collected due to async events, like
-          // closing a window, we want to circumvent a JS error.
-          if (callbackIsFunction && typeof callback != "function") {
-            MozLoopService.log.error("hawkRequest: callback function was lost.", hawkError);
-            return;
-          }
           // The hawkError.error property, while usually a string representing
           // an HTTP response status message, may also incorrectly be a native
           // error object that will cause the cloning function to fail.
-          callback(Cu.cloneInto({
+          invokeCallback(callback, Cu.cloneInto({
             error: (hawkError.error && typeof hawkError.error == "string")
                    ? hawkError.error : "Unexpected exception",
             message: hawkError.message,
             code: hawkError.code,
             errno: hawkError.errno,
           }, targetWindow));
         }).catch(Cu.reportError);
       }
@@ -838,22 +850,22 @@ function injectLoopAPI(targetWindow) {
                         .createInstance(Ci.nsIXMLHttpRequest);
         let url = `chrome://browser/content/loop/shared/sounds/${name}.ogg`;
 
         request.open("GET", url, true);
         request.responseType = "arraybuffer";
         request.onload = () => {
           if (request.status < 200 || request.status >= 300) {
             let error = new Error(request.status + " " + request.statusText);
-            callback(cloneValueInto(error, targetWindow));
+            invokeCallback(callback, cloneValueInto(error, targetWindow));
             return;
           }
 
           let blob = new Blob([request.response], {type: "audio/ogg"});
-          callback(null, cloneValueInto(blob, targetWindow));
+          invokeCallback(callback, null, cloneValueInto(blob, targetWindow));
         };
 
         request.send();
       }
     },
 
     /**
      * Compose a URL pointing to the location of an avatar by email address.
@@ -907,17 +919,17 @@ function injectLoopAPI(targetWindow) {
           win.LoopUI.getFavicon(function(err, favicon) {
             if (err) {
               MozLoopService.log.error("Error occurred whilst fetching favicon", err);
               // We don't return here intentionally to make sure the callback is
               // invoked at all times. We just report the error here.
             }
             pageData.favicon = favicon || null;
 
-            callback(cloneValueInto(pageData, targetWindow));
+            invokeCallback(callback, cloneValueInto(pageData, targetWindow));
           });
         });
         win.gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetPageData");
       }
     },
 
     /**
      * Associates a session-id and a call-id with a window for debugging.
--- a/browser/components/loop/modules/MozLoopService.jsm
+++ b/browser/components/loop/modules/MozLoopService.jsm
@@ -352,18 +352,18 @@ let MozLoopServiceInternal = {
    *                   with this channel from the PushServer.
    * @returns {Promise} A promise that is resolved with no params on completion, or
    *                    rejected with an error code or string.
    */
   createNotificationChannel: function(channelID, sessionType, serviceType, onNotification) {
     log.debug("createNotificationChannel", channelID, sessionType, serviceType);
     // Wrap the push notification registration callback in a Promise.
     return new Promise((resolve, reject) => {
-      let onRegistered = (error, pushURL, channelID) => {
-        log.debug("createNotificationChannel onRegistered:", error, pushURL, channelID);
+      let onRegistered = (error, pushURL, chID) => {
+        log.debug("createNotificationChannel onRegistered:", error, pushURL, chID);
         if (error) {
           reject(Error(error));
         } else {
           resolve(this.registerWithLoopServer(sessionType, serviceType, pushURL));
         }
       };
 
       this.pushHandler.register(channelID, onRegistered, onNotification);
@@ -506,36 +506,36 @@ let MozLoopServiceInternal = {
     }
 
     let error,
         pushURLs = this.pushURLs.get(sessionType),
         callsPushURL = pushURLs ? pushURLs.calls : null,
         roomsPushURL = pushURLs ? pushURLs.rooms : null;
     this.pushURLs.delete(sessionType);
 
-    let unregister = (sessionType, pushURL) => {
+    let unregister = (sessType, pushURL) => {
       if (!pushURL) {
         return Promise.resolve("no pushURL of this type to unregister");
       }
 
       let unregisterURL = "/registration?simplePushURL=" + encodeURIComponent(pushURL);
-      return this.hawkRequestInternal(sessionType, unregisterURL, "DELETE").then(
+      return this.hawkRequestInternal(sessType, unregisterURL, "DELETE").then(
         () => {
-          log.debug("Successfully unregistered from server for sessionType = ", sessionType);
-          return "unregistered sessionType " + sessionType;
+          log.debug("Successfully unregistered from server for sessionType = ", sessType);
+          return "unregistered sessionType " + sessType;
         },
-        error => {
-          if (error.code === 401) {
+        err => {
+          if (err.code === 401) {
             // Authorization failed, invalid token. This is fine since it may mean we already logged out.
-            log.debug("already unregistered - invalid token", sessionType);
-            return "already unregistered, sessionType = " + sessionType;
+            log.debug("already unregistered - invalid token", sessType);
+            return "already unregistered, sessionType = " + sessType;
           }
 
           log.error("Failed to unregister with the loop server. Error: ", error);
-          throw error;
+          throw err;
         });
     };
 
     return Promise.all([unregister(sessionType, callsPushURL), unregister(sessionType, roomsPushURL)]);
   },
 
   /**
    * Performs a hawk based request to the loop server - there is no pre-registration
@@ -880,20 +880,20 @@ let MozLoopServiceInternal = {
         window.addEventListener("socialFrameAttached", socialFrameChanged.bind(null, "Loop:ChatWindowAttached"));
         window.addEventListener("unload", socialFrameChanged.bind(null, "Loop:ChatWindowClosed"));
 
         const kSizeMap = {
           LoopChatEnabled: "loopChatEnabled",
           LoopChatMessageAppended: "loopChatMessageAppended"
         };
 
-        function onChatEvent(event) {
+        function onChatEvent(ev) {
           // When the chat box or messages are shown, resize the panel or window
           // to be slightly higher to accomodate them.
-          let customSize = kSizeMap[event.type];
+          let customSize = kSizeMap[ev.type];
           if (customSize) {
             chatbox.setAttribute("customSize", customSize);
             chatbox.parentNode.setAttribute("customSize", customSize);
           }
         }
 
         window.addEventListener("LoopChatEnabled", onChatEvent);
         window.addEventListener("LoopChatMessageAppended", onChatEvent);
@@ -904,18 +904,18 @@ let MozLoopServiceInternal = {
             .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
 
         let onPCLifecycleChange = (pc, winID, type) => {
           if (winID != ourID) {
             return;
           }
 
           // Chat Window Id, this is different that the internal winId
-          let windowId = window.location.hash.slice(1);
-          var context = this.conversationContexts.get(windowId);
+          let chatWindowId = window.location.hash.slice(1);
+          var context = this.conversationContexts.get(chatWindowId);
           var exists = pc.id.match(/session=(\S+)/);
           if (context && !exists) {
             // Not ideal but insert our data amidst existing data like this:
             // - 000 (id=00 url=http)
             // + 000 (session=000 call=000 id=00 url=http)
             var pair = pc.id.split("(");  //)
             if (pair.length == 2) {
               pc.id = pair[0] + "(session=" + context.sessionId +
@@ -937,26 +937,27 @@ let MozLoopServiceInternal = {
 
         let pc_static = new window.mozRTCPeerConnectionStatic();
         pc_static.registerPeerConnectionLifecycleCallback(onPCLifecycleChange);
 
         UITour.notify("Loop:ChatWindowOpened");
       }.bind(this), true);
     };
 
-    let chatbox = Chat.open(null, origin, "", url, undefined, undefined, callback);
-    if (!chatbox) {
+    let chatboxInstance = Chat.open(null, origin, "", url, undefined, undefined,
+                                    callback);
+    if (!chatboxInstance) {
       return null;
     // It's common for unit tests to overload Chat.open.
-    } else if (chatbox.setAttribute) {
+    } else if (chatboxInstance.setAttribute) {
       // Set properties that influence visual appeara nce of the chatbox right
       // away to circumvent glitches.
-      chatbox.setAttribute("dark", true);
-      chatbox.setAttribute("customSize", "loopDefault");
-      chatbox.parentNode.setAttribute("customSize", "loopDefault");
+      chatboxInstance.setAttribute("dark", true);
+      chatboxInstance.setAttribute("customSize", "loopDefault");
+      chatboxInstance.parentNode.setAttribute("customSize", "loopDefault");
     }
     return windowId;
   },
 
   /**
    * Fetch Firefox Accounts (FxA) OAuth parameters from the Loop Server.
    *
    * @return {Promise} resolved with the body of the hawk request for OAuth parameters.
@@ -1227,20 +1228,20 @@ this.MozLoopService = {
     if (!this.getLoopPref("gettingStarted.resumeOnFirstJoin")) {
       return;
     }
 
     if (!room.participants) {
       return;
     }
 
-    // The particpant that joined isn't necessarily included in room.participants (depending on
+    // The participant that joined isn't necessarily included in room.participants (depending on
     // when the broadcast happens) so concatenate.
-    for (let participant of room.participants.concat(participant)) {
-      if (participant.owner) {
+    for (let roomParticipant of room.participants.concat(participant)) {
+      if (roomParticipant.owner) {
         isOwnerInRoom = true;
       } else {
         isOtherInRoom = true;
       }
     }
 
     if (!isOwnerInRoom || !isOtherInRoom) {
       return;
--- a/browser/components/loop/standalone/content/css/webapp.css
+++ b/browser/components/loop/standalone/content/css/webapp.css
@@ -124,17 +124,17 @@ body,
   background-image: url("../shared/img/mozilla-logo.png");
   background-repeat: no-repeat;
 }
 
 /* Rooms Footer */
 
 .rooms-footer {
   background: #000;
-  margin: 0 20px;
+  margin: 0 10px;
   text-align: left;
   height: 3em;
   position: relative;
 }
 
 html[dir="rtl"] .rooms-footer {
   text-align: right;
 }
@@ -349,45 +349,26 @@ p.standalone-btn-label {
   box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.4);
   border-radius: 3px;
   z-index: 1002; /* ensures the form is always on top of the control bar */
 }
 .standalone .room-conversation-wrapper .ended-conversation .feedback {
   right: 35%;
 }
 
+html[dir="rtl"] .standalone .room-conversation-wrapper .ended-conversation .feedback {
+  right: auto;
+  left: 35%;
+}
+
 .standalone .ended-conversation .local-stream {
   /* Hide  local media stream when feedback form is shown. */
   display: none;
 }
 
-/**
- * The .text-chat-* styles are very temporarily whilst we work on text chat
- * (bug 1108892 and dependencies).
- */
-.text-chat-view {
-  height: 60px;
-  color: white;
-}
-
-.text-chat-entries {
-  /* XXX Should use flex, this is just for the initial implementation. */
-  height: calc(100% - 2em);
-  width: 30%;
-}
-
-.text-chat-box {
-  width: 30%;
-  margin: auto;
-}
-
-.text-chat-box > form > input {
-  width: 100%;
-}
-
 @media screen and (max-width:640px) {
   .standalone .ended-conversation .feedback {
     width: 92%;
     top: 10%;
     left: 5px;
     right: 5px;
   }
 }
--- a/browser/components/loop/standalone/content/index.html
+++ b/browser/components/loop/standalone/content/index.html
@@ -2,16 +2,17 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/.  -->
 <html>
   <head>
     <meta charset="utf-8">
     <meta name="locales" content="en-US" />
     <meta name="default_locale" content="en-US" />
+    <meta name="referrer" content="origin" />
 
     <link rel="shortcut icon" href="favicon.ico">
 
     <link rel="stylesheet" type="text/css" href="shared/css/reset.css">
     <link rel="stylesheet" type="text/css" href="shared/css/common.css">
     <link rel="stylesheet" type="text/css" href="shared/css/conversation.css">
     <link rel="stylesheet" type="text/css" href="css/webapp.css">
     <link rel="localization" href="l10n/{locale}/loop.properties">
--- a/browser/components/loop/standalone/content/js/standaloneAppStore.js
+++ b/browser/components/loop/standalone/content/js/standaloneAppStore.js
@@ -16,17 +16,18 @@ loop.store.StandaloneAppStore = (functio
   var sharedUtils = loop.shared.utils;
 
   var CALL_REGEXP = /\/c\/([\w\-]+)$/;
   var ROOM_REGEXP = /\/([\w\-]+)$/;
 
   /**
    * Constructor
    *
-   * @param {Object} options Options for the store. Should contain the dispatcher.
+   * @param {Object} options Options for the store. Should contain the
+   *                         dispatcher.
    */
   var StandaloneAppStore = function(options) {
     if (!options.dispatcher) {
       throw new Error("Missing option dispatcher");
     }
     if (!options.sdk) {
       throw new Error("Missing option sdk");
     }
@@ -64,19 +65,19 @@ loop.store.StandaloneAppStore = (functio
       this.trigger("change");
     },
 
     _extractWindowDataFromPath: function(windowPath) {
       var match;
       var windowType = "home";
 
       function extractId(path, regexp) {
-        var match = path.match(regexp);
-        if (match && match[1]) {
-          return match;
+        var pathMatch = path.match(regexp);
+        if (pathMatch && pathMatch[1]) {
+          return pathMatch;
         }
         return null;
       }
 
       if (windowPath) {
         match = extractId(windowPath, CALL_REGEXP);
 
         if (match) {
@@ -136,18 +137,18 @@ loop.store.StandaloneAppStore = (functio
       }
 
       this.setStoreState({
         windowType: windowType,
         isFirefox: sharedUtils.isFirefox(navigator.userAgent),
         unsupportedPlatform: unsupportedPlatform
       });
 
-      // If we've not got a window ID, don't dispatch the action, as we don't need
-      // it.
+      // If we've not got a window ID, don't dispatch the action, as we don't
+      // need it.
       if (token) {
         this._dispatcher.dispatch(new loop.shared.actions.FetchServerData({
           cryptoKey: this._extractCryptoKey(actionData.windowHash),
           token: token,
           windowType: windowType
         }));
       }
     }
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -171,16 +171,17 @@ loop.standaloneRoomViews = (function(moz
     },
 
     render: function() {
       return (
         React.createElement("header", null, 
           React.createElement("h1", null, mozL10n.get("clientShortname2")), 
           React.createElement("a", {href: loop.config.generalSupportUrl, 
              onClick: this.recordClick, 
+             rel: "noreferrer", 
              target: "_blank"}, 
             React.createElement("i", {className: "icon icon-help"})
           )
         )
       );
     }
   });
 
@@ -191,22 +192,22 @@ loop.standaloneRoomViews = (function(moz
 
     _getContent: function() {
       // We use this technique of static markup as it means we get
       // just one overall string for L10n to define the structure of
       // the whole item.
       return mozL10n.get("legal_text_and_links", {
         "clientShortname": mozL10n.get("clientShortname2"),
         "terms_of_use_url": React.renderToStaticMarkup(
-          React.createElement("a", {href: loop.config.legalWebsiteUrl, target: "_blank"}, 
+          React.createElement("a", {href: loop.config.legalWebsiteUrl, rel: "noreferrer", target: "_blank"}, 
             mozL10n.get("terms_of_use_link_text")
           )
         ),
         "privacy_notice_url": React.renderToStaticMarkup(
-          React.createElement("a", {href: loop.config.privacyWebsiteUrl, target: "_blank"}, 
+          React.createElement("a", {href: loop.config.privacyWebsiteUrl, rel: "noreferrer", target: "_blank"}, 
             mozL10n.get("privacy_notice_link_text")
           )
         )
       });
     },
 
     recordClick: function(event) {
       // Check for valid href, as this is clicking on the paragraph -
@@ -224,116 +225,22 @@ loop.standaloneRoomViews = (function(moz
           React.createElement("div", {className: "footer-logo"}), 
           React.createElement("p", {dangerouslySetInnerHTML: {__html: this._getContent()}, 
              onClick: this.recordClick})
         )
       );
     }
   });
 
-  var StandaloneRoomContextItem = React.createClass({displayName: "StandaloneRoomContextItem",
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      receivingScreenShare: React.PropTypes.bool,
-      roomContextUrl: React.PropTypes.object
-    },
-
-    recordClick: function() {
-      this.props.dispatcher.dispatch(new sharedActions.RecordClick({
-        linkInfo: "Shared URL"
-      }));
-    },
-
-    render: function() {
-      if (!this.props.roomContextUrl ||
-          !this.props.roomContextUrl.location) {
-        return null;
-      }
-
-      var locationInfo = sharedUtils.formatURL(this.props.roomContextUrl.location);
-      if (!locationInfo) {
-        return null;
-      }
-
-      var cx = React.addons.classSet;
-
-      var classes = cx({
-        "standalone-context-url": true,
-        "screen-share-active": this.props.receivingScreenShare
-      });
-
-      return (
-        React.createElement("div", {className: classes}, 
-          React.createElement("img", {src: this.props.roomContextUrl.thumbnail || "shared/img/icons-16x16.svg#globe"}), 
-          React.createElement("div", {className: "standalone-context-url-description-wrapper"}, 
-            this.props.roomContextUrl.description, 
-            React.createElement("br", null), React.createElement("a", {href: locationInfo.location, 
-                     onClick: this.recordClick, 
-                     target: "_blank", 
-                     title: locationInfo.location}, locationInfo.hostname)
-          )
-        )
-      );
-    }
-  });
-
-  var StandaloneRoomContextView = React.createClass({displayName: "StandaloneRoomContextView",
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      receivingScreenShare: React.PropTypes.bool.isRequired,
-      roomContextUrls: React.PropTypes.array,
-      roomName: React.PropTypes.string,
-      roomInfoFailure: React.PropTypes.string
-    },
-
-    getInitialState: function() {
-      return {
-        failureLogged: false
-      };
-    },
-
-    _logFailure: function(message) {
-      if (!this.state.failureLogged) {
-        console.error(mozL10n.get(message));
-        this.state.failureLogged = true;
-      }
-    },
-
-    render: function() {
-      // For failures, we currently just log the messages - UX doesn't want them
-      // displayed on primary UI at the moment.
-      if (this.props.roomInfoFailure === ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED) {
-        this._logFailure("room_information_failure_unsupported_browser");
-        return null;
-      } else if (this.props.roomInfoFailure) {
-        this._logFailure("room_information_failure_not_available");
-        return null;
-      }
-
-      // We only support one item in the context Urls array for now.
-      var roomContextUrl = (this.props.roomContextUrls &&
-                            this.props.roomContextUrls.length > 0) ?
-                            this.props.roomContextUrls[0] : null;
-      return (
-        React.createElement("div", {className: "standalone-room-info"}, 
-          React.createElement("h2", {className: "room-name"}, this.props.roomName), 
-          React.createElement(StandaloneRoomContextItem, {
-            dispatcher: this.props.dispatcher, 
-            receivingScreenShare: this.props.receivingScreenShare, 
-            roomContextUrl: roomContextUrl})
-        )
-      );
-    }
-  });
-
   var StandaloneRoomView = React.createClass({displayName: "StandaloneRoomView",
     mixins: [
       Backbone.Events,
       sharedMixins.MediaSetupMixin,
-      sharedMixins.RoomsAudioMixin
+      sharedMixins.RoomsAudioMixin,
+      loop.store.StoreMixin("activeRoomStore")
     ],
 
     propTypes: {
       activeRoomStore: React.PropTypes.oneOfType([
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore),
         React.PropTypes.instanceOf(loop.store.FxOSActiveRoomStore)
       ]).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
@@ -347,272 +254,89 @@ loop.standaloneRoomViews = (function(moz
     getInitialState: function() {
       var storeState = this.props.activeRoomStore.getStoreState();
       return _.extend({}, storeState, {
         // Used by the UI showcase.
         roomState: this.props.roomState || storeState.roomState
       });
     },
 
-    componentWillMount: function() {
-      this.listenTo(this.props.activeRoomStore, "change",
-                    this._onActiveRoomStateChanged);
-    },
-
-    /**
-     * Handles a "change" event on the roomStore, and updates this.state
-     * to match the store.
-     *
-     * @private
-     */
-    _onActiveRoomStateChanged: function() {
-      var state = this.props.activeRoomStore.getStoreState();
-      this.updateVideoDimensions(state.localVideoDimensions, state.remoteVideoDimensions);
-      this.setState(state);
-    },
-
     componentDidMount: function() {
       // Adding a class to the document body element from here to ease styling it.
       document.body.classList.add("is-standalone-room");
     },
 
-    componentWillUnmount: function() {
-      this.stopListening(this.props.activeRoomStore);
-    },
-
     /**
      * Watches for when we transition to MEDIA_WAIT room state, so we can request
      * user media access.
      *
      * @param  {Object} nextProps (Unused)
      * @param  {Object} nextState Next state object.
      */
     componentWillUpdate: function(nextProps, nextState) {
       if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
           nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
         this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
           publisherConfig: this.getDefaultPublisherConfig({publishVideo: true})
         }));
       }
 
-      if (this.state.roomState !== ROOM_STATES.JOINED &&
-          nextState.roomState === ROOM_STATES.JOINED) {
-        // This forces the video size to update - creating the publisher
-        // first, and then connecting to the session doesn't seem to set the
-        // initial size correctly.
-        this.updateVideoContainer();
-      }
-
-      if (nextState.roomState === ROOM_STATES.INIT ||
-          nextState.roomState === ROOM_STATES.GATHER ||
-          nextState.roomState === ROOM_STATES.READY) {
-        this.resetDimensionsCache();
-      }
-
-      // When screen sharing stops.
-      if (this.state.receivingScreenShare && !nextState.receivingScreenShare) {
-        // Remove the custom screenshare styles on the remote camera.
-        var node = this._getElement(".remote");
-        node.removeAttribute("style");
-      }
-
-      if (this.state.receivingScreenShare != nextState.receivingScreenShare ||
-          this.state.remoteVideoEnabled != nextState.remoteVideoEnabled) {
-        this.updateVideoContainer();
+      // UX don't want to surface these errors (as they would imply the user
+      // needs to do something to fix them, when if they're having a conversation
+      // they just need to connect). However, we do want there to be somewhere to
+      // find reasonably easily, in case there's issues raised.
+      if (!this.state.roomInfoFailure && nextState.roomInfoFailure) {
+        if (nextState.roomInfoFailure === ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED) {
+          console.error(mozL10n.get("room_information_failure_unsupported_browser"));
+        } else {
+          console.error(mozL10n.get("room_information_failure_not_available"));
+        }
       }
     },
 
     joinRoom: function() {
       this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
     },
 
     leaveRoom: function() {
       this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
     },
 
     /**
-     * Wrapper for window.matchMedia so that we use an appropriate version
-     * for the ui-showcase, which puts views inside of their own iframes.
-     *
-     * Currently, we use an icky hack, and the showcase conspires with
-     * react-frame-component to set iframe.contentWindow.matchMedia onto
-     * activeRoomStore.  Once React context matures a bit (somewhere between
-     * 0.14 and 1.0, apparently):
-     *
-     * https://facebook.github.io/react/blog/2015/02/24/streamlining-react-elements.html#solution-make-context-parent-based-instead-of-owner-based
-     *
-     * we should be able to use those to clean this up.
-     *
-     * @param queryString
-     * @returns {MediaQueryList|null}
-     * @private
-     */
-    _matchMedia: function(queryString) {
-      if ("matchMedia" in this.state) {
-        return this.state.matchMedia(queryString);
-      } else if ("matchMedia" in window) {
-        return window.matchMedia(queryString);
-      }
-      return null;
-    },
-
-    /**
      * Toggles streaming status for a given stream type.
      *
      * @param  {String}  type     Stream type ("audio" or "video").
      * @param  {Boolean} enabled  Enabled stream flag.
      */
     publishStream: function(type, enabled) {
       this.props.dispatcher.dispatch(new sharedActions.SetMute({
         type: type,
         enabled: enabled
       }));
     },
 
     /**
-     * Specifically updates the local camera stream size and position, depending
-     * on the size and position of the remote video stream.
-     * This method gets called from `updateVideoContainer`, which is defined in
-     * the `MediaSetupMixin`.
-     *
-     * @param  {Object} ratio Aspect ratio of the local camera stream
-     */
-    updateLocalCameraPosition: function(ratio) {
-      // The local stream is a quarter of the remote stream.
-      var LOCAL_STREAM_SIZE = 0.25;
-      // The local stream overlaps the remote stream by a quarter of the local stream.
-      var LOCAL_STREAM_OVERLAP = 0.25;
-      // The minimum size of video height/width allowed by the sdk css.
-      var SDK_MIN_SIZE = 48;
-
-      var node = this._getElement(".local");
-      var targetWidth;
-
-      node.style.right = "auto";
-      if (this._matchMedia("screen and (max-width:640px)").matches) {
-        // For reduced screen widths, we just go for a fixed size and no overlap.
-        targetWidth = 180;
-        node.style.width = (targetWidth * ratio.width) + "px";
-        node.style.height = (targetWidth * ratio.height) + "px";
-        node.style.left = "auto";
-      } else {
-        // The local camera view should be a quarter of the size of the remote stream
-        // and positioned to overlap with the remote stream at a quarter of its width.
-
-        // Now position the local camera view correctly with respect to the remote
-        // video stream or the screen share stream.
-        var remoteVideoDimensions;
-        var isScreenShare = this.state.receivingScreenShare;
-        var videoDisplayed = isScreenShare ?
-          this.state.screenShareVideoObject || this.props.screenSharePosterUrl :
-          this.state.remoteSrcVideoObject || this.props.remotePosterUrl;
-
-        if ((isScreenShare || this.shouldRenderRemoteVideo()) && videoDisplayed) {
-          remoteVideoDimensions = this.getRemoteVideoDimensions(
-            isScreenShare ? "screen" : "camera");
-        } else {
-          var remoteElement = this.getDOMNode().querySelector(".remote.focus-stream");
-          if (!remoteElement) {
-            return;
-          }
-          remoteVideoDimensions = {
-            streamWidth: remoteElement.offsetWidth,
-            offsetX: remoteElement.offsetLeft
-          };
-        }
-
-        targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
-
-        var realWidth = targetWidth * ratio.width;
-        var realHeight = targetWidth * ratio.height;
-
-        // If we've hit the min size limits, then limit at the minimum.
-        if (realWidth < SDK_MIN_SIZE) {
-          realWidth = SDK_MIN_SIZE;
-          realHeight = realWidth / ratio.width * ratio.height;
-        }
-        if (realHeight < SDK_MIN_SIZE) {
-          realHeight = SDK_MIN_SIZE;
-          realWidth = realHeight / ratio.height * ratio.width;
-        }
-
-        var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
-        // The horizontal offset of the stream, and the width of the resulting
-        // pillarbox, is determined by the height exponent of the aspect ratio.
-        // Therefore we multiply the width of the local camera view by the height
-        // ratio.
-        node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
-        node.style.width = realWidth + "px";
-        node.style.height = realHeight + "px";
-      }
-    },
-
-    /**
-     * Specifically updates the remote camera stream size and position, if
-     * a screen share is being received. It is slaved from the position of the
-     * local stream.
-     * This method gets called from `updateVideoContainer`, which is defined in
-     * the `MediaSetupMixin`.
-     *
-     * @param  {Object} ratio Aspect ratio of the remote camera stream
-     */
-    updateRemoteCameraPosition: function(ratio) {
-      // Nothing to do for screenshare
-      if (!this.state.receivingScreenShare) {
-        return;
-      }
-      // XXX For the time being, if we're a narrow screen, aka mobile, we don't display
-      // the remote media (bug 1133534).
-      if (this._matchMedia("screen and (max-width:640px)").matches) {
-        return;
-      }
-
-      // 10px separation between the two streams.
-      var LOCAL_REMOTE_SEPARATION = 10;
-
-      var node = this._getElement(".remote");
-      var localNode = this._getElement(".local");
-
-      // Match the width to the local video.
-      node.style.width = localNode.offsetWidth + "px";
-
-      // The height is then determined from the aspect ratio
-      var height = ((localNode.offsetWidth / ratio.width) * ratio.height);
-      node.style.height = height + "px";
-
-      node.style.right = "auto";
-      node.style.bottom = "auto";
-
-      // Now position the local camera view correctly with respect to the remote
-      // video stream.
-
-      // The top is measured from the top of the element down the screen,
-      // so subtract the height of the video and the separation distance.
-      node.style.top = (localNode.offsetTop - height - LOCAL_REMOTE_SEPARATION) + "px";
-
-      // Match the left-hand sides.
-      node.style.left = localNode.offsetLeft + "px";
-    },
-
-    /**
      * Checks if current room is active.
      *
      * @return {Boolean}
      */
     _roomIsActive: function() {
       return this.state.roomState === ROOM_STATES.JOINED            ||
              this.state.roomState === ROOM_STATES.SESSION_CONNECTED ||
              this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS;
     },
 
     /**
      * Works out if remote video should be rended or not, depending on the
      * room state and other flags.
      *
      * @return {Boolean} True if remote video should be rended.
+     *
+     * XXX Refactor shouldRenderRemoteVideo & shouldRenderLoading to remove
+     *     overlapping cases.
      */
     shouldRenderRemoteVideo: function() {
       switch(this.state.roomState) {
         case ROOM_STATES.HAS_PARTICIPANTS:
           if (this.state.remoteVideoEnabled) {
             return true;
           }
 
@@ -641,100 +365,133 @@ loop.standaloneRoomViews = (function(moz
         default:
           console.warn("StandaloneRoomView.shouldRenderRemoteVideo:" +
             " unexpected roomState: ", this.state.roomState);
           return true;
 
       }
     },
 
+    /**
+     * Should we render a visual cue to the user (e.g. a spinner) that a local
+     * stream is on its way from the camera?
+     *
+     * @returns {boolean}
+     * @private
+     */
+    _shouldRenderLocalLoading: function () {
+      return this.state.roomState === ROOM_STATES.MEDIA_WAIT &&
+             !this.state.localSrcVideoObject;
+    },
+
+    /**
+     * Should we render a visual cue to the user (e.g. a spinner) that a remote
+     * stream is on its way from the other user?
+     *
+     * @returns {boolean}
+     * @private
+     */
+    _shouldRenderRemoteLoading: function() {
+      return this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
+             !this.state.remoteSrcVideoObject &&
+             !this.state.mediaConnected;
+    },
+
+    /**
+     * Should we render a visual cue to the user (e.g. a spinner) that a remote
+     * screen-share is on its way from the other user?
+     *
+     * @returns {boolean}
+     * @private
+     */
+    _shouldRenderScreenShareLoading: function() {
+      return this.state.receivingScreenShare &&
+             !this.state.screenShareVideoObject;
+    },
+
     render: function() {
-      var localStreamClasses = React.addons.classSet({
-        local: true,
-        "local-stream": true,
-        "local-stream-audio": this.state.videoMuted
-      });
+      var displayScreenShare = this.state.receivingScreenShare ||
+        this.props.screenSharePosterUrl;
 
       var remoteStreamClasses = React.addons.classSet({
-        "video_inner": true,
         "remote": true,
-        "focus-stream": !this.state.receivingScreenShare,
-        "remote-inset-stream": this.state.receivingScreenShare
+        "focus-stream": !displayScreenShare
       });
 
       var screenShareStreamClasses = React.addons.classSet({
         "screen": true,
-        "focus-stream": this.state.receivingScreenShare,
-        hide: !this.state.receivingScreenShare
+        "focus-stream": displayScreenShare
+      });
+
+      var mediaWrapperClasses = React.addons.classSet({
+        "media-wrapper": true,
+        "receiving-screen-share": displayScreenShare,
+        "showing-local-streams": this.state.localSrcVideoObject ||
+          this.props.localPosterUrl
       });
 
       return (
         React.createElement("div", {className: "room-conversation-wrapper"}, 
           React.createElement("div", {className: "beta-logo"}), 
-          React.createElement(sharedViews.TextChatView, {dispatcher: this.props.dispatcher}), 
           React.createElement(StandaloneRoomHeader, {dispatcher: this.props.dispatcher}), 
           React.createElement(StandaloneRoomInfoArea, {roomState: this.state.roomState, 
                                   failureReason: this.state.failureReason, 
                                   joinRoom: this.joinRoom, 
                                   isFirefox: this.props.isFirefox, 
                                   activeRoomStore: this.props.activeRoomStore, 
                                   roomUsed: this.state.used}), 
-          React.createElement("div", {className: "video-layout-wrapper"}, 
-            React.createElement("div", {className: "conversation room-conversation"}, 
-              React.createElement(StandaloneRoomContextView, {
+          React.createElement("div", {className: "media-layout"}, 
+            React.createElement("div", {className: mediaWrapperClasses}, 
+              React.createElement("span", {className: "self-view-hidden-message"}, 
+                mozL10n.get("self_view_hidden_message")
+              ), 
+              React.createElement("div", {className: remoteStreamClasses}, 
+                React.createElement(sharedViews.MediaView, {displayAvatar: !this.shouldRenderRemoteVideo(), 
+                  posterUrl: this.props.remotePosterUrl, 
+                  isLoading: this._shouldRenderRemoteLoading(), 
+                  mediaType: "remote", 
+                  srcVideoObject: this.state.remoteSrcVideoObject})
+              ), 
+              React.createElement("div", {className: screenShareStreamClasses}, 
+                React.createElement(sharedViews.MediaView, {displayAvatar: false, 
+                  posterUrl: this.props.screenSharePosterUrl, 
+                  isLoading: this._shouldRenderScreenShareLoading(), 
+                  mediaType: "screen-share", 
+                  srcVideoObject: this.state.screenShareVideoObject})
+              ), 
+              React.createElement(sharedViews.TextChatView, {
                 dispatcher: this.props.dispatcher, 
-                receivingScreenShare: this.state.receivingScreenShare, 
-                roomContextUrls: this.state.roomContextUrls, 
-                roomName: this.state.roomName, 
-                roomInfoFailure: this.state.roomInfoFailure}), 
-              React.createElement("div", {className: "media nested"}, 
-                React.createElement("span", {className: "self-view-hidden-message"}, 
-                  mozL10n.get("self_view_hidden_message")
-                ), 
-                React.createElement("div", {className: "video_wrapper remote_wrapper"}, 
-                  React.createElement("div", {className: remoteStreamClasses}, 
-                    React.createElement(sharedViews.MediaView, {displayAvatar: !this.shouldRenderRemoteVideo(), 
-                      posterUrl: this.props.remotePosterUrl, 
-                      mediaType: "remote", 
-                      srcVideoObject: this.state.remoteSrcVideoObject})
-                  ), 
-                  React.createElement("div", {className: screenShareStreamClasses}, 
-                    React.createElement(sharedViews.MediaView, {displayAvatar: false, 
-                      posterUrl: this.props.screenSharePosterUrl, 
-                      mediaType: "screen-share", 
-                      srcVideoObject: this.state.screenShareVideoObject})
-                  )
-                ), 
-                React.createElement("div", {className: localStreamClasses}, 
-                  React.createElement(sharedViews.MediaView, {displayAvatar: this.state.videoMuted, 
-                    posterUrl: this.props.localPosterUrl, 
-                    mediaType: "local", 
-                    srcVideoObject: this.state.localSrcVideoObject})
-                )
-              ), 
-              React.createElement(sharedViews.ConversationToolbar, {
-                dispatcher: this.props.dispatcher, 
-                video: {enabled: !this.state.videoMuted,
-                        visible: this._roomIsActive()}, 
-                audio: {enabled: !this.state.audioMuted,
-                        visible: this._roomIsActive()}, 
-                publishStream: this.publishStream, 
-                hangup: this.leaveRoom, 
-                hangupButtonLabel: mozL10n.get("rooms_leave_button_label"), 
-                enableHangup: this._roomIsActive()})
-            )
+                showAlways: true, 
+                showRoomName: true}), 
+              React.createElement("div", {className: "local"}, 
+                React.createElement(sharedViews.MediaView, {displayAvatar: this.state.videoMuted, 
+                  posterUrl: this.props.localPosterUrl, 
+                  isLoading: this._shouldRenderLocalLoading(), 
+                  mediaType: "local", 
+                  srcVideoObject: this.state.localSrcVideoObject})
+              )
+            ), 
+            React.createElement(sharedViews.ConversationToolbar, {
+              dispatcher: this.props.dispatcher, 
+              video: {enabled: !this.state.videoMuted,
+                      visible: this._roomIsActive()}, 
+              audio: {enabled: !this.state.audioMuted,
+                      visible: this._roomIsActive()}, 
+              publishStream: this.publishStream, 
+              hangup: this.leaveRoom, 
+              hangupButtonLabel: mozL10n.get("rooms_leave_button_label"), 
+              enableHangup: this._roomIsActive()})
           ), 
           React.createElement(loop.fxOSMarketplaceViews.FxOSHiddenMarketplaceView, {
             marketplaceSrc: this.state.marketplaceSrc, 
             onMarketplaceMessage: this.state.onMarketplaceMessage}), 
           React.createElement(StandaloneRoomFooter, {dispatcher: this.props.dispatcher})
         )
       );
     }
   });
 
   return {
-    StandaloneRoomContextView: StandaloneRoomContextView,
     StandaloneRoomFooter: StandaloneRoomFooter,
     StandaloneRoomHeader: StandaloneRoomHeader,
     StandaloneRoomView: StandaloneRoomView
   };
 })(navigator.mozL10n);
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -171,16 +171,17 @@ loop.standaloneRoomViews = (function(moz
     },
 
     render: function() {
       return (
         <header>
           <h1>{mozL10n.get("clientShortname2")}</h1>
           <a href={loop.config.generalSupportUrl}
              onClick={this.recordClick}
+             rel="noreferrer"
              target="_blank">
             <i className="icon icon-help"></i>
           </a>
         </header>
       );
     }
   });
 
@@ -191,22 +192,22 @@ loop.standaloneRoomViews = (function(moz
 
     _getContent: function() {
       // We use this technique of static markup as it means we get
       // just one overall string for L10n to define the structure of
       // the whole item.
       return mozL10n.get("legal_text_and_links", {
         "clientShortname": mozL10n.get("clientShortname2"),
         "terms_of_use_url": React.renderToStaticMarkup(
-          <a href={loop.config.legalWebsiteUrl} target="_blank">
+          <a href={loop.config.legalWebsiteUrl} rel="noreferrer" target="_blank">
             {mozL10n.get("terms_of_use_link_text")}
           </a>
         ),
         "privacy_notice_url": React.renderToStaticMarkup(
-          <a href={loop.config.privacyWebsiteUrl} target="_blank">
+          <a href={loop.config.privacyWebsiteUrl} rel="noreferrer" target="_blank">
             {mozL10n.get("privacy_notice_link_text")}
           </a>
         )
       });
     },
 
     recordClick: function(event) {
       // Check for valid href, as this is clicking on the paragraph -
@@ -224,116 +225,22 @@ loop.standaloneRoomViews = (function(moz
           <div className="footer-logo" />
           <p dangerouslySetInnerHTML={{__html: this._getContent()}}
              onClick={this.recordClick}></p>
         </footer>
       );
     }
   });
 
-  var StandaloneRoomContextItem = React.createClass({
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      receivingScreenShare: React.PropTypes.bool,
-      roomContextUrl: React.PropTypes.object
-    },
-
-    recordClick: function() {
-      this.props.dispatcher.dispatch(new sharedActions.RecordClick({
-        linkInfo: "Shared URL"
-      }));
-    },
-
-    render: function() {
-      if (!this.props.roomContextUrl ||
-          !this.props.roomContextUrl.location) {
-        return null;
-      }
-
-      var locationInfo = sharedUtils.formatURL(this.props.roomContextUrl.location);
-      if (!locationInfo) {
-        return null;
-      }
-
-      var cx = React.addons.classSet;
-
-      var classes = cx({
-        "standalone-context-url": true,
-        "screen-share-active": this.props.receivingScreenShare
-      });
-
-      return (
-        <div className={classes}>
-          <img src={this.props.roomContextUrl.thumbnail || "shared/img/icons-16x16.svg#globe"} />
-          <div className="standalone-context-url-description-wrapper">
-            {this.props.roomContextUrl.description}
-            <br /><a href={locationInfo.location}
-                     onClick={this.recordClick}
-                     target="_blank"
-                     title={locationInfo.location}>{locationInfo.hostname}</a>
-          </div>
-        </div>
-      );
-    }
-  });
-
-  var StandaloneRoomContextView = React.createClass({
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      receivingScreenShare: React.PropTypes.bool.isRequired,
-      roomContextUrls: React.PropTypes.array,
-      roomName: React.PropTypes.string,
-      roomInfoFailure: React.PropTypes.string
-    },
-
-    getInitialState: function() {
-      return {
-        failureLogged: false
-      };
-    },
-
-    _logFailure: function(message) {
-      if (!this.state.failureLogged) {
-        console.error(mozL10n.get(message));
-        this.state.failureLogged = true;
-      }
-    },
-
-    render: function() {
-      // For failures, we currently just log the messages - UX doesn't want them
-      // displayed on primary UI at the moment.
-      if (this.props.roomInfoFailure === ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED) {
-        this._logFailure("room_information_failure_unsupported_browser");
-        return null;
-      } else if (this.props.roomInfoFailure) {
-        this._logFailure("room_information_failure_not_available");
-        return null;
-      }
-
-      // We only support one item in the context Urls array for now.
-      var roomContextUrl = (this.props.roomContextUrls &&
-                            this.props.roomContextUrls.length > 0) ?
-                            this.props.roomContextUrls[0] : null;
-      return (
-        <div className="standalone-room-info">
-          <h2 className="room-name">{this.props.roomName}</h2>
-          <StandaloneRoomContextItem
-            dispatcher={this.props.dispatcher}
-            receivingScreenShare={this.props.receivingScreenShare}
-            roomContextUrl={roomContextUrl} />
-        </div>
-      );
-    }
-  });
-
   var StandaloneRoomView = React.createClass({
     mixins: [
       Backbone.Events,
       sharedMixins.MediaSetupMixin,
-      sharedMixins.RoomsAudioMixin
+      sharedMixins.RoomsAudioMixin,
+      loop.store.StoreMixin("activeRoomStore")
     ],
 
     propTypes: {
       activeRoomStore: React.PropTypes.oneOfType([
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore),
         React.PropTypes.instanceOf(loop.store.FxOSActiveRoomStore)
       ]).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
@@ -347,272 +254,89 @@ loop.standaloneRoomViews = (function(moz
     getInitialState: function() {
       var storeState = this.props.activeRoomStore.getStoreState();
       return _.extend({}, storeState, {
         // Used by the UI showcase.
         roomState: this.props.roomState || storeState.roomSta