Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 17 Jan 2017 09:01:06 -0500
changeset 379109 c52fa5ad5b28d10afb19beb3d2d9e7769d3b74d6
parent 379108 87aa6ecc99aa5cb7c7f8c1f38d610093b8a2845c (current diff)
parent 365418 3e275d37a06236981bff399b7d7aa0646be3fee7 (diff)
child 379110 de76905b9d39d63a17ec45e44dfaaea6c9f0d90d
push idunknown
push userunknown
push dateunknown
milestone53.0a1
Merge m-c to graphics MozReview-Commit-ID: KzmBdQoXRRI
CLOBBER
devtools/client/debugger/new/bundle.js
devtools/client/debugger/new/styles.css
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
gfx/layers/Compositor.h
gfx/layers/ipc/PCompositorBridge.ipdl
gfx/thebes/gfxPlatform.cpp
gfx/thebes/gfxPrefs.h
ipc/keystore/KeyStore.cpp
ipc/keystore/KeyStore.h
ipc/keystore/KeyStoreConnector.cpp
ipc/keystore/KeyStoreConnector.h
ipc/keystore/moz.build
js/src/jit/BaselineCacheIRCompiler.cpp
layout/reftests/svg/reftest.list
layout/reftests/transform-3d/reftest.list
layout/reftests/w3c-css/submitted/masking/reftest.list
media/libvpx/1237848-check-lookahead-ctx.patch
media/libvpx/block_error_fp.patch
media/libvpx/cast-char-to-uint-before-shift.patch
media/libvpx/clamp-abs-QIndex.patch
media/libvpx/clamp_abs_lvl_seg.patch
media/libvpx/generate_sources_mozbuild.sh
media/libvpx/libvpx/build/make/ads2armasm_ms.pl
media/libvpx/libvpx/build/make/ads2gas_apple.pl
media/libvpx/libvpx/build/make/armlink_adapter.sh
media/libvpx/libvpx/build/make/gen_asm_deps.sh
media/libvpx/libvpx/build/make/gen_msvs_def.sh
media/libvpx/libvpx/build/make/gen_msvs_proj.sh
media/libvpx/libvpx/build/make/gen_msvs_sln.sh
media/libvpx/libvpx/build/make/gen_msvs_vcxproj.sh
media/libvpx/libvpx/build/make/iosbuild.sh
media/libvpx/libvpx/build/make/rtcd.pl
media/libvpx/libvpx/build/make/version.sh
media/libvpx/libvpx/configure
media/libvpx/libvpx/test/decode_to_md5.sh
media/libvpx/libvpx/test/decode_with_drops.sh
media/libvpx/libvpx/test/examples.sh
media/libvpx/libvpx/test/intrapred_test.cc
media/libvpx/libvpx/test/postproc.sh
media/libvpx/libvpx/test/resize_util.sh
media/libvpx/libvpx/test/set_maps.sh
media/libvpx/libvpx/test/simple_decoder.sh
media/libvpx/libvpx/test/simple_encoder.sh
media/libvpx/libvpx/test/subtract_test.cc
media/libvpx/libvpx/test/tools_common.sh
media/libvpx/libvpx/test/twopass_encoder.sh
media/libvpx/libvpx/test/vp8_multi_resolution_encoder.sh
media/libvpx/libvpx/test/vp8cx_set_ref.sh
media/libvpx/libvpx/test/vp9_avg_test.cc
media/libvpx/libvpx/test/vp9_spatial_svc_encoder.sh
media/libvpx/libvpx/test/vpx_temporal_svc_encoder.sh
media/libvpx/libvpx/test/vpxdec.sh
media/libvpx/libvpx/test/vpxenc.sh
media/libvpx/libvpx/third_party/libwebm/RELEASE.TXT
media/libvpx/libvpx/third_party/libwebm/mkvmuxer.cpp
media/libvpx/libvpx/third_party/libwebm/mkvmuxer.hpp
media/libvpx/libvpx/third_party/libwebm/mkvmuxertypes.hpp
media/libvpx/libvpx/third_party/libwebm/mkvmuxerutil.cpp
media/libvpx/libvpx/third_party/libwebm/mkvmuxerutil.hpp
media/libvpx/libvpx/third_party/libwebm/mkvparser.cpp
media/libvpx/libvpx/third_party/libwebm/mkvparser.hpp
media/libvpx/libvpx/third_party/libwebm/mkvreader.cpp
media/libvpx/libvpx/third_party/libwebm/mkvreader.hpp
media/libvpx/libvpx/third_party/libwebm/mkvwriter.cpp
media/libvpx/libvpx/third_party/libwebm/mkvwriter.hpp
media/libvpx/libvpx/third_party/libwebm/webmids.hpp
media/libvpx/libvpx/third_party/libyuv/source/compare_posix.cc
media/libvpx/libvpx/third_party/libyuv/source/row_posix.cc
media/libvpx/libvpx/third_party/libyuv/source/scale_posix.cc
media/libvpx/libvpx/tools/all_builds.py
media/libvpx/libvpx/tools/author_first_release.sh
media/libvpx/libvpx/tools/cpplint.py
media/libvpx/libvpx/tools/ftfy.sh
media/libvpx/libvpx/tools/gen_authors.sh
media/libvpx/libvpx/tools/intersect-diffs.py
media/libvpx/libvpx/tools/lint-hunks.py
media/libvpx/libvpx/tools/vpx-astyle.sh
media/libvpx/libvpx/tools/wrap-commit-msg.py
media/libvpx/libvpx/vp8/common/arm/armv6/intra4x4_predict_v6.asm
media/libvpx/libvpx/vp8/common/arm/armv6/vp8_variance_halfpixvar16x16_h_armv6.asm
media/libvpx/libvpx/vp8/common/arm/armv6/vp8_variance_halfpixvar16x16_hv_armv6.asm
media/libvpx/libvpx/vp8/common/arm/armv6/vp8_variance_halfpixvar16x16_v_armv6.asm
media/libvpx/libvpx/vp8/common/arm/neon/loopfilter_neon.c
media/libvpx/libvpx/vp8/common/arm/neon/reconintra_neon.c
media/libvpx/libvpx/vp8/common/arm/neon/vp8_subpixelvariance_neon.c
media/libvpx/libvpx/vp8/common/arm/variance_arm.c
media/libvpx/libvpx/vp8/common/loopfilter.c
media/libvpx/libvpx/vp8/common/mips/dspr2/loopfilter_filters_dspr2.c
media/libvpx/libvpx/vp8/common/variance.h
media/libvpx/libvpx/vp8/common/variance_c.c
media/libvpx/libvpx/vp8/common/x86/loopfilter_mmx.asm
media/libvpx/libvpx/vp8/common/x86/recon_wrapper_sse2.c
media/libvpx/libvpx/vp8/common/x86/variance_impl_sse2.asm
media/libvpx/libvpx/vp8/common/x86/variance_impl_ssse3.asm
media/libvpx/libvpx/vp8/common/x86/variance_ssse3.c
media/libvpx/libvpx/vp8/common/x86/vp8_variance_impl_mmx.asm
media/libvpx/libvpx/vp8/common/x86/vp8_variance_mmx.c
media/libvpx/libvpx/vp8/common/x86/vp8_variance_sse2.c
media/libvpx/libvpx/vp8/encoder/arm/neon/subtract_neon.c
media/libvpx/libvpx/vp8/encoder/quantize.c
media/libvpx/libvpx/vp8/encoder/ssim.c
media/libvpx/libvpx/vp8/encoder/x86/quantize_sse2.c
media/libvpx/libvpx/vp8/encoder/x86/ssim_opt_x86_64.asm
media/libvpx/libvpx/vp8/encoder/x86/subtract_mmx.asm
media/libvpx/libvpx/vp8/encoder/x86/subtract_sse2.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_convolve8_avg_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_convolve8_avg_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_convolve8_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_convolve8_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_convolve_avg_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_convolve_avg_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_convolve_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_copy_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_copy_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct16x16_1_add_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct16x16_1_add_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct16x16_add_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct16x16_add_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct16x16_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct32x32_1_add_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct32x32_1_add_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct32x32_add_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct32x32_add_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct4x4_1_add_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct4x4_1_add_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct4x4_add_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct4x4_add_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct8x8_1_add_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct8x8_1_add_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct8x8_add_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_idct8x8_add_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_loopfilter_16_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_loopfilter_16_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_loopfilter_4_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_loopfilter_4_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_loopfilter_8_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_loopfilter_8_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_loopfilter_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_mb_lpf_neon.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_reconintra_neon.c
media/libvpx/libvpx/vp9/common/arm/neon/vp9_reconintra_neon_asm.asm
media/libvpx/libvpx/vp9/common/arm/neon/vp9_save_reg_neon.asm
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_common_dspr2.h
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_convolve2_avg_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_convolve2_avg_horiz_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_convolve2_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_convolve2_horiz_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_convolve2_vert_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_convolve8_avg_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_convolve8_avg_horiz_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_convolve8_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_convolve8_horiz_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_convolve8_vert_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_intrapred16_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_intrapred4_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_intrapred8_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_itrans32_cols_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_itrans32_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_loopfilter_filters_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_loopfilter_filters_dspr2.h
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_loopfilter_macros_dspr2.h
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_loopfilter_masks_dspr2.h
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_mbloop_loopfilter_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_mblpf_horiz_loopfilter_dspr2.c
media/libvpx/libvpx/vp9/common/mips/dspr2/vp9_mblpf_vert_loopfilter_dspr2.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_convolve8_avg_horiz_msa.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_convolve8_avg_msa.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_convolve8_avg_vert_msa.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_convolve8_horiz_msa.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_convolve8_msa.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_convolve8_vert_msa.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_convolve_avg_msa.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_convolve_copy_msa.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_convolve_msa.h
media/libvpx/libvpx/vp9/common/mips/msa/vp9_idct32x32_msa.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_idct_msa.h
media/libvpx/libvpx/vp9/common/mips/msa/vp9_loopfilter_16_msa.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_loopfilter_4_msa.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_loopfilter_8_msa.c
media/libvpx/libvpx/vp9/common/mips/msa/vp9_loopfilter_msa.h
media/libvpx/libvpx/vp9/common/mips/msa/vp9_macros_msa.h
media/libvpx/libvpx/vp9/common/vp9_convolve.c
media/libvpx/libvpx/vp9/common/vp9_convolve.h
media/libvpx/libvpx/vp9/common/vp9_loopfilter_filters.c
media/libvpx/libvpx/vp9/common/vp9_prob.c
media/libvpx/libvpx/vp9/common/vp9_prob.h
media/libvpx/libvpx/vp9/common/vp9_systemdependent.h
media/libvpx/libvpx/vp9/common/vp9_thread.c
media/libvpx/libvpx/vp9/common/vp9_thread.h
media/libvpx/libvpx/vp9/common/x86/convolve.h
media/libvpx/libvpx/vp9/common/x86/vp9_asm_stubs.c
media/libvpx/libvpx/vp9/common/x86/vp9_copy_sse2.asm
media/libvpx/libvpx/vp9/common/x86/vp9_high_intrapred_sse2.asm
media/libvpx/libvpx/vp9/common/x86/vp9_high_loopfilter_intrin_sse2.c
media/libvpx/libvpx/vp9/common/x86/vp9_high_subpixel_8t_sse2.asm
media/libvpx/libvpx/vp9/common/x86/vp9_high_subpixel_bilinear_sse2.asm
media/libvpx/libvpx/vp9/common/x86/vp9_idct_intrin_sse2.h
media/libvpx/libvpx/vp9/common/x86/vp9_idct_ssse3_x86_64.asm
media/libvpx/libvpx/vp9/common/x86/vp9_intrapred_sse2.asm
media/libvpx/libvpx/vp9/common/x86/vp9_intrapred_ssse3.asm
media/libvpx/libvpx/vp9/common/x86/vp9_loopfilter_intrin_avx2.c
media/libvpx/libvpx/vp9/common/x86/vp9_loopfilter_intrin_sse2.c
media/libvpx/libvpx/vp9/common/x86/vp9_loopfilter_mmx.asm
media/libvpx/libvpx/vp9/common/x86/vp9_subpixel_8t_intrin_avx2.c
media/libvpx/libvpx/vp9/common/x86/vp9_subpixel_8t_intrin_ssse3.c
media/libvpx/libvpx/vp9/common/x86/vp9_subpixel_8t_sse2.asm
media/libvpx/libvpx/vp9/common/x86/vp9_subpixel_8t_ssse3.asm
media/libvpx/libvpx/vp9/common/x86/vp9_subpixel_bilinear_sse2.asm
media/libvpx/libvpx/vp9/common/x86/vp9_subpixel_bilinear_ssse3.asm
media/libvpx/libvpx/vp9/decoder/vp9_read_bit_buffer.c
media/libvpx/libvpx/vp9/decoder/vp9_read_bit_buffer.h
media/libvpx/libvpx/vp9/decoder/vp9_reader.c
media/libvpx/libvpx/vp9/decoder/vp9_reader.h
media/libvpx/libvpx/vp9/encoder/arm/neon/vp9_subtract_neon.c
media/libvpx/libvpx/vp9/encoder/arm/neon/vp9_variance_neon.c
media/libvpx/libvpx/vp9/encoder/arm/neon/vp9enc_avg_neon.c
media/libvpx/libvpx/vp9/encoder/vp9_avg.c
media/libvpx/libvpx/vp9/encoder/vp9_dct.h
media/libvpx/libvpx/vp9/encoder/vp9_fastssim.c
media/libvpx/libvpx/vp9/encoder/vp9_psnrhvs.c
media/libvpx/libvpx/vp9/encoder/vp9_ssim.c
media/libvpx/libvpx/vp9/encoder/vp9_ssim.h
media/libvpx/libvpx/vp9/encoder/vp9_variance.c
media/libvpx/libvpx/vp9/encoder/vp9_variance.h
media/libvpx/libvpx/vp9/encoder/vp9_write_bit_buffer.c
media/libvpx/libvpx/vp9/encoder/vp9_write_bit_buffer.h
media/libvpx/libvpx/vp9/encoder/vp9_writer.c
media/libvpx/libvpx/vp9/encoder/vp9_writer.h
media/libvpx/libvpx/vp9/encoder/x86/vp9_avg_intrin_sse2.c
media/libvpx/libvpx/vp9/encoder/x86/vp9_dct32x32_avx2_impl.h
media/libvpx/libvpx/vp9/encoder/x86/vp9_dct32x32_sse2_impl.h
media/libvpx/libvpx/vp9/encoder/x86/vp9_dct_avx2.c
media/libvpx/libvpx/vp9/encoder/x86/vp9_dct_mmx.asm
media/libvpx/libvpx/vp9/encoder/x86/vp9_dct_sse2.c
media/libvpx/libvpx/vp9/encoder/x86/vp9_dct_sse2.h
media/libvpx/libvpx/vp9/encoder/x86/vp9_dct_sse2_impl.h
media/libvpx/libvpx/vp9/encoder/x86/vp9_dct_ssse3_x86_64.asm
media/libvpx/libvpx/vp9/encoder/x86/vp9_highbd_quantize_intrin_sse2.c
media/libvpx/libvpx/vp9/encoder/x86/vp9_highbd_subpel_variance.asm
media/libvpx/libvpx/vp9/encoder/x86/vp9_highbd_variance_sse2.c
media/libvpx/libvpx/vp9/encoder/x86/vp9_ssim_opt_x86_64.asm
media/libvpx/libvpx/vp9/encoder/x86/vp9_subpel_variance.asm
media/libvpx/libvpx/vp9/encoder/x86/vp9_subpel_variance_impl_intrin_avx2.c
media/libvpx/libvpx/vp9/encoder/x86/vp9_subtract_sse2.asm
media/libvpx/libvpx/vp9/encoder/x86/vp9_variance_avx2.c
media/libvpx/libvpx/vp9/encoder/x86/vp9_variance_sse2.c
media/libvpx/libvpx/vpx_dsp/x86/sad_mmx.asm
media/libvpx/libvpx/vpx_dsp/x86/variance_impl_mmx.asm
media/libvpx/libvpx/vpx_dsp/x86/variance_mmx.c
media/libvpx/lint_config.sh
media/libvpx/vp9_filter_restore_aligment.patch
media/libvpx/vpx_once.patch
modules/libjar/zipwriter/test/unit/tail_zipwriter.js
modules/libpref/init/all.js
testing/web-platform/meta/eventsource/dedicated-worker/eventsource-close.htm.ini
testing/web-platform/meta/eventsource/dedicated-worker/eventsource-constructor-non-same-origin.htm.ini
testing/web-platform/meta/eventsource/dedicated-worker/eventsource-constructor-url-bogus.htm.ini
testing/web-platform/meta/eventsource/dedicated-worker/eventsource-eventtarget.worker.js.ini
testing/web-platform/meta/eventsource/dedicated-worker/eventsource-onmesage.htm.ini
testing/web-platform/meta/eventsource/dedicated-worker/eventsource-onopen.htm.ini
testing/web-platform/meta/eventsource/dedicated-worker/eventsource-prototype.htm.ini
testing/web-platform/meta/eventsource/dedicated-worker/eventsource-url.htm.ini
testing/web-platform/meta/eventsource/shared-worker/eventsource-close.htm.ini
testing/web-platform/meta/eventsource/shared-worker/eventsource-constructor-non-same-origin.htm.ini
testing/web-platform/meta/eventsource/shared-worker/eventsource-constructor-url-bogus.htm.ini
testing/web-platform/meta/eventsource/shared-worker/eventsource-eventtarget.htm.ini
testing/web-platform/meta/eventsource/shared-worker/eventsource-onmesage.htm.ini
testing/web-platform/meta/eventsource/shared-worker/eventsource-onopen.htm.ini
testing/web-platform/meta/eventsource/shared-worker/eventsource-prototype.htm.ini
testing/web-platform/meta/eventsource/shared-worker/eventsource-url.htm.ini
testing/web-platform/meta/html/semantics/selectors/pseudo-classes/inrange-outofrange.html.ini
toolkit/components/extensions/test/mochitest/test_chrome_ext_shutdown_cleanup.html
toolkit/components/printingui/mac/nsPrintingPromptServiceX.mm
toolkit/components/url-classifier/tests/unit/tail_urlclassifier.js
toolkit/identity/tests/unit/tail_identity.js
uriloader/exthandler/tests/unit/tail_handlerService.js
widget/gtk/nsWindow.cpp
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,17 +1,17 @@
 # Always ignore node_modules.
 **/node_modules/**/*.*
 
 # Exclude expected objdirs.
 obj*/**
 
 # We ignore all these directories by default, until we get them enabled.
 # If you are enabling a directory, please add directory specific exclusions
-# below.
+# below.
 addon-sdk/**
 build/**
 caps/**
 chrome/**
 config/**
 db/**
 docshell/**
 dom/**
@@ -97,26 +97,21 @@ devtools/client/webconsole/net/**
 devtools/client/webconsole/test/**
 devtools/client/webconsole/console-output.js
 devtools/client/webconsole/hudservice.js
 devtools/client/webconsole/utils.js
 devtools/client/webconsole/webconsole-connection-proxy.js
 devtools/client/webconsole/webconsole.js
 devtools/client/webide/**
 !devtools/client/webide/components/webideCli.js
-devtools/server/actors/*.js
-!devtools/server/actors/csscoverage.js
-!devtools/server/actors/inspector.js
-!devtools/server/actors/layout.js
-!devtools/server/actors/string.js
-!devtools/server/actors/styles.js
-!devtools/server/actors/tab.js
-!devtools/server/actors/webbrowser.js
-!devtools/server/actors/webextension.js
-!devtools/server/actors/webextension-inspected-window.js
+devtools/server/actors/webconsole.js
+devtools/server/actors/object.js
+devtools/server/actors/script.js
+devtools/server/actors/styleeditor.js
+devtools/server/actors/stylesheets.js
 devtools/server/tests/browser/**
 !devtools/server/tests/browser/browser_webextension_inspected_window.js
 devtools/server/tests/mochitest/**
 devtools/server/tests/unit/**
 devtools/shared/heapsnapshot/**
 devtools/shared/transport/tests/unit/**
 devtools/shared/webconsole/test/**
 
--- 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 1328744 libvpx sources moved directories
+Bug 1223692 vp8/common/loopfilter.c removed.
--- a/accessible/base/nsAccessibilityService.h
+++ b/accessible/base/nsAccessibilityService.h
@@ -338,22 +338,17 @@ nsAccessibilityService* GetOrCreateAccSe
 void MaybeShutdownAccService(uint32_t aFormerConsumer);
 
 /**
  * Return true if we're in a content process and not B2G.
  */
 inline bool
 IPCAccessibilityActive()
 {
-#ifdef MOZ_B2G
-  return false;
-#else
-  return XRE_IsContentProcess() &&
-    mozilla::Preferences::GetBool("accessibility.ipc_architecture.enabled", true);
-#endif
+  return XRE_IsContentProcess();
 }
 
 /**
  * Map nsIAccessibleEvents constants to strings. Used by
  * nsAccessibilityService::GetStringEventType() method.
  */
 static const char kEventTypeNames[][40] = {
   "unknown",                                 //
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -186,16 +186,20 @@ DocAccessibleParent::RecvHideEvent(const
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 DocAccessibleParent::RecvEvent(const uint64_t& aID, const uint32_t& aEventType)
 {
+  if (mShutdown) {
+    return IPC_OK();
+  }
+
   ProxyAccessible* proxy = GetAccessible(aID);
   if (!proxy) {
     NS_ERROR("no proxy for event!");
     return IPC_OK();
   }
 
   ProxyEvent(proxy, aEventType);
 
@@ -214,16 +218,20 @@ DocAccessibleParent::RecvEvent(const uin
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 DocAccessibleParent::RecvStateChangeEvent(const uint64_t& aID,
                                           const uint64_t& aState,
                                           const bool& aEnabled)
 {
+  if (mShutdown) {
+    return IPC_OK();
+  }
+
   ProxyAccessible* target = GetAccessible(aID);
   if (!target) {
     NS_ERROR("we don't know about the target of a state change event!");
     return IPC_OK();
   }
 
   ProxyStateChangeEvent(target, aState, aEnabled);
 
@@ -244,16 +252,20 @@ DocAccessibleParent::RecvStateChangeEven
   nsCoreUtils::DispatchAccEvent(Move(event));
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 DocAccessibleParent::RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset)
 {
+  if (mShutdown) {
+    return IPC_OK();
+  }
+
   ProxyAccessible* proxy = GetAccessible(aID);
   if (!proxy) {
     NS_ERROR("unknown caret move event target!");
     return IPC_OK();
   }
 
   ProxyCaretMoveEvent(proxy, aOffset);
 
@@ -276,16 +288,20 @@ DocAccessibleParent::RecvCaretMoveEvent(
 mozilla::ipc::IPCResult
 DocAccessibleParent::RecvTextChangeEvent(const uint64_t& aID,
                                          const nsString& aStr,
                                          const int32_t& aStart,
                                          const uint32_t& aLen,
                                          const bool& aIsInsert,
                                          const bool& aFromUser)
 {
+  if (mShutdown) {
+    return IPC_OK();
+  }
+
   ProxyAccessible* target = GetAccessible(aID);
   if (!target) {
     NS_ERROR("text change event target is unknown!");
     return IPC_OK();
   }
 
   ProxyTextChangeEvent(target, aStr, aStart, aLen, aIsInsert, aFromUser);
 
@@ -306,16 +322,20 @@ DocAccessibleParent::RecvTextChangeEvent
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 DocAccessibleParent::RecvSelectionEvent(const uint64_t& aID,
                                         const uint64_t& aWidgetID,
                                         const uint32_t& aType)
 {
+  if (mShutdown) {
+    return IPC_OK();
+  }
+
   ProxyAccessible* target = GetAccessible(aID);
   ProxyAccessible* widget = GetAccessible(aWidgetID);
   if (!target || !widget) {
     NS_ERROR("invalid id in selection event");
     return IPC_OK();
   }
 
   ProxySelectionEvent(target, widget, aType);
@@ -329,16 +349,20 @@ DocAccessibleParent::RecvSelectionEvent(
   nsCoreUtils::DispatchAccEvent(Move(event));
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 DocAccessibleParent::RecvRoleChangedEvent(const uint32_t& aRole)
 {
+  if (mShutdown) {
+    return IPC_OK();
+  }
+
  if (aRole >= roles::LAST_ROLE) {
    NS_ERROR("child sent bad role in RoleChangedEvent");
    return IPC_FAIL_NO_REASON(this);
  }
 
  mRole = static_cast<a11y::role>(aRole);
  return IPC_OK();
 }
@@ -431,16 +455,21 @@ DocAccessibleParent::Destroy()
     mChildDocs[i]->Destroy();
 
   for (auto iter = mAccessibles.Iter(); !iter.Done(); iter.Next()) {
     MOZ_ASSERT(iter.Get()->mProxy != this);
     ProxyDestroyed(iter.Get()->mProxy);
     iter.Remove();
   }
 
+  // The code above should have already completely cleared these, but to be
+  // extra safe make sure they are cleared here.
+  mAccessibles.Clear();
+  mChildDocs.Clear();
+
   DocManager::NotifyOfRemoteDocShutdown(this);
   ProxyDestroyed(this);
   if (mParentDoc)
     mParentDoc->RemoveChildDoc(this);
   else if (IsTopLevel())
     GetAccService()->RemoteDocShutdown(this);
 }
 
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -531,16 +531,19 @@ pref("privacy.sanitize.timeSpan", 1);
 pref("privacy.sanitize.sanitizeOnShutdown", false);
 
 pref("privacy.sanitize.migrateFx3Prefs",    false);
 
 pref("privacy.panicButton.enabled",         true);
 
 pref("privacy.firstparty.isolate",          false);
 
+// Time until temporary permissions expire, in ms
+pref("privacy.temporary_permission_expire_time_ms",  3600000);
+
 pref("network.proxy.share_proxy_settings",  false); // use the same proxy settings for all protocols
 
 // simple gestures support
 pref("browser.gesture.swipe.left", "Browser:BackOrBackDuplicate");
 pref("browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate");
 pref("browser.gesture.swipe.up", "cmd_scrollTop");
 pref("browser.gesture.swipe.down", "cmd_scrollBottom");
 #ifdef XP_MACOSX
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1122,16 +1122,20 @@ var gBrowserInit = {
       // Reset the zoom for the tabcrashed page.
       ZoomManager.setZoomForBrowser(browser, 1);
     }, false, true);
 
     gBrowser.addEventListener("InsecureLoginFormsStateChange", function() {
       gIdentityHandler.refreshForInsecureLoginForms();
     });
 
+    gBrowser.addEventListener("PermissionStateChange", function() {
+      gIdentityHandler.refreshIdentityBlock();
+    });
+
     let uriToLoad = this._getUriToLoad();
     if (uriToLoad && uriToLoad != "about:blank") {
       if (uriToLoad instanceof Ci.nsIArray) {
         let count = uriToLoad.length;
         let specs = [];
         for (let i = 0; i < count; i++) {
           let urisstring = uriToLoad.queryElementAt(i, Ci.nsISupportsString);
           specs.push(urisstring.data);
@@ -3220,16 +3224,20 @@ function BrowserReloadWithFlags(reloadFl
   if (gBrowser.updateBrowserRemotenessByURL(gBrowser.selectedBrowser, url)) {
     // If the remoteness has changed, the new browser doesn't have any
     // information of what was loaded before, so we need to load the previous
     // URL again.
     gBrowser.loadURIWithFlags(url, reloadFlags);
     return;
   }
 
+  // Reset temporary permissions on the current tab. This is done here
+  // because we only want to reset permissions on user reload.
+  SitePermissions.clearTemporaryPermissions(gBrowser.selectedBrowser);
+
   let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindowUtils);
 
   gBrowser.selectedBrowser
           .messageManager
           .sendAsyncMessage("Browser:Reload",
                             { flags: reloadFlags,
                               handlingUserInput: windowUtils.isHandlingUserInput });
@@ -3322,17 +3330,17 @@ function getPEMString(cert) {
 
 var PrintPreviewListener = {
   _printPreviewTab: null,
   _tabBeforePrintPreview: null,
   _simplifyPageTab: null,
 
   getPrintPreviewBrowser() {
     if (!this._printPreviewTab) {
-      let browser = gBrowser.selectedTab.linkedBrowser;
+      let browser = gBrowser.selectedBrowser;
       let preferredRemoteType = browser.remoteType;
       this._tabBeforePrintPreview = gBrowser.selectedTab;
       this._printPreviewTab = gBrowser.loadOneTab("about:blank",
                                                   { inBackground: false,
                                                     preferredRemoteType,
                                                     relatedBrowser: browser });
       gBrowser.selectedTab = this._printPreviewTab;
     }
@@ -4638,18 +4646,22 @@ var XULBrowserWindow = {
   // aRequest will be null always in case 2 and 3, and sometimes in case 1 (for
   // instance, there won't be a request when STATE_BLOCKED_TRACKING_CONTENT is observed).
   onSecurityChange(aWebProgress, aRequest, aState, aIsSimulated) {
     // Don't need to do anything if the data we use to update the UI hasn't
     // changed
     let uri = gBrowser.currentURI;
     let spec = uri.spec;
     if (this._state == aState &&
-        this._lastLocation == spec)
+        this._lastLocation == spec) {
+      // Switching to a tab of the same URL doesn't change most security
+      // information, but tab specific permissions may be different.
+      gIdentityHandler.refreshIdentityBlock();
       return;
+    }
     this._state = aState;
     this._lastLocation = spec;
 
     if (typeof(aIsSimulated) != "boolean" && typeof(aIsSimulated) != "undefined") {
       throw "onSecurityChange: aIsSimulated receieved an unexpected type";
     }
 
     // Make sure the "https" part of the URL is striked out or not,
@@ -7016,26 +7028,26 @@ var gIdentityHandler = {
     for (let icon of Object.values(permissionAnchors)) {
       icon.removeAttribute("showing");
     }
 
     // keeps track if we should show an indicator that there are active permissions
     let hasGrantedPermissions = false;
 
     // show permission icons
-    for (let permission of SitePermissions.getAllByURI(this._uri)) {
-      if (permission.state === SitePermissions.BLOCK) {
+    let permissions = SitePermissions.getAllForBrowser(gBrowser.selectedBrowser);
+    for (let permission of permissions) {
+      if (permission.state == SitePermissions.BLOCK) {
 
         let icon = permissionAnchors[permission.id];
         if (icon) {
           icon.setAttribute("showing", "true");
         }
 
-      } else if (permission.state === SitePermissions.ALLOW ||
-                 permission.state === SitePermissions.SESSION) {
+      } else if (permission.state != SitePermissions.UNKNOWN) {
         hasGrantedPermissions = true;
       }
     }
 
     if (hasGrantedPermissions) {
       this._identityBox.classList.add("grantedPermissions");
     }
 
@@ -7360,19 +7372,19 @@ var gIdentityHandler = {
       this._permissionEmptyHint.removeAttribute("hidden");
     }
   },
 
   updateSitePermissions() {
     while (this._permissionList.hasChildNodes())
       this._permissionList.removeChild(this._permissionList.lastChild);
 
-    let uri = gBrowser.currentURI;
-
-    let permissions = SitePermissions.getPermissionDetailsByURI(uri);
+    let permissions =
+      SitePermissions.getAllPermissionDetailsForBrowser(gBrowser.selectedBrowser);
+
     if (this._sharingState) {
       // If WebRTC device or screen permissions are in use, we need to find
       // the associated permission item to set the inUse field to true.
       for (let id of ["camera", "microphone", "screen"]) {
         if (this._sharingState[id]) {
           let found = false;
           for (let permission of permissions) {
             if (permission.id != id)
@@ -7380,17 +7392,18 @@ var gIdentityHandler = {
             found = true;
             permission.inUse = true;
             break;
           }
           if (!found) {
             // If the permission item we were looking for doesn't exist,
             // the user has temporarily allowed sharing and we need to add
             // an item in the permissions array to reflect this.
-            let permission = SitePermissions.getPermissionItem(id);
+            let permission =
+              SitePermissions.getPermissionDetails(id, SitePermissions.SCOPE_REQUEST);
             permission.inUse = true;
             permissions.push(permission);
           }
         }
       }
     }
     for (let permission of permissions) {
       let item = this._createPermissionItem(permission);
@@ -7436,67 +7449,83 @@ var gIdentityHandler = {
     let nameLabel = document.createElement("label");
     nameLabel.setAttribute("flex", "1");
     nameLabel.setAttribute("class", "identity-popup-permission-label");
     nameLabel.textContent = SitePermissions.getPermissionLabel(aPermission.id);
 
     let stateLabel = document.createElement("label");
     stateLabel.setAttribute("flex", "1");
     stateLabel.setAttribute("class", "identity-popup-permission-state-label");
-    stateLabel.textContent = SitePermissions.getStateLabel(
-      aPermission.id, aPermission.state, aPermission.inUse || false);
+    let {state, scope} = aPermission;
+    // If the user did not permanently allow this device but it is currently
+    // used, set the variables to display a "temporarily allowed" info.
+    if (state != SitePermissions.ALLOW && aPermission.inUse) {
+      state = SitePermissions.ALLOW;
+      scope = SitePermissions.SCOPE_REQUEST;
+    }
+    stateLabel.textContent = SitePermissions.getStateLabel(state, scope);
 
     let button = document.createElement("button");
     button.setAttribute("class", "identity-popup-permission-remove-button");
     let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip");
     button.setAttribute("tooltiptext", tooltiptext);
     button.addEventListener("command", () => {
+	  let browser = gBrowser.selectedBrowser;
       // Only resize the window if the reload hint was previously hidden.
       this._handleHeightChange(() => this._permissionList.removeChild(container),
                                this._permissionReloadHint.hasAttribute("hidden"));
       if (aPermission.inUse &&
           ["camera", "microphone", "screen"].includes(aPermission.id)) {
         let windowId = this._sharingState.windowId;
         if (aPermission.id == "screen") {
           windowId = "screen:" + windowId;
         } else {
           // If we set persistent permissions or the sharing has
           // started due to existing persistent permissions, we need
           // to handle removing these even for frames with different hostnames.
-          let uris = gBrowser.selectedBrowser._devicePermissionURIs || [];
+          let uris = browser._devicePermissionURIs || [];
           for (let uri of uris) {
             // It's not possible to stop sharing one of camera/microphone
             // without the other.
             for (let id of ["camera", "microphone"]) {
-              if (this._sharingState[id] &&
-                  SitePermissions.get(uri, id) == SitePermissions.ALLOW)
-                SitePermissions.remove(uri, id);
+              if (this._sharingState[id]) {
+                let perm = SitePermissions.get(uri, id);
+                if (perm.state == SitePermissions.ALLOW &&
+                    perm.scope == SitePermissions.SCOPE_PERSISTENT) {
+                  SitePermissions.remove(uri, id);
+                }
+              }
             }
           }
         }
-        let mm = gBrowser.selectedBrowser.messageManager;
-        mm.sendAsyncMessage("webrtc:StopSharing", windowId);
+        browser.messageManager.sendAsyncMessage("webrtc:StopSharing", windowId);
       }
-      SitePermissions.remove(gBrowser.currentURI, aPermission.id);
+      SitePermissions.remove(gBrowser.currentURI, aPermission.id, browser);
 
       this._permissionReloadHint.removeAttribute("hidden");
 
       // Set telemetry values for clearing a permission
       let histogram = Services.telemetry.getKeyedHistogramById("WEB_PERMISSION_CLEARED");
 
       let permissionType = 0;
-      if (aPermission.state == SitePermissions.ALLOW) {
+      if (aPermission.state == SitePermissions.ALLOW &&
+          aPermission.scope == SitePermissions.SCOPE_PERSISTENT) {
         // 1 : clear permanently allowed permission
         permissionType = 1;
-      } else if (aPermission.state == SitePermissions.BLOCK) {
+      } else if (aPermission.state == SitePermissions.BLOCK &&
+                 aPermission.scope == SitePermissions.SCOPE_PERSISTENT) {
         // 2 : clear permanently blocked permission
         permissionType = 2;
+      } else if (aPermission.state == SitePermissions.ALLOW) {
+        // 3 : clear temporary allowed permission
+        permissionType = 3;
+      } else if (aPermission.state == SitePermissions.BLOCK) {
+        // 4 : clear temporary blocked permission
+        permissionType = 4;
       }
-      // 3 : TODO clear temporary allowed permission
-      // 4 : TODO clear temporary blocked permission
 
       histogram.add("(all)", permissionType);
       histogram.add(aPermission.id, permissionType);
     });
 
     container.appendChild(img);
     container.appendChild(nameLabel);
     container.appendChild(stateLabel);
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -68,27 +68,27 @@ function initRow(aPartId) {
     initPluginsRow();
     return;
   }
 
   createRow(aPartId);
 
   var checkbox = document.getElementById(aPartId + "Def");
   var command  = document.getElementById("cmd_" + aPartId + "Toggle");
-  var perm = SitePermissions.get(gPermURI, aPartId);
+  var {state} = SitePermissions.get(gPermURI, aPartId);
 
-  if (perm) {
+  if (state != SitePermissions.UNKNOWN) {
     checkbox.checked = false;
     command.removeAttribute("disabled");
   } else {
     checkbox.checked = true;
     command.setAttribute("disabled", "true");
-    perm = SitePermissions.getDefault(aPartId);
+    state = SitePermissions.getDefault(aPartId);
   }
-  setRadioState(aPartId, perm);
+  setRadioState(aPartId, state);
 
   if (aPartId == "indexedDB") {
     initIndexedDBRow();
   }
 }
 
 function createRow(aPartId) {
   let rowId = "perm-" + aPartId + "-row";
@@ -130,17 +130,17 @@ function createRow(aPartId) {
   controls.appendChild(spacer);
 
   let radiogroup = document.createElement("radiogroup");
   radiogroup.setAttribute("id", radiogroupId);
   radiogroup.setAttribute("orient", "horizontal");
   for (let state of SitePermissions.getAvailableStates(aPartId)) {
     let radio = document.createElement("radio");
     radio.setAttribute("id", aPartId + "#" + state);
-    radio.setAttribute("label", SitePermissions.getStateLabel(aPartId, state));
+    radio.setAttribute("label", SitePermissions.getStateLabel(state));
     radio.setAttribute("command", commandId);
     radiogroup.appendChild(radio);
   }
   controls.appendChild(radiogroup);
 
   row.appendChild(controls);
 
   document.getElementById("permList").appendChild(row);
@@ -309,12 +309,12 @@ function initPluginsRow() {
 }
 
 function setPluginsRadioState() {
   let box = document.getElementById("perm-plugins-row");
   for (let permissionEntry of box.childNodes) {
     if (permissionEntry.hasAttribute("permString")) {
       let permString = permissionEntry.getAttribute("permString");
       let permission = SitePermissions.get(gPermURI, permString);
-      setRadioState(permString, permission);
+      setRadioState(permString, permission.state);
     }
   }
 }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1683,17 +1683,18 @@
               }
               if (!isRemote && aBrowser.contentWindow.opener != aOptions.opener) {
                 throw new Error("Cannot change opener on an already non-remote browser!");
               }
             }
 
             // Abort if we're not going to change anything
             if (isRemote == aShouldBeRemote && !aOptions.newFrameloader && !aOptions.freshProcess &&
-                (!isRemote || aBrowser.getAttribute("remoteType") == aOptions.remoteType)) {
+                (!isRemote || aBrowser.getAttribute("remoteType") == aOptions.remoteType) &&
+                !aBrowser.frameLoader.isFreshProcess) {
               return false;
             }
 
             let tab = this.getTabForBrowser(aBrowser);
             let evt = document.createEvent("Events");
             evt.initEvent("BeforeTabRemotenessChange", true, false);
             tab.dispatchEvent(evt);
 
@@ -1830,17 +1831,18 @@
 
             // If this URL can't load in the current browser then flip it to the
             // correct type.
             let currentRemoteType = aBrowser.remoteType;
             aOptions.remoteType =
               E10SUtils.getRemoteTypeForURI(aURL, gMultiProcessBrowser,
                                             currentRemoteType);
             if (currentRemoteType != aOptions.remoteType ||
-                aOptions.freshProcess || aOptions.newFrameloader) {
+                aOptions.freshProcess || aOptions.newFrameloader ||
+                aBrowser.frameLoader.isFreshProcess) {
               let remote = aOptions.remoteType != E10SUtils.NOT_REMOTE;
               return this.updateBrowserRemoteness(aBrowser, remote, aOptions);
             }
 
             return false;
           ]]>
         </body>
       </method>
@@ -2865,16 +2867,18 @@
             }
             if (aOtherTab.hasAttribute("sharing")) {
               aOurTab.setAttribute("sharing", aOtherTab.getAttribute("sharing"));
               modifiedAttrs.push("sharing");
               aOurTab._sharingState = aOtherTab._sharingState;
               webrtcUI.swapBrowserForNotification(otherBrowser, ourBrowser);
             }
 
+            SitePermissions.copyTemporaryPermissions(otherBrowser, ourBrowser);
+
             // If the other tab is pending (i.e. has not been restored, yet)
             // then do not switch docShells but retrieve the other tab's state
             // and apply it to our tab.
             if (isPending) {
               SessionStore.setTabState(aOurTab, SessionStore.getTabState(aOtherTab));
 
               // Make sure to unregister any open URIs.
               this._swapRegisteredOpenURIs(ourBrowser, otherBrowser);
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -415,16 +415,21 @@ skip-if = os == "linux" || os == "mac" #
 skip-if = (os == "mac" && !e10s) # Bug 1237713 - OSX eats keypresses for some reason
 [browser_tabopen_reflows.js]
 [browser_tabs_close_beforeunload.js]
 support-files =
   close_beforeunload_opens_second_tab.html
   close_beforeunload.html
 [browser_tabs_isActive.js]
 [browser_tabs_owner.js]
+[browser_temporary_permissions.js]
+support-files =
+  permissions.html
+  temporary_permissions_subframe.html
+[browser_temporary_permissions_navigation.js]
 [browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js]
 run-if = e10s
 [browser_trackingUI_1.js]
 tags = trackingprotection
 support-files =
   trackingPage.html
   benignPage.html
 [browser_trackingUI_2.js]
--- a/browser/base/content/test/general/browser_parsable_css.js
+++ b/browser/base/content/test/general/browser_parsable_css.js
@@ -9,17 +9,17 @@
  * matching the offending error. If an object has multiple regex criteria, they
  * ALL need to match an error in order for that error not to cause a test
  * failure. */
 let whitelist = [
   // CodeMirror is imported as-is, see bug 1004423.
   {sourceName: /codemirror\.css$/i,
    isFromDevTools: true},
   // The debugger uses cross-browser CSS.
-  {sourceName: /devtools\/client\/debugger\/new\/styles.css/i,
+  {sourceName: /devtools\/client\/debugger\/new\/debugger.css/i,
    isFromDevTools: true},
   // PDFjs is futureproofing its pseudoselectors, and those rules are dropped.
   {sourceName: /web\/viewer\.css$/i,
    errorMessage: /Unknown pseudo-class.*(fullscreen|selection)/i,
    isFromDevTools: false},
   // PDFjs rules needed for compat with other UAs.
   {sourceName: /web\/viewer\.css$/i,
    errorMessage: /Unknown property.*appearance/i,
--- a/browser/base/content/test/general/browser_permissions.js
+++ b/browser/base/content/test/general/browser_permissions.js
@@ -85,17 +85,17 @@ add_task(function* testIdentityIcon() {
   ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
     "identity-box doesn't signal granted permissions");
 
   SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK);
 
   ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
     "identity-box doesn't signal granted permissions");
 
-  SitePermissions.set(gBrowser.currentURI, "cookie", SitePermissions.SESSION);
+  SitePermissions.set(gBrowser.currentURI, "cookie", SitePermissions.ALLOW_COOKIES_FOR_SESSION);
 
   ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
     "identity-box signals granted permissions");
 
   SitePermissions.remove(gBrowser.currentURI, "geo");
   SitePermissions.remove(gBrowser.currentURI, "camera");
   SitePermissions.remove(gBrowser.currentURI, "cookie");
 });
@@ -174,29 +174,23 @@ add_task(function* testPermissionHints()
 
 add_task(function* testPermissionIcons() {
   let {gIdentityHandler} = gBrowser.ownerGlobal;
   let tab = gBrowser.selectedTab = gBrowser.addTab();
   yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
 
   SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.ALLOW);
   SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.BLOCK);
-  SitePermissions.set(gBrowser.currentURI, "microphone", SitePermissions.SESSION);
 
   let geoIcon = gIdentityHandler._identityBox
     .querySelector(".blocked-permission-icon[data-permission-id='geo']");
   ok(geoIcon.hasAttribute("showing"), "blocked permission icon is shown");
 
   let cameraIcon = gIdentityHandler._identityBox
     .querySelector(".blocked-permission-icon[data-permission-id='camera']");
   ok(!cameraIcon.hasAttribute("showing"),
     "allowed permission icon is not shown");
 
-  let microphoneIcon  = gIdentityHandler._identityBox
-    .querySelector(".blocked-permission-icon[data-permission-id='microphone']");
-  ok(!microphoneIcon.hasAttribute("showing"),
-    "allowed permission icon is not shown");
-
   SitePermissions.remove(gBrowser.currentURI, "geo");
 
   ok(!geoIcon.hasAttribute("showing"),
     "blocked permission icon is not shown after reset");
 });
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_temporary_permissions.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource:///modules/SitePermissions.jsm", this);
+Cu.import("resource:///modules/E10SUtils.jsm");
+
+const SUBFRAME_PAGE = "https://example.com/browser/browser/base/content/test/general/temporary_permissions_subframe.html";
+
+// Test that setting temp permissions triggers a change in the identity block.
+add_task(function* testTempPermissionChangeEvents() {
+  let uri = NetUtil.newURI("https://example.com");
+  let id = "geo";
+
+  yield BrowserTestUtils.withNewTab(uri.spec, function*(browser) {
+    SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
+
+    Assert.deepEqual(SitePermissions.get(uri, id, browser), {
+      state: SitePermissions.BLOCK,
+      scope: SitePermissions.SCOPE_TEMPORARY,
+    });
+
+    let geoIcon = document.querySelector(".blocked-permission-icon[data-permission-id=geo]");
+
+    Assert.notEqual(geoIcon.boxObject.width, 0, "geo anchor should be visible");
+
+    SitePermissions.remove(uri, id, browser);
+
+    Assert.equal(geoIcon.boxObject.width, 0, "geo anchor should not be visible");
+  });
+});
+
+// Test that temp permissions are persisted through moving tabs to new windows.
+add_task(function* testTempPermissionOnTabMove() {
+  let uri = NetUtil.newURI("https://example.com");
+  let id = "geo";
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, uri.spec);
+
+  SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, tab.linkedBrowser);
+
+  Assert.deepEqual(SitePermissions.get(uri, id, tab.linkedBrowser), {
+    state: SitePermissions.BLOCK,
+    scope: SitePermissions.SCOPE_TEMPORARY,
+  });
+
+  let promiseWin = BrowserTestUtils.waitForNewWindow();
+  gBrowser.replaceTabWithWindow(tab);
+  let win = yield promiseWin;
+  tab = win.gBrowser.selectedTab;
+
+  Assert.deepEqual(SitePermissions.get(uri, id, tab.linkedBrowser), {
+    state: SitePermissions.BLOCK,
+    scope: SitePermissions.SCOPE_TEMPORARY,
+  });
+
+  SitePermissions.remove(uri, id, tab.linkedBrowser);
+  yield BrowserTestUtils.closeWindow(win);
+});
+
+// Test that temp permissions don't affect other tabs of the same URI.
+add_task(function* testTempPermissionMultipleTabs() {
+  let uri = NetUtil.newURI("https://example.com");
+  let id = "geo";
+
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, uri.spec);
+  let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, uri.spec);
+
+  SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, tab2.linkedBrowser);
+
+  Assert.deepEqual(SitePermissions.get(uri, id, tab2.linkedBrowser), {
+    state: SitePermissions.BLOCK,
+    scope: SitePermissions.SCOPE_TEMPORARY,
+  });
+
+  Assert.deepEqual(SitePermissions.get(uri, id, tab1.linkedBrowser), {
+    state: SitePermissions.UNKNOWN,
+    scope: SitePermissions.SCOPE_PERSISTENT,
+  });
+
+  let geoIcon = document.querySelector(".blocked-permission-icon[data-permission-id=geo]");
+
+  Assert.notEqual(geoIcon.boxObject.width, 0, "geo anchor should be visible");
+
+  yield BrowserTestUtils.switchTab(gBrowser, tab1);
+
+  Assert.equal(geoIcon.boxObject.width, 0, "geo anchor should not be visible");
+
+  SitePermissions.remove(uri, id, tab2.linkedBrowser);
+  yield BrowserTestUtils.removeTab(tab1);
+  yield BrowserTestUtils.removeTab(tab2);
+});
+
+// Test that temp blocked permissions requested by subframes (with a different URI) affect the whole page.
+add_task(function* testTempPermissionSubframes() {
+  let uri = NetUtil.newURI("https://example.com");
+  let id = "geo";
+
+  yield BrowserTestUtils.withNewTab(SUBFRAME_PAGE, function*(browser) {
+    let popupshown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+
+    // Request a permission;
+    yield ContentTask.spawn(browser, uri.host, function(host) {
+      E10SUtils.wrapHandlingUserInput(content, true, function() {
+        let frame = content.document.getElementById("frame");
+        let frameDoc = frame.contentWindow.document;
+
+        // Make sure that the origin of our test page is different.
+        Assert.notEqual(frameDoc.location.host, host);
+
+        frameDoc.getElementById("geo").click();
+      });
+    });
+
+    yield popupshown;
+
+    let popuphidden = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+
+    let notification = PopupNotifications.panel.firstChild;
+    EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
+
+    yield popuphidden;
+
+    Assert.deepEqual(SitePermissions.get(uri, id, browser), {
+      state: SitePermissions.BLOCK,
+      scope: SitePermissions.SCOPE_TEMPORARY,
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_temporary_permissions_navigation.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource:///modules/SitePermissions.jsm", this);
+
+// Test that temporary permissions are removed on user initiated reload only.
+add_task(function* testTempPermissionOnReload() {
+  let uri = NetUtil.newURI("https://example.com");
+  let id = "geo";
+
+  yield BrowserTestUtils.withNewTab(uri.spec, function*(browser) {
+    let reloadButton = document.getElementById("urlbar-reload-button");
+
+    SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
+
+    let reloaded = BrowserTestUtils.browserLoaded(browser, false, uri.spec);
+
+    Assert.deepEqual(SitePermissions.get(uri, id, browser), {
+      state: SitePermissions.BLOCK,
+      scope: SitePermissions.SCOPE_TEMPORARY,
+    });
+
+    // Reload through the page (should not remove the temp permission).
+    yield ContentTask.spawn(browser, {}, () => content.document.location.reload());
+
+    yield reloaded;
+    yield BrowserTestUtils.waitForCondition(() => {
+      return reloadButton.disabled == false;
+    });
+
+    Assert.deepEqual(SitePermissions.get(uri, id, browser), {
+      state: SitePermissions.BLOCK,
+      scope: SitePermissions.SCOPE_TEMPORARY,
+    });
+
+    // Reload as a user (should remove the temp permission).
+    EventUtils.synthesizeMouseAtCenter(reloadButton, {});
+
+    Assert.deepEqual(SitePermissions.get(uri, id, browser), {
+      state: SitePermissions.UNKNOWN,
+      scope: SitePermissions.SCOPE_PERSISTENT,
+    });
+
+    SitePermissions.remove(uri, id, browser);
+  });
+});
+
+// Test that temporary permissions are persisted through navigation in a tab.
+add_task(function* testTempPermissionOnNavigation() {
+  let uri = NetUtil.newURI("https://example.com/");
+  let id = "geo";
+
+  yield BrowserTestUtils.withNewTab(uri.spec, function*(browser) {
+    SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
+
+    Assert.deepEqual(SitePermissions.get(uri, id, browser), {
+      state: SitePermissions.BLOCK,
+      scope: SitePermissions.SCOPE_TEMPORARY,
+    });
+
+    let loaded = BrowserTestUtils.browserLoaded(browser, false, "https://example.org/");
+
+    // Navigate to another domain.
+    yield ContentTask.spawn(browser, {}, () => content.document.location = "https://example.org/");
+
+    yield loaded;
+
+    // The temporary permissions for the current URI should be reset.
+    Assert.deepEqual(SitePermissions.get(browser.currentURI, id, browser), {
+      state: SitePermissions.UNKNOWN,
+      scope: SitePermissions.SCOPE_PERSISTENT,
+    });
+
+    loaded = BrowserTestUtils.browserLoaded(browser, false, uri.spec);
+
+    // Navigate to the original domain.
+    yield ContentTask.spawn(browser, {}, () => content.document.location = "https://example.com/");
+
+    yield loaded;
+
+    // The temporary permissions for the original URI should still exist.
+    Assert.deepEqual(SitePermissions.get(browser.currentURI, id, browser), {
+      state: SitePermissions.BLOCK,
+      scope: SitePermissions.SCOPE_TEMPORARY,
+    });
+
+    SitePermissions.remove(uri, id, browser);
+  });
+});
+
--- a/browser/base/content/test/general/permissions.html
+++ b/browser/base/content/test/general/permissions.html
@@ -4,10 +4,11 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <html dir="ltr" xml:lang="en-US" lang="en-US">
   <head>
     <meta charset="utf8">
   </head>
   <body>
 	<!-- This page could eventually request permissions from content
 	     and make sure that chrome responds appropriately -->
+  <button id="geo" onclick="navigator.geolocation.getCurrentPosition(() => {})">Geolocation</button>
   </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/temporary_permissions_subframe.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Temporary Permissions Subframe Test</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+  <iframe id="frame" src="https://example.org/browser/browser/base/content/test/general/permissions.html" />
+</body>
+</html>
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media.js
@@ -114,16 +114,44 @@ var gTests = [
 
     yield promiseMessage(permissionError, () => {
       activateSecondaryAction(kActionDeny);
     });
 
     yield expectObserverCalled("getUserMedia:response:deny");
     yield expectObserverCalled("recording-window-ended");
     yield checkNotSharing();
+
+    // Verify that we set 'Temporarily blocked' permissions.
+    let browser = gBrowser.selectedBrowser;
+    let blockedPerms = document.getElementById("blocked-permissions-container");
+
+    let {state, scope} = SitePermissions.get(null, "camera", browser);
+    Assert.equal(state, SitePermissions.BLOCK);
+    Assert.equal(scope, SitePermissions.SCOPE_TEMPORARY);
+    ok(blockedPerms.querySelector(".blocked-permission-icon.camera-icon[showing=true]"),
+       "the blocked camera icon is shown");
+
+    ({state, scope} = SitePermissions.get(null, "microphone", browser));
+    Assert.equal(state, SitePermissions.BLOCK);
+    Assert.equal(scope, SitePermissions.SCOPE_TEMPORARY);
+    ok(blockedPerms.querySelector(".blocked-permission-icon.microphone-icon[showing=true]"),
+       "the blocked microphone icon is shown");
+
+    info("requesting devices again to check temporarily blocked permissions");
+    promise = promiseMessage(permissionError);
+    yield promiseRequestDevice(true, true);
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    yield expectObserverCalled("getUserMedia:response:deny");
+    yield expectObserverCalled("recording-window-ended");
+    yield checkNotSharing();
+
+    SitePermissions.remove(browser.currentURI, "camera", browser);
+    SitePermissions.remove(browser.currentURI, "microphone", browser);
   }
 },
 
 {
   desc: "getUserMedia audio+video: stop sharing",
   run: function* checkStopSharing() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, true);
@@ -176,17 +204,17 @@ var gTests = [
 },
 
 {
   desc: "getUserMedia prompt: Always/Never Share",
   run: function* checkRememberCheckbox() {
     let elt = id => document.getElementById(id);
 
     function* checkPerm(aRequestAudio, aRequestVideo,
-                       aExpectedAudioPerm, aExpectedVideoPerm, aNever) {
+                        aExpectedAudioPerm, aExpectedVideoPerm, aNever) {
       let promise = promisePopupNotificationShown("webRTC-shareDevices");
       yield promiseRequestDevice(aRequestAudio, aRequestVideo);
       yield promise;
       yield expectObserverCalled("getUserMedia:request");
 
       is(elt("webRTC-selectMicrophone").hidden, !aRequestAudio,
          "microphone selector expected to be " + (aRequestAudio ? "visible" : "hidden"));
 
@@ -275,16 +303,19 @@ var gTests = [
         yield expectObserverCalled("getUserMedia:request");
 
         // Deny the request to cleanup...
         yield promiseMessage(permissionError, () => {
           activateSecondaryAction(kActionDeny);
         });
         yield expectObserverCalled("getUserMedia:response:deny");
         yield expectObserverCalled("recording-window-ended");
+        let browser = gBrowser.selectedBrowser;
+        SitePermissions.remove(null, "camera", browser);
+        SitePermissions.remove(null, "microphone", browser);
       } else {
         let expectedMessage = aExpectStream ? "ok" : permissionError;
         let promise = promiseMessage(expectedMessage);
         yield promiseRequestDevice(aRequestAudio, aRequestVideo);
         yield promise;
 
         if (expectedMessage == "ok") {
           yield expectObserverCalled("getUserMedia:request");
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
@@ -330,16 +330,18 @@ var gTests = [
 
     yield promiseMessage(permissionError, () => {
       PopupNotifications.panel.firstChild.button.click();
     });
 
     yield expectObserverCalled("getUserMedia:response:deny");
     yield expectObserverCalled("recording-window-ended");
     yield checkNotSharing();
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
   }
 },
 
 
 {
   desc: "getUserMedia screen, user clicks \"Don't Allow\"",
   run: function* checkDontShare() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
@@ -350,16 +352,18 @@ var gTests = [
 
     yield promiseMessage(permissionError, () => {
       activateSecondaryAction(kActionDeny);
     });
 
     yield expectObserverCalled("getUserMedia:response:deny");
     yield expectObserverCalled("recording-window-ended");
     yield checkNotSharing();
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
   }
 },
 
 {
   desc: "getUserMedia audio+video+screen: stop sharing",
   run: function* checkStopSharing() {
     if (AppConstants.platform == "macosx") {
       todo(false, "Bug 1323481 - On Mac on treeherder, but not locally, requesting microphone + screen never makes the permission prompt appear, and so causes the test to timeout");
@@ -494,20 +498,20 @@ var gTests = [
 
     yield closeStream();
   }
 },
 
 {
   desc: "Only persistent block is possible for screen sharing",
   run: function* checkPersistentPermissions() {
-    let Perms = Services.perms;
-    let uri = gBrowser.selectedBrowser.documentURI;
-    let devicePerms = Perms.testExactPermission(uri, "screen");
-    is(devicePerms, Perms.UNKNOWN_ACTION,
+    let browser = gBrowser.selectedBrowser;
+    let uri = browser.documentURI;
+    let devicePerms = SitePermissions.get(uri, "screen", browser);
+    is(devicePerms.state, SitePermissions.UNKNOWN,
        "starting without screen persistent permissions");
 
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(false, true, null, "screen");
     yield promise;
     yield expectObserverCalled("getUserMedia:request");
     checkDeviceSelectors(false, false, true);
     document.getElementById("webRTC-selectWindow-menulist")
@@ -528,27 +532,30 @@ var gTests = [
     // Click "Don't Allow" to save a persistent block permission.
     yield promiseMessage(permissionError, () => {
       activateSecondaryAction(kActionDeny);
     });
     yield expectObserverCalled("getUserMedia:response:deny");
     yield expectObserverCalled("recording-window-ended");
     yield checkNotSharing();
 
-    is(Perms.testExactPermission(uri, "screen"), Perms.DENY_ACTION,
+    let permission = SitePermissions.get(uri, "screen", browser);
+    is(permission.state, SitePermissions.BLOCK,
+       "screen sharing is blocked");
+    is(permission.scope, SitePermissions.SCOPE_PERSISTENT,
        "screen sharing is persistently blocked");
 
     // Request screensharing again, expect an immediate failure.
     promise = promiseMessage(permissionError);
     yield promiseRequestDevice(false, true, null, "screen");
     yield promise;
     yield expectObserverCalled("recording-window-ended");
 
     // Now set the permission to allow and expect a prompt.
-    Perms.add(uri, "screen", Perms.ALLOW_ACTION);
+    SitePermissions.set(uri, "screen", SitePermissions.ALLOW);
 
     // Request devices and expect a prompt despite the saved 'Allow' permission.
     promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(false, true, null, "screen");
     yield promise;
     yield expectObserverCalled("getUserMedia:request");
 
     // The 'remember' checkbox shouldn't be checked anymore.
@@ -559,17 +566,17 @@ var gTests = [
     ok(!checkbox.checked, "checkbox is not checked");
 
     // Deny the request to cleanup...
     yield promiseMessage(permissionError, () => {
       activateSecondaryAction(kActionDeny);
     });
     yield expectObserverCalled("getUserMedia:response:deny");
     yield expectObserverCalled("recording-window-ended");
-    Perms.remove(uri, "screen");
+    SitePermissions.remove(uri, "screen", browser);
   }
 }
 
 ];
 
 function test() {
   waitForExplicitFinish();
 
--- a/browser/base/content/test/webrtc/head.js
+++ b/browser/base/content/test/webrtc/head.js
@@ -1,9 +1,10 @@
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource:///modules/SitePermissions.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 
 const PREF_PERMISSION_FAKE = "media.navigator.permission.fake";
 const CONTENT_SCRIPT_HELPER = getRootDirectory(gTestPath) + "get_user_media_content_script.js";
 
 function waitForCondition(condition, nextTest, errorMsg, retryTimes) {
--- a/browser/components/extensions/test/browser/browser_ext_tabs_cookieStoreId.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_cookieStoreId.js
@@ -91,16 +91,17 @@ add_task(function* () {
             browser.test.assertTrue(tab != undefined, "Tab found!");
             testTab(data, tab);
           }
 
           let stores = await browser.cookies.getAllCookieStores();
 
           let store = stores.find(store => store.id === tab.cookieStoreId);
           browser.test.assertTrue(!!store, "We have a store for this tab.");
+          browser.test.assertTrue(store.tabIds.includes(tab.id), "tabIds includes this tab.");
 
           await browser.tabs.remove(tab.id);
 
           browser.test.sendMessage("test-done");
         } catch (e) {
           browser.test.fail("An exception has been thrown");
         }
       }
--- a/browser/components/preferences/in-content/applications.xul
+++ b/browser/components/preferences/in-content/applications.xul
@@ -47,18 +47,19 @@
               name="browser.audioFeeds.handlers.webservice"
               type="string"/>
 
   <preference id="pref.downloads.disable_button.edit_actions"
               name="pref.downloads.disable_button.edit_actions"
               type="bool"/>
 </preferences>
 
-<keyset>
-  <!-- These <key>s have oncommand attributes because of bug 371900 -->
+<keyset data-category="paneApplications">
+  <!-- Ctrl+f/k focus the search box in the Applications pane.
+       These <key>s have oncommand attributes because of bug 371900. -->
   <key key="&focusSearch1.key;" modifiers="accel" id="focusSearch1" oncommand=";"/>
   <key key="&focusSearch2.key;" modifiers="accel" id="focusSearch2" oncommand=";"/>
 </keyset>
 
 <hbox id="header-applications"
       class="header"
       hidden="true"
       data-category="paneApplications">
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -195,21 +195,31 @@ function gotoPref(aCategory) {
   mainContent.scrollTop = 0;
 
   Services.telemetry
           .getHistogramById("FX_PREFERENCES_CATEGORY_OPENED")
           .add(telemetryBucketForCategory(friendlyName));
 }
 
 function search(aQuery, aAttribute) {
-  let elements = document.getElementById("mainPrefPane").children;
+  let mainPrefPane = document.getElementById("mainPrefPane");
+  let elements = mainPrefPane.children;
   for (let element of elements) {
     let attributeValue = element.getAttribute(aAttribute);
     element.hidden = (attributeValue != aQuery);
   }
+
+  let keysets = mainPrefPane.getElementsByTagName("keyset");
+  for (let element of keysets) {
+    let attributeValue = element.getAttribute(aAttribute);
+    if (attributeValue == aQuery)
+      element.removeAttribute("disabled");
+    else
+      element.setAttribute("disabled", true);
+  }
 }
 
 function helpButtonCommand() {
   let pane = history.state;
   let categories = document.getElementById("categories");
   let helpTopic = categories.querySelector(".category[value=" + pane + "]")
                             .getAttribute("helpTopic");
   openHelpLink(helpTopic);
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -56,16 +56,17 @@
 ]>
 
 #ifdef XP_WIN
 #define USE_WIN_TITLE_STYLE
 #endif
 
 <page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
       xmlns:html="http://www.w3.org/1999/xhtml"
+      disablefastfind="true"
 #ifdef USE_WIN_TITLE_STYLE
       title="&prefWindow.titleWin;">
 #else
       title="&prefWindow.title;">
 #endif
 
   <html:link rel="shortcut icon"
               href="chrome://browser/skin/preferences/in-content/favicon.ico"/>
@@ -173,16 +174,23 @@
                     helpTopic="prefs-advanced-general"
                     tooltiptext="&paneAdvanced.title;"
                     align="center">
         <image class="category-icon"/>
         <label class="category-name" flex="1">&paneAdvanced.title;</label>
       </richlistitem>
     </richlistbox>
 
+    <keyset>
+      <!-- Disable the findbar because it doesn't work properly.
+           Remove this keyset once bug 1094240 ("disablefastfind" attribute
+           broken in e10s mode) is fixed. -->
+      <key key="&focusSearch1.key;" modifiers="accel" id="focusSearch1" oncommand=";"/>
+    </keyset>
+
     <vbox class="main-content" flex="1">
       <prefpane id="mainPrefPane">
 #include main.xul
 #include search.xul
 #include privacy.xul
 #include containers.xul
 #include advanced.xul
 #include applications.xul
--- a/browser/components/preferences/in-content/tests/browser_bug731866.js
+++ b/browser/components/preferences/in-content/tests/browser_bug731866.js
@@ -8,19 +8,21 @@ function test() {
   waitForExplicitFinish();
   open_preferences(runTest);
 }
 
 var gElements;
 
 function checkElements(expectedPane) {
   for (let element of gElements) {
-    // preferences elements fail is_element_visible checks because they are never visible.
+    // keyset and preferences elements fail is_element_visible checks because they are never visible.
     // special-case the drmGroup item because its visibility depends on pref + OS version
-    if (element.nodeName == "preferences" || element.id === "drmGroup") {
+    if (element.nodeName == "keyset" ||
+        element.nodeName == "preferences" ||
+        element.id === "drmGroup") {
       continue;
     }
     let attributeValue = element.getAttribute("data-category");
     let suffix = " (id=" + element.id + ")";
     if (attributeValue == "pane" + expectedPane) {
       is_element_visible(element, expectedPane + " elements should be visible" + suffix);
     } else {
       is_element_hidden(element, "Elements not in " + expectedPane + " should be hidden" + suffix);
--- a/browser/components/sessionstore/SessionSaver.jsm
+++ b/browser/components/sessionstore/SessionSaver.jsm
@@ -209,27 +209,30 @@ var SessionSaverInternal = {
           break;
         }
 
         delete state._closedWindows[i]._shouldRestore;
         state.windows.unshift(state._closedWindows.pop());
       }
     }
 
-    // Clear all cookies on clean shutdown according to user preferences
+    // Clear all cookies and storage on clean shutdown according to user preferences
     if (RunState.isClosing) {
       let expireCookies = Services.prefs.getIntPref("network.cookie.lifetimePolicy") ==
                           Services.cookies.QueryInterface(Ci.nsICookieService).ACCEPT_SESSION;
       let sanitizeCookies = Services.prefs.getBoolPref("privacy.sanitize.sanitizeOnShutdown") &&
                             Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies");
       let restart = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
-      // Don't clear cookies when restarting
+      // Don't clear when restarting
       if ((expireCookies || sanitizeCookies) && !restart) {
         for (let window of state.windows) {
           delete window.cookies;
+          for (let tab of window.tabs) {
+            delete tab.storage;
+          }
         }
       }
     }
 
     stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
     return this._writeState(state);
   },
 
--- a/browser/extensions/mortar/host/common/ppapi-runtime.jsm
+++ b/browser/extensions/mortar/host/common/ppapi-runtime.jsm
@@ -1663,16 +1663,19 @@ class PPAPIInstance {
     return this.id;
   }
 
   viewportActionHandler(message) {
     switch(message.type) {
       case 'setFullscreen':
         this.mm.sendAsyncMessage("ppapi.js:setFullscreen", message.fullscreen);
         break;
+      case 'save':
+        this.mm.sendAsyncMessage("ppapipdf.js:save");
+        break;
       case 'viewport':
       case 'rotateClockwise':
       case 'rotateCounterclockwise':
       case 'selectAll':
       case 'getSelectedText':
       case 'getNamedDestination':
       case 'getPasswordComplete':
         let data = PP_Var.fromJSValue(new Dictionary(message), this);
--- a/browser/extensions/mortar/host/pdf/chrome/js/toolbar.js
+++ b/browser/extensions/mortar/host/pdf/chrome/js/toolbar.js
@@ -189,16 +189,20 @@ class Toolbar {
         this._viewport.page++;
         break;
       case 'zoomIn':
         this._zoomIn();
         break;
       case 'zoomOut':
         this._zoomOut();
         break;
+      case 'download':
+      case 'secondaryDownload':
+        this._viewport.save();
+        break;
       case 'pageRotateCw':
         this._viewport.rotateClockwise();
         break;
       case 'pageRotateCcw':
         this._viewport.rotateCounterClockwise();
         break;
       case 'secondaryToolbarToggle':
         this._secondaryToolbar.toggle();
--- a/browser/extensions/mortar/host/pdf/chrome/js/viewport.js
+++ b/browser/extensions/mortar/host/pdf/chrome/js/viewport.js
@@ -414,16 +414,22 @@ class Viewport {
   }
 
   rotateCounterClockwise() {
     this._doAction({
       type: 'rotateCounterclockwise'
     });
   }
 
+  save() {
+    this._doAction({
+      type: 'save'
+    });
+  }
+
   // A handler for delivering messages to runtime.
   registerActionHandler(handler) {
     if (typeof handler === 'function') {
       this._actionHandler = handler;
     }
   }
 
   /***************************/
--- a/browser/extensions/mortar/host/pdf/ppapi-content-sandbox.js
+++ b/browser/extensions/mortar/host/pdf/ppapi-content-sandbox.js
@@ -4,17 +4,24 @@
  * 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/. */
 
 /**
  * This code runs in the sandbox in the content process where the page that
  * loaded the plugin lives. It communicates with the process where the PPAPI
  * implementation lives.
  */
-const { utils: Cu } = Components;
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+                                          "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+                             "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 let mm = pluginElement.frameLoader.messageManager;
 let containerWindow = pluginElement.ownerDocument.defaultView;
 // Prevent the drag event's default action on the element, to avoid dragging of
 // that element while the user selects text.
 pluginElement.addEventListener("dragstart",
                                function(event) { event.preventDefault(); });
 // For synthetic documents only, prevent the select event's default action on
@@ -87,9 +94,82 @@ mm.addMessageListener("ppapi.js:frameLoa
 mm.addMessageListener("ppapi.js:setFullscreen", ({ data }) => {
   if (data) {
     pluginElement.requestFullscreen();
   } else {
     containerWindow.document.exitFullscreen();
   }
 });
 
+mm.addMessageListener("ppapipdf.js:save", () => {
+  let url = containerWindow.document.location;
+  let filename = "document.pdf";
+  let regex = /[^\/#\?]+\.pdf$/i;
+
+  let result = regex.exec(url.hash) ||
+               regex.exec(url.search) ||
+               regex.exec(url.pathname);
+  if (result) {
+    filename = result[0];
+  }
+
+  let originalUri = NetUtil.newURI(url.href);
+  let extHelperAppSvc =
+        Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
+           getService(Ci.nsIExternalHelperAppService);
+
+  let docIsPrivate =
+    PrivateBrowsingUtils.isContentWindowPrivate(containerWindow);
+  let netChannel = NetUtil.newChannel({
+    uri: originalUri,
+    loadUsingSystemPrincipal: true,
+  });
+
+  if ("nsIPrivateBrowsingChannel" in Ci &&
+      netChannel instanceof Ci.nsIPrivateBrowsingChannel) {
+    netChannel.setPrivate(docIsPrivate);
+  }
+  NetUtil.asyncFetch(netChannel, function(aInputStream, aResult) {
+    if (!Components.isSuccessCode(aResult)) {
+      return;
+    }
+    // Create a nsIInputStreamChannel so we can set the url on the channel
+    // so the filename will be correct.
+    let channel = Cc["@mozilla.org/network/input-stream-channel;1"].
+                     createInstance(Ci.nsIInputStreamChannel);
+    channel.QueryInterface(Ci.nsIChannel);
+    channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT;
+    channel.contentDispositionFilename = filename;
+    channel.setURI(originalUri);
+    channel.loadInfo = netChannel.loadInfo;
+    channel.contentStream = aInputStream;
+    if ("nsIPrivateBrowsingChannel" in Ci &&
+        channel instanceof Ci.nsIPrivateBrowsingChannel) {
+      channel.setPrivate(docIsPrivate);
+    }
+
+    let listener = {
+      extListener: null,
+      onStartRequest(aRequest, aContext) {
+        var loadContext = containerWindow
+                            .QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsIWebNavigation)
+                            .QueryInterface(Ci.nsILoadContext);
+        this.extListener = extHelperAppSvc.doContent(
+          "application/pdf", aRequest, loadContext, false);
+        this.extListener.onStartRequest(aRequest, aContext);
+      },
+      onStopRequest(aRequest, aContext, aStatusCode) {
+        if (this.extListener) {
+          this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
+        }
+      },
+      onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount) {
+        this.extListener
+          .onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
+      }
+    };
+
+    channel.asyncOpen2(listener);
+  });
+});
+
 mm.loadFrameScript("resource://ppapi.js/ppapi-instance.js", true);
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -471,28 +471,32 @@ social.error.message=%1$S is unable to c
 social.error.tryAgain.label=Try Again
 social.error.tryAgain.accesskey=T
 social.error.closeSidebar.label=Close This Sidebar
 social.error.closeSidebar.accesskey=C
 
 # LOCALIZATION NOTE: %1$S is the label for the toolbar button, %2$S is the associated badge numbering that the social provider may provide.
 social.aria.toolbarButtonBadgeText=%1$S (%2$S)
 
-# LOCALIZATION NOTE (getUserMedia.shareCamera2.message, getUserMedia.shareMicrophone2.message,
-#                    getUserMedia.shareScreen2.message, getUserMedia.shareCameraAndMicrophone2.message,
-#                    getUserMedia.shareScreenAndMicrophone2.message, getUserMedia.shareCameraAndAudioCapture2.message,
-#                    getUserMedia.shareAudioCapture2.message, getUserMedia.shareScreenAndAudioCapture2.message):
-#  %S is the website origin (e.g. www.mozilla.org)
+# LOCALIZATION NOTE (getUserMedia.shareCamera2.message,
+#                    getUserMedia.shareMicrophone2.message,
+#                    getUserMedia.shareScreen3.message,
+#                    getUserMedia.shareCameraAndMicrophone2.message,
+#                    getUserMedia.shareCameraAndAudioCapture2.message,
+#                    getUserMedia.shareScreenAndMicrophone3.message,
+#                    getUserMedia.shareScreenAndAudioCapture3.message,
+#                    getUserMedia.shareAudioCapture2.message):
+# %S is the website origin (e.g. www.mozilla.org)
 getUserMedia.shareCamera2.message = Will you allow %S to use your camera?
 getUserMedia.shareMicrophone2.message = Will you allow %S to use your microphone?
-getUserMedia.shareScreen2.message = Will you allow %S to see your screen or application window?
+getUserMedia.shareScreen3.message = Will you allow %S to see your screen?
 getUserMedia.shareCameraAndMicrophone2.message = Will you allow %S to use your camera and microphone?
 getUserMedia.shareCameraAndAudioCapture2.message = Will you allow %S to use your camera and listen to this tab’s audio?
-getUserMedia.shareScreenAndMicrophone2.message = Will you allow %S to use your microphone and see your screen or application window?
-getUserMedia.shareScreenAndAudioCapture2.message = Will you allow %S to listen to this tab’s audio and see your screen or application window?
+getUserMedia.shareScreenAndMicrophone3.message = Will you allow %S to use your microphone and see your screen?
+getUserMedia.shareScreenAndAudioCapture3.message = Will you allow %S to listen to this tab’s audio and see your screen?
 getUserMedia.shareAudioCapture2.message = Will you allow %S to listen to this tab’s audio?
 # LOCALIZATION NOTE (getUserMedia.shareScreenWarning.message): NB: inserted via innerHTML, so please don't use <, > or & in this string.
 # %S will be the 'learn more' link
 getUserMedia.shareScreenWarning.message = Only share screens with sites you trust. Sharing can allow deceptive sites to browse as you and steal your private data. %S
 # LOCALIZATION NOTE (getUserMedia.shareFirefoxWarning.message): NB: inserted via innerHTML, so please don't use <, > or & in this string.
 # %1$S is brandShortName (eg. Firefox)
 # %2$S will be the 'learn more' link
 getUserMedia.shareFirefoxWarning.message = Only share %1$S with sites you trust. Sharing can allow deceptive sites to browse as you and steal your private data. %2$S
@@ -513,26 +517,30 @@ getUserMedia.shareEntireScreen.label = E
 # Example: Screen 1, Screen 2,..
 getUserMedia.shareMonitor.label = Screen %S
 # LOCALIZATION NOTE (getUserMedia.shareApplicationWindowCount.label):
 # Semicolon-separated list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # Replacement for #1 is the name of the application.
 # Replacement for #2 is the number of windows currently displayed by the application.
 getUserMedia.shareApplicationWindowCount.label=#1 (#2 window);#1 (#2 windows)
+# LOCALIZATION NOTE (getUserMedia.allow.label,
+#                    getUserMedia.dontAllow.label):
+# These two buttons are the possible answers to the various prompts in the
+# "getUserMedia.share{device}.message" strings.
 getUserMedia.allow.label = Allow
 getUserMedia.allow.accesskey = A
 getUserMedia.dontAllow.label = Don’t Allow
 getUserMedia.dontAllow.accesskey = D
 getUserMedia.remember=Remember this decision
-# LOCALIZATION NOTE (ggetUserMedia.reasonForNoPermanentAllow.screen,
+# LOCALIZATION NOTE (getUserMedia.reasonForNoPermanentAllow.screen2,
 #                    getUserMedia.reasonForNoPermanentAllow.audio,
 #                    getUserMedia.reasonForNoPermanentAllow.insecure):
 # %S is brandShortName
-getUserMedia.reasonForNoPermanentAllow.screen=%S can not allow permanent access to your screen or application without asking which one to share.
+getUserMedia.reasonForNoPermanentAllow.screen2=%S can not allow permanent access to your screen without asking which one to share.
 getUserMedia.reasonForNoPermanentAllow.audio=%S can not allow permanent access to your tab’s audio without asking which tab to share.
 getUserMedia.reasonForNoPermanentAllow.insecure=Your connection to this site is not secure. To protect you, %S will only allow access for this session.
 
 getUserMedia.sharingMenu.label = Tabs sharing devices
 getUserMedia.sharingMenu.accesskey = d
 # LOCALIZATION NOTE (getUserMedia.sharingMenuCamera
 #                    getUserMedia.sharingMenuMicrophone,
 #                    getUserMedia.sharingMenuAudioCapture,
--- a/browser/locales/en-US/chrome/browser/sitePermissions.properties
+++ b/browser/locales/en-US/chrome/browser/sitePermissions.properties
@@ -1,16 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 allow = Allow
 allowForSession = Allow for Session
 allowTemporarily = Allow Temporarily
 block = Block
+blockTemporarily = Block Temporarily
 alwaysAsk = Always Ask
 
 permission.cookie.label = Set Cookies
 permission.desktop-notification2.label = Receive Notifications
 permission.image.label = Load Images
 permission.camera.label = Use the Camera
 permission.microphone.label = Use the Microphone
 permission.screen.label = Share the Screen
--- a/browser/modules/E10SUtils.jsm
+++ b/browser/modules/E10SUtils.jsm
@@ -147,16 +147,23 @@ this.E10SUtils = {
     return remoteType == this.getRemoteTypeForURI(aURI.spec, true, remoteType);
   },
 
   shouldLoadURI(aDocShell, aURI, aReferrer) {
     // Inner frames should always load in the current process
     if (aDocShell.QueryInterface(Ci.nsIDocShellTreeItem).sameTypeParent)
       return true;
 
+    // If we are in a fresh process, and it wouldn't be content visible to
+    // change processes, we want to load into a new process so that we can throw
+    // this one out.
+    if (aDocShell.inFreshProcess && aDocShell.isOnlyToplevelInTabGroup) {
+      return false;
+    }
+
     // If the URI can be loaded in the current process then continue
     return this.shouldLoadURIInThisProcess(aURI);
   },
 
   redirectLoad(aDocShell, aURI, aReferrer, aFreshProcess) {
     // Retarget the load to the correct process
     let messageManager = aDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                   .getInterface(Ci.nsIContentFrameMessageManager);
--- a/browser/modules/FormSubmitObserver.jsm
+++ b/browser/modules/FormSubmitObserver.jsm
@@ -96,50 +96,58 @@ FormSubmitObserver.prototype =
   notifyInvalidSubmit(aFormElement, aInvalidElements) {
     // We are going to handle invalid form submission attempt by focusing the
     // first invalid element and show the corresponding validation message in a
     // panel attached to the element.
     if (!aInvalidElements.length) {
       return;
     }
 
-    // Insure that this is the FormSubmitObserver associated with the
-    // element / window this notification is about.
-    let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
-    if (this._content != element.ownerGlobal.top.document.defaultView) {
-      return;
-    }
+    // Show a validation message on the first focusable element.
+    for (let i = 0; i < aInvalidElements.length; i++) {
+      // Insure that this is the FormSubmitObserver associated with the
+      // element / window this notification is about.
+      let element = aInvalidElements.queryElementAt(i, Ci.nsISupports);
+      if (this._content != element.ownerGlobal.top.document.defaultView) {
+        return;
+      }
 
-    if (!(element instanceof HTMLInputElement ||
-          element instanceof HTMLTextAreaElement ||
-          element instanceof HTMLSelectElement ||
-          element instanceof HTMLButtonElement)) {
-      return;
-    }
+      if (!(element instanceof HTMLInputElement ||
+            element instanceof HTMLTextAreaElement ||
+            element instanceof HTMLSelectElement ||
+            element instanceof HTMLButtonElement)) {
+        continue;
+      }
 
-    // Update validation message before showing notification
-    this._validationMessage = element.validationMessage;
+      if (!Services.focus.elementIsFocusable(element, 0)) {
+        continue;
+      }
 
-    // Don't connect up to the same element more than once.
-    if (this._element == element) {
-      this._showPopup(element);
-      return;
-    }
-    this._element = element;
+      // Update validation message before showing notification
+      this._validationMessage = element.validationMessage;
 
-    element.focus();
+      // Don't connect up to the same element more than once.
+      if (this._element == element) {
+        this._showPopup(element);
+        break;
+      }
+      this._element = element;
 
-    // Watch for input changes which may change the validation message.
-    element.addEventListener("input", this, false);
+      element.focus();
+
+      // Watch for input changes which may change the validation message.
+      element.addEventListener("input", this, false);
 
-    // Watch for focus changes so we can disconnect our listeners and
-    // hide the popup.
-    element.addEventListener("blur", this, false);
+      // Watch for focus changes so we can disconnect our listeners and
+      // hide the popup.
+      element.addEventListener("blur", this, false);
 
-    this._showPopup(element);
+      this._showPopup(element);
+      break;
+    }
   },
 
   /*
    * Internal
    */
 
   /*
    * Handles input changes on the form element we've associated a popup
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -60,16 +60,18 @@ this.EXPORTED_SYMBOLS = [
  * the caller having called into createPermissionPrompt.
  */
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
+  "resource:///modules/SitePermissions.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
   return Services.strings
                  .createBundle('chrome://branding/locale/brand.properties');
 });
 
@@ -209,27 +211,20 @@ this.PermissionPromptPrototype = {
    * via a dropdown menu. The first item in this array will be
    * the default selection. Each action is an Object with the
    * following properties:
    *
    *  label (string):
    *    The label that will be displayed for this choice.
    *  accessKey (string):
    *    The access key character that will be used for this choice.
-   *  action (Ci.nsIPermissionManager action, optional)
-   *    The nsIPermissionManager action that will be associated with
-   *    this choice. For example, Ci.nsIPermissionManager.DENY_ACTION.
+   *  action (SitePermissions state)
+   *    The action that will be associated with this choice.
+   *    This should be either SitePermissions.ALLOW or SitePermissions.BLOCK.
    *
-   *    If omitted, the nsIPermissionManager will not be written to
-   *    when this choice is chosen.
-   *  expireType (Ci.nsIPermissionManager expiration policy, optional)
-   *    The nsIPermissionManager expiration policy that will be associated
-   *    with this choice. For example, Ci.nsIPermissionManager.EXPIRE_SESSION.
-   *
-   *    If action is not set, expireType will be ignored.
    *  callback (function, optional)
    *    A callback function that will fire if the user makes this choice, with
    *    a single parameter, state. State is an Object that contains the property
    *    checkboxChecked, which identifies whether the checkbox to remember this
    *    decision was checked.
    */
   get promptActions() {
     return [];
@@ -266,62 +261,68 @@ this.PermissionPromptPrototype = {
     if (!(requestingURI instanceof Ci.nsIStandardURL)) {
       return;
     }
 
     if (this.permissionKey) {
       // If we're reading and setting permissions, then we need
       // to check to see if we already have a permission setting
       // for this particular principal.
-      let result =
-        Services.perms.testExactPermissionFromPrincipal(this.principal,
-                                                        this.permissionKey);
+      let {state} = SitePermissions.get(requestingURI,
+                                        this.permissionKey,
+                                        this.browser);
 
-      if (result == Ci.nsIPermissionManager.DENY_ACTION) {
+      if (state == SitePermissions.BLOCK) {
         this.cancel();
         return;
       }
 
-      if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
+      if (state == SitePermissions.ALLOW) {
         this.allow();
         return;
       }
     }
 
     // Transform the PermissionPrompt actions into PopupNotification actions.
     let popupNotificationActions = [];
     for (let promptAction of this.promptActions) {
-      // Don't offer action in PB mode if the action remembers permission
-      // for more than a session.
-      if (PrivateBrowsingUtils.isWindowPrivate(chromeWin) &&
-          promptAction.expireType != Ci.nsIPermissionManager.EXPIRE_SESSION &&
-          promptAction.action) {
-        continue;
-      }
-
       let action = {
         label: promptAction.label,
         accessKey: promptAction.accessKey,
         callback: state => {
           if (promptAction.callback) {
             promptAction.callback();
           }
 
           if (this.permissionKey) {
-            // Remember permissions.
-            if (state && state.checkboxChecked && promptAction.action) {
-              Services.perms.addFromPrincipal(this.principal,
-                                              this.permissionKey,
-                                              promptAction.action,
-                                              promptAction.expireType);
+
+            // Permanently store permission.
+            if (state && state.checkboxChecked) {
+              let scope = SitePermissions.SCOPE_PERSISTENT;
+              // Only remember permission for session if in PB mode.
+              if (PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
+                scope = SitePermissions.SCOPE_SESSION;
+              }
+              SitePermissions.set(this.principal.URI,
+                                  this.permissionKey,
+                                  promptAction.action,
+                                  scope);
+            } else if (promptAction.action == SitePermissions.BLOCK) {
+              // Temporarily store BLOCK permissions only.
+              // SitePermissions does not consider subframes when storing temporary
+              // permissions on a tab, thus storing ALLOW could be exploited.
+              SitePermissions.set(this.principal.URI,
+                                  this.permissionKey,
+                                  promptAction.action,
+                                  SitePermissions.SCOPE_TEMPORARY,
+                                  this.browser);
             }
 
-            // Grant permission if action is null or ALLOW_ACTION.
-            if (!promptAction.action ||
-                promptAction.action == Ci.nsIPermissionManager.ALLOW_ACTION) {
+            // Grant permission if action is ALLOW.
+            if (promptAction.action == SitePermissions.ALLOW) {
               this.allow();
             } else {
               this.cancel();
             }
           }
         },
       };
       if (promptAction.dismiss) {
@@ -474,33 +475,29 @@ GeolocationPermissionPrompt.prototype = 
       Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_NEVER_SHARE;
 
     let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
 
     return [{
       label: gBrowserBundle.GetStringFromName("geolocation.allowLocation"),
       accessKey:
         gBrowserBundle.GetStringFromName("geolocation.allowLocation.accesskey"),
-      action: null,
-      expireType: null,
+      action: SitePermissions.ALLOW,
       callback(state) {
         if (state && state.checkboxChecked) {
           secHistogram.add(ALWAYS_SHARE);
         } else {
           secHistogram.add(SHARE_LOCATION);
         }
       },
     }, {
       label: gBrowserBundle.GetStringFromName("geolocation.dontAllowLocation"),
       accessKey:
         gBrowserBundle.GetStringFromName("geolocation.dontAllowLocation.accesskey"),
-      action: Ci.nsIPermissionManager.DENY_ACTION,
-      expireType: PrivateBrowsingUtils.isWindowPrivate(this.browser.ownerGlobal) ?
-                  Ci.nsIPermissionManager.EXPIRE_SESSION :
-                  null,
+      action: SitePermissions.BLOCK,
       callback(state) {
         if (state && state.checkboxChecked) {
           secHistogram.add(NEVER_SHARE);
         }
       },
     }];
   },
 
@@ -574,28 +571,22 @@ DesktopNotificationPermissionPrompt.prot
   },
 
   get promptActions() {
     return [
       {
         label: gBrowserBundle.GetStringFromName("webNotifications.allow"),
         accessKey:
           gBrowserBundle.GetStringFromName("webNotifications.allow.accesskey"),
-        action: Ci.nsIPermissionManager.ALLOW_ACTION,
-        expireType: PrivateBrowsingUtils.isBrowserPrivate(this.browser) ?
-                    Ci.nsIPermissionManager.EXPIRE_SESSION :
-                    null,
+        action: SitePermissions.ALLOW,
       },
       {
         label: gBrowserBundle.GetStringFromName("webNotifications.dontAllow"),
         accessKey:
           gBrowserBundle.GetStringFromName("webNotifications.dontAllow.accesskey"),
-        action: Ci.nsIPermissionManager.DENY_ACTION,
-        expireType: PrivateBrowsingUtils.isBrowserPrivate(this.browser) ?
-                    Ci.nsIPermissionManager.EXPIRE_SESSION :
-                    null,
+        action: SitePermissions.BLOCK,
       },
     ];
   },
 };
 
 PermissionUI.DesktopNotificationPermissionPrompt =
   DesktopNotificationPermissionPrompt;
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -1,194 +1,517 @@
 /* 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/. */
 
 this.EXPORTED_SYMBOLS = [ "SitePermissions" ];
 
 Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var gStringBundle =
   Services.strings.createBundle("chrome://browser/locale/sitePermissions.properties");
 
+/**
+ * A helper module to manage temporarily blocked permissions.
+ *
+ * Permissions are keyed by browser, so methods take a Browser
+ * element to identify the corresponding permission set.
+ *
+ * This uses a WeakMap to key browsers, so that entries are
+ * automatically cleared once the browser stops existing
+ * (once there are no other references to the browser object);
+ */
+const TemporaryBlockedPermissions = {
+  // This is a three level deep map with the following structure:
+  //
+  // Browser => {
+  //   <prePath>: {
+  //     <permissionID>: {Number} <timeStamp>
+  //   }
+  // }
+  //
+  // Only the top level browser elements are stored via WeakMap. The WeakMap
+  // value is an object with URI prePaths as keys. The keys of that object
+  // are ids that identify permissions that were set for the specific URI.
+  // The final value is an object containing the timestamp of when the permission
+  // was set (in order to invalidate after a certain amount of time has passed).
+  _stateByBrowser: new WeakMap(),
+
+  // Private helper method that bundles some shared behavior for
+  // get() and getAll(), e.g. deleting permissions when they have expired.
+  _get(entry, prePath, id, timeStamp) {
+    if (timeStamp == null) {
+      delete entry[prePath][id];
+      return null;
+    }
+    if (timeStamp + SitePermissions.temporaryPermissionExpireTime < Date.now()) {
+      delete entry[prePath][id];
+      return null;
+    }
+    return {id, state: SitePermissions.BLOCK, scope: SitePermissions.SCOPE_TEMPORARY};
+  },
+
+  // Sets a new permission for the specified browser.
+  set(browser, id) {
+    if (!browser) {
+      return;
+    }
+    if (!this._stateByBrowser.has(browser)) {
+      this._stateByBrowser.set(browser, {});
+    }
+    let entry = this._stateByBrowser.get(browser);
+    let prePath = browser.currentURI.prePath;
+    if (!entry[prePath]) {
+      entry[prePath] = {};
+    }
+    entry[prePath][id] = Date.now();
+  },
+
+  // Removes a permission with the specified id for the specified browser.
+  remove(browser, id) {
+    if (!browser) {
+      return;
+    }
+    let entry = this._stateByBrowser.get(browser);
+    let prePath = browser.currentURI.prePath;
+    if (entry && entry[prePath]) {
+      delete entry[prePath][id];
+    }
+  },
+
+  // Gets a permission with the specified id for the specified browser.
+  get(browser, id) {
+    if (!browser || !browser.currentURI) {
+      return null;
+    }
+    let entry = this._stateByBrowser.get(browser);
+    let prePath = browser.currentURI.prePath;
+    if (entry && entry[prePath]) {
+      let permission = entry[prePath][id];
+      return this._get(entry, prePath, id, permission);
+    }
+    return null;
+  },
+
+  // Gets all permissions for the specified browser.
+  // Note that only permissions that apply to the current URI
+  // of the passed browser element will be returned.
+  getAll(browser) {
+    let permissions = [];
+    let entry = this._stateByBrowser.get(browser);
+    let prePath = browser.currentURI.prePath;
+    if (entry && entry[prePath]) {
+      let timeStamps = entry[prePath];
+      for (let id of Object.keys(timeStamps)) {
+        let permission = this._get(entry, prePath, id, timeStamps[id]);
+        // _get() returns null when the permission has expired.
+        if (permission) {
+          permissions.push(permission);
+        }
+      }
+    }
+    return permissions;
+  },
+
+  // Clears all permissions for the specified browser.
+  // Unlike other methods, this does NOT clear only for
+  // the currentURI but the whole browser state.
+  clear(browser) {
+    this._stateByBrowser.delete(browser);
+  },
+
+  // Copies the temporary permission state of one browser
+  // into a new entry for the other browser.
+  copy(browser, newBrowser) {
+    let entry = this._stateByBrowser.get(browser);
+    if (entry) {
+      this._stateByBrowser.set(newBrowser, entry);
+    }
+  },
+};
+
 this.SitePermissions = {
-
+  // Permission states.
   UNKNOWN: Services.perms.UNKNOWN_ACTION,
   ALLOW: Services.perms.ALLOW_ACTION,
   BLOCK: Services.perms.DENY_ACTION,
-  SESSION: Components.interfaces.nsICookiePermission.ACCESS_SESSION,
+  ALLOW_COOKIES_FOR_SESSION: Components.interfaces.nsICookiePermission.ACCESS_SESSION,
 
-  /* Returns all custom permissions for a given URI, the return
-   * type is a list of objects with the keys:
-   * - id: the permissionId of the permission
-   * - state: a constant representing the current permission state
-   *   (e.g. SitePermissions.ALLOW)
+  // Permission scopes.
+  SCOPE_REQUEST: "{SitePermissions.SCOPE_REQUEST}",
+  SCOPE_TEMPORARY: "{SitePermissions.SCOPE_TEMPORARY}",
+  SCOPE_SESSION: "{SitePermissions.SCOPE_SESSION}",
+  SCOPE_PERSISTENT: "{SitePermissions.SCOPE_PERSISTENT}",
+
+  /**
+   * Gets all custom permissions for a given URI.
+   * Install addon permission is excluded, check bug 1303108.
    *
-   * To receive a more detailed, albeit less performant listing see
-   * SitePermissions.getPermissionDetailsByURI().
-   *
-   * install addon permission is excluded, check bug 1303108
+   * @return {Array} a list of objects with the keys:
+   *          - id: the permissionId of the permission
+   *          - scope: the scope of the permission (e.g. SitePermissions.SCOPE_TEMPORARY)
+   *          - state: a constant representing the current permission state
+   *            (e.g. SitePermissions.ALLOW)
    */
-  getAllByURI(aURI) {
+  getAllByURI(uri) {
     let result = [];
-    if (!this.isSupportedURI(aURI)) {
+    if (!this.isSupportedURI(uri)) {
       return result;
     }
 
-    let permissions = Services.perms.getAllForURI(aURI);
+    let permissions = Services.perms.getAllForURI(uri);
     while (permissions.hasMoreElements()) {
       let permission = permissions.getNext();
 
       // filter out unknown permissions
       if (gPermissionObject[permission.type]) {
         // XXX Bug 1303108 - Control Center should only show non-default permissions
         if (permission.type == "install") {
           continue;
         }
+        let scope = this.SCOPE_PERSISTENT;
+        if (permission.expireType == Services.perms.EXPIRE_SESSION) {
+          scope = this.SCOPE_SESSION;
+        }
         result.push({
           id: permission.type,
+          scope,
           state: permission.capability,
         });
       }
     }
 
     return result;
   },
 
-  /* Returns an object representing the aId permission. It contains the
-   * following keys:
-   * - id: the permissionID of the permission
-   * - label: the localized label
-   * - state: a constant representing the aState permission state
-   *   (e.g. SitePermissions.ALLOW), or the default if aState is omitted
-   * - availableStates: an array of all available states for that permission,
-   *   represented as objects with the keys:
-   *   - id: the state constant
-   *   - label: the translated label of that state
+  /**
+   * Returns detailed information on the specified permission.
+   *
+   * @param {String} id
+   *        The permissionID of the permission.
+   * @param {SitePermissions scope} scope
+   *        The current scope of the permission.
+   * @param {SitePermissions state} state (optional)
+   *        The current state of the permission.
+   *        Will default to the default state if omitted.
+   *
+   * @return {Object} an object with the keys:
+   *           - id: the permissionID of the permission
+   *           - label: the localized label
+   *           - state: the passed in state argument
+   *           - scope: the passed in scope argument
+   *           - availableStates: an array of all available states for that permission,
+   *             represented as objects with the keys:
+   *             - id: the state constant
+   *             - label: the translated label of that state
    */
-  getPermissionItem(aId, aState) {
-    let availableStates = this.getAvailableStates(aId).map(state => {
-      return { id: state, label: this.getStateLabel(aId, state) };
+  getPermissionDetails(id, scope, state = this.getDefault(id)) {
+    let availableStates = this.getAvailableStates(id).map(val => {
+      return { id: val, label: this.getStateLabel(val) };
     });
-    if (aState == undefined)
-      aState = this.getDefault(aId);
-    return {id: aId, label: this.getPermissionLabel(aId),
-            state: aState, availableStates};
+    return {id, label: this.getPermissionLabel(id), state, scope, availableStates};
   },
 
-  /* Returns a list of objects representing all permissions that are currently
-   * set for the given URI. See getPermissionItem for the content of each object.
+  /**
+   * Returns all custom permissions for a given browser.
+   *
+   * To receive a more detailed, albeit less performant listing see
+   * SitePermissions.getAllPermissionDetailsForBrowser().
+   *
+   * @param {Browser} browser
+   *        The browser to fetch permission for.
+   *
+   * @return {Array} a list of objects with the keys:
+   *         - id: the permissionId of the permission
+   *         - state: a constant representing the current permission state
+   *           (e.g. SitePermissions.ALLOW)
+   *         - scope: a constant representing how long the permission will
+   *           be kept.
    */
-  getPermissionDetailsByURI(aURI) {
-    let permissions = [];
-    for (let {state, id} of this.getAllByURI(aURI)) {
-      permissions.push(this.getPermissionItem(id, state));
+  getAllForBrowser(browser) {
+    let permissions = {};
+
+    for (let permission of TemporaryBlockedPermissions.getAll(browser)) {
+      permission.scope = this.SCOPE_TEMPORARY;
+      permissions[permission.id] = permission;
+    }
+
+    for (let permission of this.getAllByURI(browser.currentURI)) {
+      permissions[permission.id] = permission;
     }
 
-    return permissions;
+    return Object.values(permissions);
   },
 
-  /* Checks whether a UI for managing permissions should be exposed for a given
+  /**
+   * Returns a list of objects with detailed information on all permissions
+   * that are currently set for the given browser.
+   *
+   * @param {Browser} browser
+   *        The browser to fetch permission for.
+   *
+   * @return {Array} a list of objects. See getPermissionDetails for the content of each object.
+   */
+  getAllPermissionDetailsForBrowser(browser) {
+    return this.getAllForBrowser(browser).map(({id, scope, state}) =>
+      this.getPermissionDetails(id, scope, state));
+  },
+
+  /**
+   * Checks whether a UI for managing permissions should be exposed for a given
    * URI. This excludes file URIs, for instance, as they don't have a host,
    * even though nsIPermissionManager can still handle them.
+   *
+   * @param {nsIURI} uri
+   *        The URI to check.
+   *
+   * @return {boolean} if the URI is supported.
    */
-  isSupportedURI(aURI) {
-    return aURI.schemeIs("http") || aURI.schemeIs("https");
+  isSupportedURI(uri) {
+    return uri && (uri.schemeIs("http") || uri.schemeIs("https"));
   },
 
-  /* Returns an array of all permission IDs.
+  /**
+   * Gets an array of all permission IDs.
+   *
+   * @return {Array<String>} an array of all permission IDs.
    */
   listPermissions() {
     return Object.keys(gPermissionObject);
   },
 
-  /* Returns an array of permission states to be exposed to the user for a
+  /**
+   * Returns an array of permission states to be exposed to the user for a
    * permission with the given ID.
+   *
+   * @param {string} permissionID
+   *        The ID to get permission states for.
+   *
+   * @return {Array<SitePermissions state>} an array of all permission states.
    */
-  getAvailableStates(aPermissionID) {
-    if (aPermissionID in gPermissionObject &&
-        gPermissionObject[aPermissionID].states)
-      return gPermissionObject[aPermissionID].states;
+  getAvailableStates(permissionID) {
+    if (permissionID in gPermissionObject &&
+        gPermissionObject[permissionID].states)
+      return gPermissionObject[permissionID].states;
 
-    if (this.getDefault(aPermissionID) == this.UNKNOWN)
+    if (this.getDefault(permissionID) == this.UNKNOWN)
       return [ SitePermissions.UNKNOWN, SitePermissions.ALLOW, SitePermissions.BLOCK ];
 
     return [ SitePermissions.ALLOW, SitePermissions.BLOCK ];
   },
 
-  /* Returns the default state of a particular permission.
+  /**
+   * Returns the default state of a particular permission.
+   *
+   * @param {string} permissionID
+   *        The ID to get the default for.
+   *
+   * @return {SitePermissions.state} the default state.
    */
-  getDefault(aPermissionID) {
-    if (aPermissionID in gPermissionObject &&
-        gPermissionObject[aPermissionID].getDefault)
-      return gPermissionObject[aPermissionID].getDefault();
+  getDefault(permissionID) {
+    if (permissionID in gPermissionObject &&
+        gPermissionObject[permissionID].getDefault)
+      return gPermissionObject[permissionID].getDefault();
 
     return this.UNKNOWN;
   },
 
-  /* Returns the state of a particular permission for a given URI.
+  /**
+   * Returns the state and scope of a particular permission for a given URI.
+   *
+   * @param {nsIURI} uri
+   *        The URI to check.
+   * @param {String} permissionID
+   *        The id of the permission.
+   * @param {Browser} browser (optional)
+   *        The browser object to check for temporary permissions.
+   *
+   * @return {Object} an object with the keys:
+   *           - state: The current state of the permission
+   *             (e.g. SitePermissions.ALLOW)
+   *           - scope: The scope of the permission
+   *             (e.g. SitePermissions.SCOPE_PERSISTENT)
    */
-  get(aURI, aPermissionID) {
-    if (!this.isSupportedURI(aURI))
-      return this.UNKNOWN;
+  get(uri, permissionID, browser) {
+    let result = { state: this.UNKNOWN, scope: this.SCOPE_PERSISTENT };
+    if (this.isSupportedURI(uri)) {
+      let permission = null;
+      if (permissionID in gPermissionObject &&
+        gPermissionObject[permissionID].exactHostMatch) {
+        permission = Services.perms.getPermissionObjectForURI(uri, permissionID, true);
+      } else {
+        permission = Services.perms.getPermissionObjectForURI(uri, permissionID, false);
+      }
 
-    let state;
-    if (aPermissionID in gPermissionObject &&
-        gPermissionObject[aPermissionID].exactHostMatch)
-      state = Services.perms.testExactPermission(aURI, aPermissionID);
-    else
-      state = Services.perms.testPermission(aURI, aPermissionID);
-    return state;
+      if (permission) {
+        result.state = permission.capability;
+        if (permission.expireType == Services.perms.EXPIRE_SESSION) {
+          result.scope = this.SCOPE_SESSION;
+        }
+      }
+    }
+
+    if (!result.state) {
+      // If there's no persistent permission saved, check if we have something
+      // set temporarily.
+      let value = TemporaryBlockedPermissions.get(browser, permissionID);
+
+      if (value) {
+        result.state = value.state;
+        result.scope = this.SCOPE_TEMPORARY;
+      }
+    }
+
+    return result;
   },
 
-  /* Sets the state of a particular permission for a given URI.
+  /**
+   * Sets the state of a particular permission for a given URI or browser.
+   *
+   * @param {nsIURI} uri
+   *        The URI to set the permission for.
+   *        Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
+   * @param {String} permissionID
+   *        The id of the permission.
+   * @param {SitePermissions state} state
+   *        The state of the permission.
+   * @param {SitePermissions scope} scope (optional)
+   *        The scope of the permission. Defaults to SCOPE_PERSISTENT.
+   * @param {Browser} browser (optional)
+   *        The browser object to set temporary permissions on.
+   *        This needs to be provided if the scope is SCOPE_TEMPORARY!
    */
-  set(aURI, aPermissionID, aState) {
-    if (!this.isSupportedURI(aURI))
-      return;
-
-    if (aState == this.UNKNOWN) {
-      this.remove(aURI, aPermissionID);
+  set(uri, permissionID, state, scope = this.SCOPE_PERSISTENT, browser = null) {
+    if (state == this.UNKNOWN) {
+      this.remove(uri, permissionID, browser);
       return;
     }
 
-    Services.perms.add(aURI, aPermissionID, aState);
+    if (state == this.ALLOW_COOKIES_FOR_SESSION && permissionID != "cookie") {
+      throw "ALLOW_COOKIES_FOR_SESSION can only be set on the cookie permission";
+    }
+
+    // Save temporary permissions.
+    if (scope == this.SCOPE_TEMPORARY) {
+      // We do not support setting temp ALLOW for security reasons.
+      // In its current state, this permission could be exploited by subframes
+      // on the same page. This is because for BLOCK we ignore the request
+      // URI and only consider the current browser URI, to avoid notification spamming.
+      //
+      // If you ever consider removing this line, you likely want to implement
+      // a more fine-grained TemporaryBlockedPermissions that temporarily blocks for the
+      // entire browser, but temporarily allows only for specific frames.
+      if (state != this.BLOCK) {
+        throw "'Block' is the only permission we can save temporarily on a browser";
+      }
+
+      if (!browser) {
+        throw "TEMPORARY scoped permissions require a browser object";
+      }
+
+      TemporaryBlockedPermissions.set(browser, permissionID);
+
+      browser.dispatchEvent(new browser.ownerGlobal
+                                       .CustomEvent("PermissionStateChange"));
+    } else if (this.isSupportedURI(uri)) {
+      let perms_scope = Services.perms.EXPIRE_NEVER;
+      if (scope == this.SCOPE_SESSION) {
+        perms_scope = Services.perms.EXPIRE_SESSION;
+      }
+
+      Services.perms.add(uri, permissionID, state, perms_scope);
+    }
   },
 
-  /* Removes the saved state of a particular permission for a given URI.
+  /**
+   * Removes the saved state of a particular permission for a given URI and/or browser.
+   *
+   * @param {nsIURI} uri
+   *        The URI to remove the permission for.
+   * @param {String} permissionID
+   *        The id of the permission.
+   * @param {Browser} browser (optional)
+   *        The browser object to remove temporary permissions on.
    */
-  remove(aURI, aPermissionID) {
-    if (!this.isSupportedURI(aURI))
-      return;
+  remove(uri, permissionID, browser) {
+    if (this.isSupportedURI(uri))
+      Services.perms.remove(uri, permissionID);
 
-    Services.perms.remove(aURI, aPermissionID);
+    // TemporaryBlockedPermissions.get() deletes expired permissions automatically,
+    if (TemporaryBlockedPermissions.get(browser, permissionID)) {
+      // If it exists but has not expired, remove it explicitly.
+      TemporaryBlockedPermissions.remove(browser, permissionID);
+      // Send a PermissionStateChange event only if the permission hasn't expired.
+      browser.dispatchEvent(new browser.ownerGlobal
+                                       .CustomEvent("PermissionStateChange"));
+    }
   },
 
-  /* Returns the localized label for the permission with the given ID, to be
-   * used in a UI for managing permissions.
+  /**
+   * Clears all permissions that were temporarily saved.
+   *
+   * @param {Browser} browser
+   *        The browser object to clear.
    */
-  getPermissionLabel(aPermissionID) {
-    let labelID = gPermissionObject[aPermissionID].labelID || aPermissionID;
+  clearTemporaryPermissions(browser) {
+    TemporaryBlockedPermissions.clear(browser);
+  },
+
+  /**
+   * Copy all permissions that were temporarily saved on one
+   * browser object to a new browser.
+   *
+   * @param {Browser} browser
+   *        The browser object to copy from.
+   * @param {Browser} newBrowser
+   *        The browser object to copy to.
+   */
+  copyTemporaryPermissions(browser, newBrowser) {
+    TemporaryBlockedPermissions.copy(browser, newBrowser);
+  },
+
+  /**
+   * Returns the localized label for the permission with the given ID, to be
+   * used in a UI for managing permissions.
+   *
+   * @param {string} permissionID
+   *        The permission to get the label for.
+   *
+   * @return {String} the localized label.
+   */
+  getPermissionLabel(permissionID) {
+    let labelID = gPermissionObject[permissionID].labelID || permissionID;
     return gStringBundle.GetStringFromName("permission." + labelID + ".label");
   },
 
-  /* Returns the localized label for the given permission state, to be used in
+  /**
+   * Returns the localized label for the given permission state, to be used in
    * a UI for managing permissions.
+   *
+   * @param {SitePermissions state} state
+   *        The state to get the label for.
+   * @param {SitePermissions scope} scope (optional)
+   *        The scope to get the label for.
+   *
+   * @return {String} the localized label.
    */
-  getStateLabel(aPermissionID, aState, aInUse = false) {
-    switch (aState) {
+  getStateLabel(state, scope = null) {
+    switch (state) {
       case this.UNKNOWN:
-        if (aInUse)
-          return gStringBundle.GetStringFromName("allowTemporarily");
         return gStringBundle.GetStringFromName("alwaysAsk");
       case this.ALLOW:
+        if (scope && scope != this.SCOPE_PERSISTENT)
+          return gStringBundle.GetStringFromName("allowTemporarily");
         return gStringBundle.GetStringFromName("allow");
-      case this.SESSION:
+      case this.ALLOW_COOKIES_FOR_SESSION:
         return gStringBundle.GetStringFromName("allowForSession");
       case this.BLOCK:
+        if (scope && scope != this.SCOPE_PERSISTENT)
+          return gStringBundle.GetStringFromName("blockTemporarily");
         return gStringBundle.GetStringFromName("block");
       default:
         return null;
     }
   }
 };
 
 var gPermissionObject = {
@@ -217,23 +540,23 @@ var gPermissionObject = {
   "image": {
     getDefault() {
       return Services.prefs.getIntPref("permissions.default.image") == 2 ?
                SitePermissions.BLOCK : SitePermissions.ALLOW;
     }
   },
 
   "cookie": {
-    states: [ SitePermissions.ALLOW, SitePermissions.SESSION, SitePermissions.BLOCK ],
+    states: [ SitePermissions.ALLOW, SitePermissions.ALLOW_COOKIES_FOR_SESSION, SitePermissions.BLOCK ],
     getDefault() {
       if (Services.prefs.getIntPref("network.cookie.cookieBehavior") == 2)
         return SitePermissions.BLOCK;
 
       if (Services.prefs.getIntPref("network.cookie.lifetimePolicy") == 2)
-        return SitePermissions.SESSION;
+        return SitePermissions.ALLOW_COOKIES_FOR_SESSION;
 
       return SitePermissions.ALLOW;
     }
   },
 
   "desktop-notification": {
     exactHostMatch: true,
     labelID: "desktop-notification2",
@@ -261,9 +584,11 @@ var gPermissionObject = {
 
   "geo": {
     exactHostMatch: true
   },
 
   "indexedDB": {}
 };
 
-const kPermissionIDs = Object.keys(gPermissionObject);
+XPCOMUtils.defineLazyPreferenceGetter(SitePermissions, "temporaryPermissionExpireTime",
+                                      "privacy.temporary_permission_expire_time_ms", 3600 * 1000);
+
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -19,16 +19,20 @@ support-files =
 [browser_NetworkPrioritizer.js]
 [browser_PermissionUI.js]
 [browser_ProcessHangNotifications.js]
 skip-if = !e10s
 [browser_SelfSupportBackend.js]
 support-files =
   ../../components/uitour/test/uitour.html
   ../../components/uitour/UITour-lib.js
+[browser_SitePermissions.js]
+[browser_SitePermissions_combinations.js]
+[browser_SitePermissions_expiry.js]
+[browser_SitePermissions_tab_urls.js]
 [browser_taskbar_preview.js]
 skip-if = os != "win"
 [browser_UnsubmittedCrashHandler.js]
 run-if = crashreporter
 [browser_UsageTelemetry.js]
 [browser_UsageTelemetry_private_and_restore.js]
 [browser_UsageTelemetry_urlbar.js]
 support-files =
@@ -36,8 +40,9 @@ support-files =
   usageTelemetrySearchSuggestions.xml
 [browser_UsageTelemetry_searchbar.js]
 support-files =
   usageTelemetrySearchSuggestions.sjs
   usageTelemetrySearchSuggestions.xml
 [browser_UsageTelemetry_content.js]
 [browser_UsageTelemetry_content_aboutHome.js]
 [browser_urlBar_zoom.js]
+[browser_bug1319078.js]
\ No newline at end of file
--- a/browser/modules/test/browser_PermissionUI.js
+++ b/browser/modules/test/browser_PermissionUI.js
@@ -3,16 +3,17 @@
  * permission prompts to the user. It also tests to ensure that
  * add-ons can introduce their own permission prompts.
  */
 
 "use strict";
 
 Cu.import("resource://gre/modules/Integration.jsm", this);
 Cu.import("resource:///modules/PermissionUI.jsm", this);
+Cu.import("resource:///modules/SitePermissions.jsm", this);
 
 /**
  * Given a <xul:browser> at some non-internal web page,
  * return something that resembles an nsIContentPermissionRequest,
  * using the browsers currently loaded document to get a principal.
  *
  * @param browser (<xul:browser>)
  *        The browser that we'll create a nsIContentPermissionRequest
@@ -216,38 +217,36 @@ add_task(function* test_with_permission_
     const kTestNotificationID = "test-notification";
     const kTestMessage = "Test message";
     const kTestPermissionKey = "test-permission-key";
 
     let allowed = false;
     let mainAction = {
       label: "Allow",
       accessKey: "M",
-      action: Ci.nsIPermissionManager.ALLOW_ACTION,
-      expiryType: Ci.nsIPermissionManager.EXPIRE_SESSION,
+      action: SitePermissions.ALLOW,
       callback() {
         allowed = true;
       }
     };
 
     let denied = false;
     let secondaryAction = {
       label: "Deny",
       accessKey: "D",
-      action: Ci.nsIPermissionManager.DENY_ACTION,
-      expiryType: Ci.nsIPermissionManager.EXPIRE_SESSION,
+      action: SitePermissions.BLOCK,
       callback() {
         denied = true;
       }
     };
 
     let mockRequest = makeMockPermissionRequest(browser);
     let principal = mockRequest.principal;
     registerCleanupFunction(function() {
-      Services.perms.removeFromPrincipal(principal, kTestPermissionKey);
+      SitePermissions.remove(principal.URI, kTestPermissionKey);
     });
 
     let TestPrompt = {
       __proto__: PermissionUI.PermissionPromptForRequestPrototype,
       request: mockRequest,
       notificationID: kTestNotificationID,
       permissionKey: kTestPermissionKey,
       message: kTestMessage,
@@ -264,54 +263,90 @@ add_task(function* test_with_permission_
     let shownPromise =
       BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
     TestPrompt.prompt();
     yield shownPromise;
     let notification =
       PopupNotifications.getNotification(kTestNotificationID, browser);
     Assert.ok(notification, "Should have gotten the notification");
 
-    let curPerm =
-      Services.perms.testPermissionFromPrincipal(principal,
-                                                 kTestPermissionKey);
-    Assert.equal(curPerm, Ci.nsIPermissionManager.UNKNOWN_ACTION,
+    let curPerm = SitePermissions.get(principal.URI, kTestPermissionKey, browser);
+    Assert.equal(curPerm.state, SitePermissions.UNKNOWN,
                  "Should be no permission set to begin with.");
 
-    // First test denying the permission request.
+    // First test denying the permission request without the checkbox checked.
+    let popupNotification = getPopupNotificationNode();
+    popupNotification.checkbox.checked = false;
+
     Assert.equal(notification.secondaryActions.length, 1,
                  "There should only be 1 secondary action");
     yield clickSecondaryAction();
-    curPerm = Services.perms.testPermissionFromPrincipal(principal,
-                                                         kTestPermissionKey);
-    Assert.equal(curPerm, Ci.nsIPermissionManager.DENY_ACTION,
-                 "Should have denied the action");
+    curPerm = SitePermissions.get(principal.URI, kTestPermissionKey, browser);
+    Assert.deepEqual(curPerm, {
+                       state: SitePermissions.BLOCK,
+                       scope: SitePermissions.SCOPE_TEMPORARY,
+                     }, "Should have denied the action temporarily");
+    // Try getting the permission without passing the browser object (should fail).
+    curPerm = SitePermissions.get(principal.URI, kTestPermissionKey);
+    Assert.deepEqual(curPerm, {
+                       state: SitePermissions.UNKNOWN,
+                       scope: SitePermissions.SCOPE_PERSISTENT,
+                     }, "Should have made no permanent permission entry");
     Assert.ok(denied, "The secondaryAction callback should have fired");
     Assert.ok(!allowed, "The mainAction callback should not have fired");
     Assert.ok(mockRequest._cancelled,
               "The request should have been cancelled");
     Assert.ok(!mockRequest._allowed,
               "The request should not have been allowed");
 
     // Clear the permission and pretend we never denied
-    Services.perms.removeFromPrincipal(principal, kTestPermissionKey);
+    SitePermissions.remove(principal.URI, kTestPermissionKey, browser);
     denied = false;
     mockRequest._cancelled = false;
 
     // Bring the PopupNotification back up now...
     shownPromise =
       BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
     TestPrompt.prompt();
     yield shownPromise;
 
-    // Next test allowing the permission request.
+    // Test denying the permission request.
+    Assert.equal(notification.secondaryActions.length, 1,
+                 "There should only be 1 secondary action");
+    yield clickSecondaryAction();
+    curPerm = SitePermissions.get(principal.URI, kTestPermissionKey);
+    Assert.deepEqual(curPerm, {
+                       state: SitePermissions.BLOCK,
+                       scope: SitePermissions.SCOPE_PERSISTENT
+                     }, "Should have denied the action");
+    Assert.ok(denied, "The secondaryAction callback should have fired");
+    Assert.ok(!allowed, "The mainAction callback should not have fired");
+    Assert.ok(mockRequest._cancelled,
+              "The request should have been cancelled");
+    Assert.ok(!mockRequest._allowed,
+              "The request should not have been allowed");
+
+    // Clear the permission and pretend we never denied
+    SitePermissions.remove(principal.URI, kTestPermissionKey);
+    denied = false;
+    mockRequest._cancelled = false;
+
+    // Bring the PopupNotification back up now...
+    shownPromise =
+      BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+    TestPrompt.prompt();
+    yield shownPromise;
+
+    // Test allowing the permission request.
     yield clickMainAction();
-    curPerm = Services.perms.testPermissionFromPrincipal(principal,
-                                                         kTestPermissionKey);
-    Assert.equal(curPerm, Ci.nsIPermissionManager.ALLOW_ACTION,
-                 "Should have allowed the action");
+    curPerm = SitePermissions.get(principal.URI, kTestPermissionKey);
+    Assert.deepEqual(curPerm, {
+                       state: SitePermissions.ALLOW,
+                       scope: SitePermissions.SCOPE_PERSISTENT
+                     }, "Should have allowed the action");
     Assert.ok(!denied, "The secondaryAction callback should not have fired");
     Assert.ok(allowed, "The mainAction callback should have fired");
     Assert.ok(!mockRequest._cancelled,
               "The request should not have been cancelled");
     Assert.ok(mockRequest._allowed,
               "The request should have been allowed");
   });
 });
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_SitePermissions.js
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Cu.import("resource:///modules/SitePermissions.jsm", this);
+
+// This asserts that SitePermissions.set can not save ALLOW permissions
+// temporarily on a tab.
+add_task(function* testTempAllowThrows() {
+  let uri = Services.io.newURI("https://example.com");
+  let id = "notifications";
+
+  yield BrowserTestUtils.withNewTab(uri.spec, function(browser) {
+    Assert.throws(function() {
+      SitePermissions.set(uri, id, SitePermissions.ALLOW, SitePermissions.SCOPE_TEMPORARY, browser);
+    }, "'Block' is the only permission we can save temporarily on a tab");
+  });
+});
+
+// This tests the SitePermissions.getAllPermissionDetailsForBrowser function.
+add_task(function* testGetAllPermissionDetailsForBrowser() {
+  let uri = Services.io.newURI("https://example.com");
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, uri.spec);
+
+  SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
+  SitePermissions.set(uri, "cookie", SitePermissions.ALLOW_COOKIES_FOR_SESSION);
+  SitePermissions.set(uri, "popup", SitePermissions.BLOCK);
+  SitePermissions.set(uri, "geo", SitePermissions.ALLOW, SitePermissions.SCOPE_SESSION);
+
+  let permissions = SitePermissions.getAllPermissionDetailsForBrowser(tab.linkedBrowser);
+
+  let camera = permissions.find(({id}) => id === "camera");
+  Assert.deepEqual(camera, {
+    id: "camera",
+    label: "Use the Camera",
+    state: SitePermissions.ALLOW,
+    scope: SitePermissions.SCOPE_PERSISTENT,
+    availableStates: [
+      { id: SitePermissions.UNKNOWN, label: "Always Ask" },
+      { id: SitePermissions.ALLOW, label: "Allow" },
+      { id: SitePermissions.BLOCK, label: "Block" },
+    ]
+  });
+
+  // check that removed permissions (State.UNKNOWN) are skipped
+  SitePermissions.remove(uri, "camera");
+  permissions = SitePermissions.getAllPermissionDetailsForBrowser(tab.linkedBrowser);
+
+  camera = permissions.find(({id}) => id === "camera");
+  Assert.equal(camera, undefined);
+
+  // check that different available state values are represented
+
+  let cookie = permissions.find(({id}) => id === "cookie");
+  Assert.deepEqual(cookie, {
+    id: "cookie",
+    label: "Set Cookies",
+    state: SitePermissions.ALLOW_COOKIES_FOR_SESSION,
+    scope: SitePermissions.SCOPE_PERSISTENT,
+    availableStates: [
+      { id: SitePermissions.ALLOW, label: "Allow" },
+      { id: SitePermissions.ALLOW_COOKIES_FOR_SESSION, label: "Allow for Session" },
+      { id: SitePermissions.BLOCK, label: "Block" },
+    ]
+  });
+
+  let popup = permissions.find(({id}) => id === "popup");
+  Assert.deepEqual(popup, {
+    id: "popup",
+    label: "Open Pop-up Windows",
+    state: SitePermissions.BLOCK,
+    scope: SitePermissions.SCOPE_PERSISTENT,
+    availableStates: [
+      { id: SitePermissions.ALLOW, label: "Allow" },
+      { id: SitePermissions.BLOCK, label: "Block" },
+    ]
+  });
+
+  let geo = permissions.find(({id}) => id === "geo");
+  Assert.deepEqual(geo, {
+    id: "geo",
+    label: "Access Your Location",
+    state: SitePermissions.ALLOW,
+    scope: SitePermissions.SCOPE_SESSION,
+    availableStates: [
+      { id: SitePermissions.UNKNOWN, label: "Always Ask" },
+      { id: SitePermissions.ALLOW, label: "Allow" },
+      { id: SitePermissions.BLOCK, label: "Block" },
+    ]
+  });
+
+  SitePermissions.remove(uri, "cookie");
+  SitePermissions.remove(uri, "popup");
+  SitePermissions.remove(uri, "geo");
+
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_SitePermissions_combinations.js
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Cu.import("resource:///modules/SitePermissions.jsm", this);
+
+// This function applies combinations of different permissions and
+// checks how they override each other.
+function* checkPermissionCombinations(combinations) {
+  let uri = Services.io.newURI("https://example.com");
+
+  yield BrowserTestUtils.withNewTab(uri.spec, function(browser) {
+    let id = "geo";
+    for (let {reverse, states, result} of combinations) {
+      let loop = () => {
+        for (let [state, scope] of states) {
+          SitePermissions.set(uri, id, state, scope, browser);
+        }
+        Assert.deepEqual(SitePermissions.get(uri, id, browser), result);
+        SitePermissions.remove(uri, id, browser);
+      };
+
+      loop();
+
+      if (reverse) {
+        states.reverse();
+        loop();
+      }
+    }
+  });
+}
+
+// Test that passing null as scope becomes SCOPE_PERSISTENT.
+add_task(function* testDefaultScope() {
+  yield checkPermissionCombinations([{
+    states: [
+      [SitePermissions.ALLOW, null],
+    ],
+    result: {
+      state: SitePermissions.ALLOW,
+      scope: SitePermissions.SCOPE_PERSISTENT,
+    },
+  }]);
+});
+
+// Test that "wide" scopes like PERSISTENT always override "narrower" ones like TAB.
+add_task(function* testScopeOverrides() {
+  yield checkPermissionCombinations([
+    {
+      // The behavior of SCOPE_SESSION is not in line with the general behavior
+      // because of the legacy nsIPermissionManager implementation.
+      states: [
+        [SitePermissions.ALLOW, SitePermissions.SCOPE_PERSISTENT],
+        [SitePermissions.BLOCK, SitePermissions.SCOPE_SESSION],
+      ],
+      result: {
+        state: SitePermissions.BLOCK,
+        scope: SitePermissions.SCOPE_SESSION,
+      },
+    }, {
+      states: [
+        [SitePermissions.BLOCK, SitePermissions.SCOPE_SESSION],
+        [SitePermissions.ALLOW, SitePermissions.SCOPE_PERSISTENT],
+      ],
+      result: {
+        state: SitePermissions.ALLOW,
+        scope: SitePermissions.SCOPE_PERSISTENT,
+      },
+
+    }, {
+      reverse: true,
+      states: [
+        [SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY],
+        [SitePermissions.ALLOW, SitePermissions.SCOPE_SESSION],
+      ],
+      result: {
+        state: SitePermissions.ALLOW,
+        scope: SitePermissions.SCOPE_SESSION,
+      },
+    }, {
+      reverse: true,
+      states: [
+        [SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY],
+        [SitePermissions.ALLOW, SitePermissions.SCOPE_PERSISTENT],
+      ],
+      result: {
+        state: SitePermissions.ALLOW,
+        scope: SitePermissions.SCOPE_PERSISTENT,
+      },
+    }
+  ]);
+});
+
+// Test that clearing a temporary permission also removes a
+// persistent permission that was set for the same URL.
+add_task(function* testClearTempPermission() {
+  yield checkPermissionCombinations([{
+    states: [
+      [SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY],
+      [SitePermissions.ALLOW, SitePermissions.SCOPE_PERSISTENT],
+      [SitePermissions.UNKNOWN, SitePermissions.SCOPE_TEMPORARY],
+    ],
+    result: {
+      state: SitePermissions.UNKNOWN,
+      scope: SitePermissions.SCOPE_PERSISTENT,
+    },
+  }]);
+});
+
+// Test that states override each other when applied with the same scope.
+add_task(function* testStateOverride() {
+  yield checkPermissionCombinations([
+    {
+      states: [
+        [SitePermissions.ALLOW, SitePermissions.SCOPE_PERSISTENT],
+        [SitePermissions.BLOCK, SitePermissions.SCOPE_PERSISTENT],
+      ],
+      result: {
+        state: SitePermissions.BLOCK,
+        scope: SitePermissions.SCOPE_PERSISTENT,
+      },
+    }, {
+      states: [
+        [SitePermissions.BLOCK, SitePermissions.SCOPE_PERSISTENT],
+        [SitePermissions.ALLOW, SitePermissions.SCOPE_PERSISTENT],
+      ],
+      result: {
+        state: SitePermissions.ALLOW,
+        scope: SitePermissions.SCOPE_PERSISTENT,
+      },
+    }
+  ]);
+});
+
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_SitePermissions_expiry.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Cu.import("resource:///modules/SitePermissions.jsm", this);
+
+// This tests the time delay to expire temporary permission entries.
+add_task(function* testTemporaryPermissionExpiry() {
+  SpecialPowers.pushPrefEnv({set: [
+        ["privacy.temporary_permission_expire_time_ms", 100],
+  ]});
+
+  let uri = Services.io.newURI("https://example.com")
+  let id = "camera";
+
+  yield BrowserTestUtils.withNewTab(uri.spec, function*(browser) {
+    SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
+
+    Assert.deepEqual(SitePermissions.get(uri, id, browser), {
+      state: SitePermissions.BLOCK,
+      scope: SitePermissions.SCOPE_TEMPORARY,
+    });
+
+    yield new Promise((c) => setTimeout(c, 500));
+
+    Assert.deepEqual(SitePermissions.get(uri, id, browser), {
+      state: SitePermissions.UNKNOWN,
+      scope: SitePermissions.SCOPE_PERSISTENT,
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_SitePermissions_tab_urls.js
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Cu.import("resource:///modules/SitePermissions.jsm", this);
+
+// This tests the key used to store the URI -> permission map on a tab.
+add_task(function* testTemporaryPermissionTabURLs() {
+
+  // Prevent showing a dialog for https://name:password@example.com
+  SpecialPowers.pushPrefEnv({set: [
+        ["network.http.phishy-userpass-length", 2048],
+  ]});
+
+  // This usually takes about 60 seconds on 32bit Linux debug,
+  // due to the combinatory nature of the test that is hard to fix.
+  requestLongerTimeout(2);
+
+  let same = [ "https://example.com", "https://example.com/sub/path", "https://example.com:443" ].map(Services.io.newURI);
+  let different = [ "https://example.com", "https://name:password@example.com", "https://test1.example.com", "http://example.com", "http://example.org" ].map(Services.io.newURI);
+
+  let id = "microphone";
+
+  yield BrowserTestUtils.withNewTab("about:blank", function*(browser) {
+    for (let uri of same) {
+        let loaded = BrowserTestUtils.browserLoaded(browser, false, uri.spec);
+        browser.loadURI(uri.spec);
+        yield loaded;
+
+        SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
+
+        for (let uri2 of same) {
+          let loaded2 = BrowserTestUtils.browserLoaded(browser, false, uri2.spec);
+          browser.loadURI(uri2.spec);
+          yield loaded2;
+
+          Assert.deepEqual(SitePermissions.get(uri2, id, browser), {
+            state: SitePermissions.BLOCK,
+            scope: SitePermissions.SCOPE_TEMPORARY,
+          }, `${uri.spec} should share tab permissions with ${uri2.spec}`);
+        }
+
+        SitePermissions.clearTemporaryPermissions(browser);
+    }
+
+    for (let uri of different) {
+      let loaded = BrowserTestUtils.browserLoaded(browser, false, uri.spec);
+      browser.loadURI(uri.spec);
+      yield loaded;
+
+      SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
+
+      Assert.deepEqual(SitePermissions.get(uri, id, browser), {
+        state: SitePermissions.BLOCK,
+        scope: SitePermissions.SCOPE_TEMPORARY,
+      });
+
+      for (let uri2 of different) {
+        loaded = BrowserTestUtils.browserLoaded(browser, false, uri2.spec);
+        browser.loadURI(uri2.spec);
+        yield loaded;
+
+        if (uri2 != uri) {
+          Assert.deepEqual(SitePermissions.get(uri2, id, browser), {
+            state: SitePermissions.UNKNOWN,
+            scope: SitePermissions.SCOPE_PERSISTENT,
+          }, `${uri.spec} should not share tab permissions with ${uri2.spec}`);
+        }
+      }
+
+      SitePermissions.clearTemporaryPermissions(browser);
+    }
+  });
+
+});
+
--- a/browser/modules/test/browser_UsageTelemetry.js
+++ b/browser/modules/test/browser_UsageTelemetry.js
@@ -5,16 +5,19 @@ const TAB_EVENT_COUNT = "browser.engagem
 const MAX_CONCURRENT_WINDOWS = "browser.engagement.max_concurrent_window_count";
 const WINDOW_OPEN_COUNT = "browser.engagement.window_open_event_count";
 const TOTAL_URI_COUNT = "browser.engagement.total_uri_count";
 const UNIQUE_DOMAINS_COUNT = "browser.engagement.unique_domains_count";
 const UNFILTERED_URI_COUNT = "browser.engagement.unfiltered_uri_count";
 
 const TELEMETRY_SUBSESSION_TOPIC = "internal-telemetry-after-subsession-split";
 
+// Reset internal URI counter in case URIs were opened by other tests.
+Services.obs.notifyObservers(null, TELEMETRY_SUBSESSION_TOPIC, "");
+
 /**
  * Waits for the web progress listener associated with this tab to fire an
  * onLocationChange for a non-error page.
  *
  * @param {xul:browser} browser
  *        A xul:browser.
  *
  * @return {Promise}
@@ -56,18 +59,17 @@ let checkScalar = (scalars, scalarName, 
   }
   ok(!(scalarName in scalars), scalarName + " must not be reported.");
 };
 
 /**
  * Get a snapshot of the scalars and check them against the provided values.
  */
 let checkScalars = (countsObject) => {
-  const scalars =
-    Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
 
   // Check the expected values. Scalars that are never set must not be reported.
   checkScalar(scalars, MAX_CONCURRENT_TABS, countsObject.maxTabs,
               "The maximum tab count must match the expected value.");
   checkScalar(scalars, TAB_EVENT_COUNT, countsObject.tabOpenCount,
               "The number of open tab event count must match the expected value.");
   checkScalar(scalars, MAX_CONCURRENT_WINDOWS, countsObject.maxWindows,
               "The maximum window count must match the expected value.");
@@ -179,18 +181,17 @@ add_task(function* test_subsessionSplit(
 });
 
 add_task(function* test_URIAndDomainCounts() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
 
   let checkCounts = (countsObject) => {
     // Get a snapshot of the scalars and then clear them.
-    const scalars =
-      Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
     checkScalar(scalars, TOTAL_URI_COUNT, countsObject.totalURIs,
                 "The URI scalar must contain the expected value.");
     checkScalar(scalars, UNIQUE_DOMAINS_COUNT, countsObject.domainCount,
                 "The unique domains scalar must contain the expected value.");
     checkScalar(scalars, UNFILTERED_URI_COUNT, countsObject.totalUnfilteredURIs,
                 "The unfiltered URI scalar must contain the expected value.");
   };
 
--- a/browser/modules/test/browser_UsageTelemetry_content.js
+++ b/browser/modules/test/browser_UsageTelemetry_content.js
@@ -66,18 +66,17 @@ add_task(function* test_context_menu() {
                                            gBrowser.selectedBrowser);
   yield popupPromise;
 
   info("Click on search.");
   let searchItem = contextMenu.getElementsByAttribute("id", "context-searchselect")[0];
   searchItem.click();
 
   info("Validate the search metrics.");
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(scalars, SCALAR_CONTEXT_MENU, "search", 1);
   Assert.equal(Object.keys(scalars[SCALAR_CONTEXT_MENU]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.contextmenu', 1);
 
   // Also check events.
@@ -103,18 +102,17 @@ add_task(function* test_about_newtab() {
 
   info("Trigger a simple serch, just text + enter.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield typeInSearchField(tab.linkedBrowser, "test query", "newtab-search-text");
   yield BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser);
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(scalars, SCALAR_ABOUT_NEWTAB, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_ABOUT_NEWTAB]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.newtab', 1);
 
   // Also check events.
--- a/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
+++ b/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
@@ -66,18 +66,17 @@ add_task(function* test_abouthome_simple
 
   info("Trigger a simple serch, just test + enter.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield typeInSearchField(tab.linkedBrowser, "test query", "searchText");
   yield BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser);
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(scalars, SCALAR_ABOUT_HOME, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_ABOUT_HOME]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.abouthome', 1);
 
   // Also check events.
--- a/browser/modules/test/browser_UsageTelemetry_private_and_restore.js
+++ b/browser/modules/test/browser_UsageTelemetry_private_and_restore.js
@@ -22,18 +22,17 @@ add_task(function* test_privateMode() {
   Services.telemetry.clearScalars();
 
   // Open a private window and load a website in it.
   let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
   yield BrowserTestUtils.loadURI(privateWin.gBrowser.selectedBrowser, "http://example.com/");
   yield BrowserTestUtils.browserLoaded(privateWin.gBrowser.selectedBrowser);
 
   // Check that tab and window count is recorded.
-  const scalars =
-    Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
 
   ok(!(TOTAL_URI_COUNT in scalars), "We should not track URIs in private mode.");
   ok(!(UNFILTERED_URI_COUNT in scalars), "We should not track URIs in private mode.");
   ok(!(UNIQUE_DOMAINS_COUNT in scalars), "We should not track unique domains in private mode.");
   is(scalars[TAB_EVENT_COUNT], 1, "The number of open tab event count must match the expected value.");
   is(scalars[MAX_CONCURRENT_TABS], 2, "The maximum tab count must match the expected value.");
   is(scalars[WINDOW_OPEN_COUNT], 1, "The number of window open event count must match the expected value.");
   is(scalars[MAX_CONCURRENT_WINDOWS], 2, "The maximum window count must match the expected value.");
@@ -71,18 +70,17 @@ add_task(function* test_sessionRestore()
 
   // Load the custom state and wait for SSTabRestored, as we want to make sure
   // that the URI counting code was hit.
   let tabRestored = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "SSTabRestored");
   SessionStore.setBrowserState(JSON.stringify(state));
   yield tabRestored;
 
   // Check that the URI is not recorded.
-  const scalars =
-    Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
 
   ok(!(TOTAL_URI_COUNT in scalars), "We should not track URIs from restored sessions.");
   ok(!(UNFILTERED_URI_COUNT in scalars), "We should not track URIs from restored sessions.");
   ok(!(UNIQUE_DOMAINS_COUNT in scalars), "We should not track unique domains from restored sessions.");
 
   // Restore the original session and cleanup.
   let sessionRestored = promiseBrowserStateRestored();
   SessionStore.setBrowserState(JSON.stringify(state));
--- a/browser/modules/test/browser_UsageTelemetry_searchbar.js
+++ b/browser/modules/test/browser_UsageTelemetry_searchbar.js
@@ -92,18 +92,17 @@ add_task(function* test_plainQuery() {
 
   info("Simulate entering a simple search.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInSearchbar("simple query");
   EventUtils.sendKey("return");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.searchbar', 1);
 
   // Also check events.
@@ -127,18 +126,17 @@ add_task(function* test_oneOff() {
   yield searchInSearchbar("query");
 
   info("Pressing Alt+Down to highlight the first one off engine.");
   EventUtils.synthesizeKey("VK_DOWN", { altKey: true });
   EventUtils.sendKey("return");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_oneoff", 1);
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch2.searchbar', 1);
 
   // Also check events.
@@ -173,18 +171,17 @@ add_task(function* test_suggestion() {
   info("Perform a one-off search using the first engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInSearchbar("query");
   info("Clicking the searchbar suggestion.");
   clickSearchbarSuggestion("queryfoo");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_suggestion", 1);
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   let searchEngineId = 'other-' + suggestionEngine.name;
   checkKeyedHistogram(search_hist, searchEngineId + '.searchbar', 1);
 
--- a/browser/modules/test/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser_UsageTelemetry_urlbar.js
@@ -111,18 +111,17 @@ add_task(function* test_simpleQuery() {
 
   info("Simulate entering a simple search.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInAwesomebar("simple query");
   EventUtils.sendKey("return");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(scalars, SCALAR_URLBAR, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.urlbar', 1);
 
   // Also check events.
@@ -157,18 +156,17 @@ add_task(function* test_searchAlias() {
 
   info("Search using a search alias.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInAwesomebar("mozalias query");
   EventUtils.sendKey("return");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(scalars, SCALAR_URLBAR, "search_alias", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.urlbar', 1);
 
   // Also check events.
@@ -206,18 +204,17 @@ add_task(function* test_oneOff() {
   yield searchInAwesomebar("query");
 
   info("Pressing Alt+Down to take us to the first one-off engine.");
   EventUtils.synthesizeKey("VK_DOWN", { altKey: true });
   EventUtils.sendKey("return");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(scalars, SCALAR_URLBAR, "search_oneoff", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.urlbar', 1);
 
   // Also check events.
@@ -266,18 +263,17 @@ add_task(function* test_suggestion() {
   info("Perform a one-off search using the first engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInAwesomebar("query");
   info("Clicking the urlbar suggestion.");
   clickURLBarSuggestion("queryfoo");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(scalars, SCALAR_URLBAR, "search_suggestion", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   let searchEngineId = 'other-' + suggestionEngine.name;
   checkKeyedHistogram(search_hist, searchEngineId + '.urlbar', 1);
 
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_bug1319078.js
@@ -0,0 +1,49 @@
+"use strict";
+
+var gInvalidFormPopup = document.getElementById('invalid-form-popup');
+
+function checkPopupHide() {
+  ok(gInvalidFormPopup.state != 'showing' && gInvalidFormPopup.state != 'open',
+     "[Test " + testId + "] The invalid form popup should not be shown");
+}
+
+var testId = 0;
+
+function incrementTest() {
+  testId++;
+  info("Starting next part of test");
+}
+
+/**
+ * In this test, we check that no popup appears if the element display is none.
+ */
+add_task(function* () {
+  ok(gInvalidFormPopup,
+     "The browser should have a popup to show when a form is invalid");
+
+  incrementTest();
+  let testPage =
+    'data:text/html,' +
+    '<form target="t"><input type="url"  placeholder="url" value="http://" style="display: none;"><input id="s" type="button" value="check"></form>';
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage);
+  yield BrowserTestUtils.synthesizeMouse("#s", 0, 0, {}, gBrowser.selectedBrowser);
+
+  checkPopupHide();
+  yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * In this test, we check that no popup appears if the element visibility is hidden.
+ */
+add_task(function* () {
+  incrementTest();
+  let testPage =
+    'data:text/html,' +
+    '<form target="t"><input type="url"  placeholder="url" value="http://" style="visibility: hidden;"><input id="s" type="button" value="check"></form>';
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage);
+  yield BrowserTestUtils.synthesizeMouse("#s", 0, 0, {}, gBrowser.selectedBrowser);
+
+  checkPopupHide();
+  yield BrowserTestUtils.removeTab(tab);
+});
+
--- a/browser/modules/test/head.js
+++ b/browser/modules/test/head.js
@@ -90,16 +90,26 @@ function getSearchCountsHistogram() {
  * Check that the keyed histogram contains the right value.
  */
 function checkKeyedHistogram(h, key, expectedValue) {
   const snapshot = h.snapshot();
   Assert.ok(key in snapshot, `The histogram must contain ${key}.`);
   Assert.equal(snapshot[key].sum, expectedValue, `The key ${key} must contain ${expectedValue}.`);
 }
 
+/**
+ * Return the scalars from the parent-process.
+ */
+function getParentProcessScalars(aChannel, aKeyed = false, aClear = false) {
+  const scalars = aKeyed ?
+    Services.telemetry.snapshotKeyedScalars(aChannel, aClear)["default"] :
+    Services.telemetry.snapshotScalars(aChannel, aClear)["default"];
+  return scalars || {};
+}
+
 function checkEvents(events, expectedEvents) {
   if (!Services.telemetry.canRecordExtended) {
     // Currently we only collect the tested events when extended Telemetry is enabled.
     return;
   }
 
   Assert.equal(events.length, expectedEvents.length, "Should have matching amount of events.");
 
--- a/browser/modules/test/xpcshell/test_SitePermissions.js
+++ b/browser/modules/test/xpcshell/test_SitePermissions.js
@@ -19,97 +19,35 @@ add_task(function* testGetAllByURI() {
   let wrongURI = Services.io.newURI("file:///example.js")
   Assert.deepEqual(SitePermissions.getAllByURI(wrongURI), []);
 
   let uri = Services.io.newURI("https://example.com")
   Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
 
   SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
   Assert.deepEqual(SitePermissions.getAllByURI(uri), [
-      { id: "camera", state: SitePermissions.ALLOW }
+      { id: "camera", state: SitePermissions.ALLOW, scope: SitePermissions.SCOPE_PERSISTENT }
   ]);
 
-  SitePermissions.set(uri, "microphone", SitePermissions.SESSION);
+  SitePermissions.set(uri, "microphone", SitePermissions.ALLOW, SitePermissions.SCOPE_SESSION);
   SitePermissions.set(uri, "desktop-notification", SitePermissions.BLOCK);
 
   Assert.deepEqual(SitePermissions.getAllByURI(uri), [
-      { id: "camera", state: SitePermissions.ALLOW },
-      { id: "microphone", state: SitePermissions.SESSION },
-      { id: "desktop-notification", state: SitePermissions.BLOCK }
+      { id: "camera", state: SitePermissions.ALLOW, scope: SitePermissions.SCOPE_PERSISTENT },
+      { id: "microphone", state: SitePermissions.ALLOW, scope: SitePermissions.SCOPE_SESSION },
+      { id: "desktop-notification", state: SitePermissions.BLOCK, scope: SitePermissions.SCOPE_PERSISTENT }
   ]);
 
   SitePermissions.remove(uri, "microphone");
   Assert.deepEqual(SitePermissions.getAllByURI(uri), [
-      { id: "camera", state: SitePermissions.ALLOW },
-      { id: "desktop-notification", state: SitePermissions.BLOCK }
+      { id: "camera", state: SitePermissions.ALLOW, scope: SitePermissions.SCOPE_PERSISTENT },
+      { id: "desktop-notification", state: SitePermissions.BLOCK, scope: SitePermissions.SCOPE_PERSISTENT }
   ]);
 
   SitePermissions.remove(uri, "camera");
   SitePermissions.remove(uri, "desktop-notification");
   Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
 
   // XXX Bug 1303108 - Control Center should only show non-default permissions
   SitePermissions.set(uri, "addon", SitePermissions.BLOCK);
   Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
   SitePermissions.remove(uri, "addon");
 });
-
-add_task(function* testGetPermissionDetailsByURI() {
-  // check that it returns an empty array on an invalid URI
-  // like a file URI, which doesn't support site permissions
-  let wrongURI = Services.io.newURI("file:///example.js")
-  Assert.deepEqual(SitePermissions.getPermissionDetailsByURI(wrongURI), []);
-
-  let uri = Services.io.newURI("https://example.com")
-
-  SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
-  SitePermissions.set(uri, "cookie", SitePermissions.SESSION);
-  SitePermissions.set(uri, "popup", SitePermissions.BLOCK);
-
-  let permissions = SitePermissions.getPermissionDetailsByURI(uri);
-
-  let camera = permissions.find(({id}) => id === "camera");
-  Assert.deepEqual(camera, {
-    id: "camera",
-    label: "Use the Camera",
-    state: SitePermissions.ALLOW,
-    availableStates: [
-      { id: SitePermissions.UNKNOWN, label: "Always Ask" },
-      { id: SitePermissions.ALLOW, label: "Allow" },
-      { id: SitePermissions.BLOCK, label: "Block" },
-    ]
-  });
-
-  // check that removed permissions (State.UNKNOWN) are skipped
-  SitePermissions.remove(uri, "camera");
-  permissions = SitePermissions.getPermissionDetailsByURI(uri);
-
-  camera = permissions.find(({id}) => id === "camera");
-  Assert.equal(camera, undefined);
-
-  // check that different available state values are represented
-
-  let cookie = permissions.find(({id}) => id === "cookie");
-  Assert.deepEqual(cookie, {
-    id: "cookie",
-    label: "Set Cookies",
-    state: SitePermissions.SESSION,
-    availableStates: [
-      { id: SitePermissions.ALLOW, label: "Allow" },
-      { id: SitePermissions.SESSION, label: "Allow for Session" },
-      { id: SitePermissions.BLOCK, label: "Block" },
-    ]
-  });
-
-  let popup = permissions.find(({id}) => id === "popup");
-  Assert.deepEqual(popup, {
-    id: "popup",
-    label: "Open Pop-up Windows",
-    state: SitePermissions.BLOCK,
-    availableStates: [
-      { id: SitePermissions.ALLOW, label: "Allow" },
-      { id: SitePermissions.BLOCK, label: "Block" },
-    ]
-  });
-
-  SitePermissions.remove(uri, "cookie");
-  SitePermissions.remove(uri, "popup");
-});
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -15,16 +15,18 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
+                                  "resource:///modules/SitePermissions.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
   return Services.strings.createBundle("chrome://branding/locale/brand.properties");
 });
 
 this.webrtcUI = {
   peerConnectionBlockers: new Set(),
   emitter: new EventEmitter(),
@@ -328,24 +330,51 @@ function getHost(uri, href) {
       let bundle = Services.strings.createBundle(kBundleURI);
       host = bundle.GetStringFromName("getUserMedia.sharingMenuUnknownHost");
     }
   }
   return host;
 }
 
 function prompt(aBrowser, aRequest) {
-  let {audioDevices: audioDevices, videoDevices: videoDevices,
-       sharingScreen: sharingScreen, sharingAudio: sharingAudio,
-       requestTypes: requestTypes} = aRequest;
+  let { audioDevices, videoDevices, sharingScreen, sharingAudio,
+        requestTypes } = aRequest;
+
+  // If the user has already denied access once in this tab,
+  // deny again without even showing the notification icon.
+  if ((audioDevices.length && SitePermissions
+        .get(null, "microphone", aBrowser).state == SitePermissions.BLOCK) ||
+      (videoDevices.length && SitePermissions
+        .get(null, sharingScreen ? "screen" : "camera", aBrowser).state == SitePermissions.BLOCK)) {
+    denyRequest(aBrowser, aRequest);
+    return;
+  }
+
   let uri = Services.io.newURI(aRequest.documentURI);
   let host = getHost(uri);
   let chromeDoc = aBrowser.ownerDocument;
   let stringBundle = chromeDoc.defaultView.gNavigatorBundle;
-  let stringId = "getUserMedia.share" + requestTypes.join("And") + "2.message";
+
+  // Mind the order, because for simplicity we're iterating over the list using
+  // "includes()". This allows the rotation of string identifiers. We list the
+  // full identifiers here so they can be cross-referenced more easily.
+  let joinedRequestTypes = requestTypes.join("And");
+  let stringId = [
+    // Individual request types first.
+    "getUserMedia.shareCamera2.message",
+    "getUserMedia.shareMicrophone2.message",
+    "getUserMedia.shareScreen3.message",
+    "getUserMedia.shareAudioCapture2.message",
+    // Combinations of the above request types last.
+    "getUserMedia.shareCameraAndMicrophone2.message",
+    "getUserMedia.shareCameraAndAudioCapture2.message",
+    "getUserMedia.shareScreenAndMicrophone3.message",
+    "getUserMedia.shareScreenAndAudioCapture3.message",
+  ].find(id => id.includes(joinedRequestTypes));
+
   let message = stringBundle.getFormattedString(stringId, [host]);
 
   let notification; // Used by action callbacks.
   let mainAction = {
     label: stringBundle.getString("getUserMedia.allow.label"),
     accessKey: stringBundle.getString("getUserMedia.allow.accesskey"),
     // The real callback will be set during the "showing" event. The
     // empty function here is so that PopupNotifications.show doesn't
@@ -354,35 +383,38 @@ function prompt(aBrowser, aRequest) {
   };
 
   let secondaryActions = [
     {
       label: stringBundle.getString("getUserMedia.dontAllow.label"),
       accessKey: stringBundle.getString("getUserMedia.dontAllow.accesskey"),
       callback(aState) {
         denyRequest(notification.browser, aRequest);
+        let scope = SitePermissions.SCOPE_TEMPORARY;
         if (aState && aState.checkboxChecked) {
-          let perms = Services.perms;
-          if (audioDevices.length)
-            perms.add(uri, "microphone", perms.DENY_ACTION);
-          if (videoDevices.length)
-            perms.add(uri, sharingScreen ? "screen" : "camera", perms.DENY_ACTION);
+          scope = SitePermissions.SCOPE_PERSISTENT;
         }
+        if (audioDevices.length)
+          SitePermissions.set(uri, "microphone",
+                              SitePermissions.BLOCK, scope, notification.browser);
+        if (videoDevices.length)
+          SitePermissions.set(uri, sharingScreen ? "screen" : "camera",
+                              SitePermissions.BLOCK, scope, notification.browser);
       }
     }
   ];
 
   let productName = gBrandBundle.GetStringFromName("brandShortName");
 
   // Disable the permanent 'Allow' action if the connection isn't secure, or for
   // screen/audio sharing (because we can't guess which window the user wants to
   // share without prompting).
   let reasonForNoPermanentAllow = "";
   if (sharingScreen) {
-    reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.screen";
+    reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.screen2";
   } else if (sharingAudio) {
     reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.audio";
   } else if (!aRequest.secure) {
     reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.insecure";
   }
 
   let options = {
     persistent: true,
@@ -417,55 +449,49 @@ function prompt(aBrowser, aRequest) {
           menupopup.removeEventListener("command", menupopup._commandEventListener);
           menupopup._commandEventListener = null;
         }
       }
 
       if (aTopic != "showing")
         return false;
 
-      // DENY_ACTION is handled immediately by MediaManager, but handling
-      // of ALLOW_ACTION is delayed until the popupshowing event
+      // BLOCK is handled immediately by MediaManager if it has been set
+      // persistently in the permission manager. If it has been set on the tab,
+      // it is handled synchronously before we add the notification.
+      // Handling of ALLOW is delayed until the popupshowing event,
       // to avoid granting permissions automatically to background tabs.
       if (aRequest.secure) {
-        let perms = Services.perms;
+        let micAllowed =
+          SitePermissions.get(uri, "microphone").state == SitePermissions.ALLOW;
+        let camAllowed =
+          SitePermissions.get(uri, "camera").state == SitePermissions.ALLOW;
 
-        let micPerm = perms.testExactPermission(uri, "microphone");
-        if (micPerm == perms.PROMPT_ACTION)
-          micPerm = perms.UNKNOWN_ACTION;
-
-        let camPerm = perms.testExactPermission(uri, "camera");
-
+        let perms = Services.perms;
         let mediaManagerPerm =
           perms.testExactPermission(uri, "MediaManagerVideo");
         if (mediaManagerPerm) {
           perms.remove(uri, "MediaManagerVideo");
         }
 
-        if (camPerm == perms.PROMPT_ACTION)
-          camPerm = perms.UNKNOWN_ACTION;
-
         // Screen sharing shouldn't follow the camera permissions.
         if (videoDevices.length && sharingScreen)
-          camPerm = perms.UNKNOWN_ACTION;
+          camAllowed = false;
 
-        // We don't check that permissions are set to ALLOW_ACTION in this
-        // test; only that they are set. This is because if audio is allowed
-        // and video is denied persistently, we don't want to show the prompt,
-        // and will grant audio access immediately.
-        if ((!audioDevices.length || micPerm) && (!videoDevices.length || camPerm)) {
+        if ((!audioDevices.length || micAllowed) &&
+            (!videoDevices.length || camAllowed)) {
           // All permissions we were about to request are already persistently set.
           let allowedDevices = [];
-          if (videoDevices.length && camPerm == perms.ALLOW_ACTION) {
+          if (videoDevices.length && camAllowed) {
             allowedDevices.push(videoDevices[0].deviceIndex);
             Services.perms.add(uri, "MediaManagerVideo",
                                Services.perms.ALLOW_ACTION,
                                Services.perms.EXPIRE_SESSION);
           }
-          if (audioDevices.length && micPerm == perms.ALLOW_ACTION)
+          if (audioDevices.length && micAllowed)
             allowedDevices.push(audioDevices[0].deviceIndex);
 
           // Remember on which URIs we found persistent permissions so that we
           // can remove them if the user clicks 'Stop Sharing'. There's no
           // other way for the stop sharing code to know the hostnames of frames
           // using devices until bug 1066082 is fixed.
           let browser = this.browser;
           browser._devicePermissionURIs = browser._devicePermissionURIs || [];
@@ -649,31 +675,34 @@ function prompt(aBrowser, aRequest) {
           let videoDeviceIndex = doc.getElementById(listId).value;
           let allowCamera = videoDeviceIndex != "-1";
           if (allowCamera) {
             allowedDevices.push(videoDeviceIndex);
             // Session permission will be removed after use
             // (it's really one-shot, not for the entire session)
             perms.add(uri, "MediaManagerVideo", perms.ALLOW_ACTION,
                       perms.EXPIRE_SESSION);
-          }
-          if (remember) {
-            perms.add(uri, "camera",
-                      allowCamera ? perms.ALLOW_ACTION : perms.DENY_ACTION);
+            if (remember)
+              SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
+          } else {
+            let scope = remember ? SitePermissions.SCOPE_PERSISTENT : SitePermissions.SCOPE_TEMPORARY;
+            SitePermissions.set(uri, "camera", SitePermissions.BLOCK, scope, aBrowser);
           }
         }
         if (audioDevices.length) {
           if (!sharingAudio) {
             let audioDeviceIndex = doc.getElementById("webRTC-selectMicrophone-menulist").value;
             let allowMic = audioDeviceIndex != "-1";
-            if (allowMic)
+            if (allowMic) {
               allowedDevices.push(audioDeviceIndex);
-            if (remember) {
-              perms.add(uri, "microphone",
-                        allowMic ? perms.ALLOW_ACTION : perms.DENY_ACTION);
+              if (remember)
+                SitePermissions.set(uri, "microphone", SitePermissions.ALLOW);
+            } else {
+                let scope = remember ? SitePermissions.SCOPE_PERSISTENT : SitePermissions.SCOPE_TEMPORARY;
+                SitePermissions.set(uri, "microphone", SitePermissions.BLOCK, scope, aBrowser);
             }
           } else {
             // Only one device possible for audio capture.
             allowedDevices.push(0);
           }
         }
 
         if (!allowedDevices.length) {
--- a/browser/themes/shared/identity-block/icons.inc.css
+++ b/browser/themes/shared/identity-block/icons.inc.css
@@ -38,25 +38,21 @@
 
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedDomain > #connection-icon@selectorSuffix@,
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity > #connection-icon@selectorSuffix@,
 #urlbar[pageproxystate="valid"] > #identity-box.mixedActiveBlocked > #connection-icon@selectorSuffix@ {
   list-style-image: url(chrome://browser/skin/connection-secure.svg);
   visibility: visible;
 }
 
+#urlbar[pageproxystate="valid"] > #identity-box.weakCipher > #connection-icon@selectorSuffix@,
+#urlbar[pageproxystate="valid"] > #identity-box.mixedDisplayContent > #connection-icon@selectorSuffix@,
+#urlbar[pageproxystate="valid"] > #identity-box.mixedDisplayContentLoadedActiveBlocked > #connection-icon@selectorSuffix@,
 #urlbar[pageproxystate="valid"] > #identity-box.certUserOverridden > #connection-icon@selectorSuffix@ {
   list-style-image: url(chrome://browser/skin/connection-mixed-passive-loaded.svg#icon@iconVariant@);
   visibility: visible;
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.insecureLoginForms > #connection-icon@selectorSuffix@,
 #urlbar[pageproxystate="valid"] > #identity-box.mixedActiveContent > #connection-icon@selectorSuffix@ {
   list-style-image: url(chrome://browser/skin/connection-mixed-active-loaded.svg#icon@iconVariant@);
   visibility: visible;
 }
-
-#urlbar[pageproxystate="valid"] > #identity-box.weakCipher > #connection-icon@selectorSuffix@,
-#urlbar[pageproxystate="valid"] > #identity-box.mixedDisplayContent > #connection-icon@selectorSuffix@,
-#urlbar[pageproxystate="valid"] > #identity-box.mixedDisplayContentLoadedActiveBlocked > #connection-icon@selectorSuffix@ {
-  list-style-image: url(chrome://browser/skin/connection-mixed-passive-loaded.svg#icon@iconVariant@);
-  visibility: visible;
-}
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
@@ -88,21 +88,22 @@ this.ControlCenter = {
 
         yield loadPage(PERMISSIONS_PAGE);
         yield openIdentityPopup();
       }),
     },
 
     allPermissions: {
       applyConfig: Task.async(function* () {
-        // there are 3 possible non-default permission states, so we alternate between them
-        let states = [SitePermissions.ALLOW, SitePermissions.BLOCK, SitePermissions.SESSION];
+        // TODO: (Bug 1330601) Rewrite this to consider temporary (TAB) permission states.
+        // There are 2 possible non-default permission states, so we alternate between them.
+        let states = [SitePermissions.ALLOW, SitePermissions.BLOCK];
         let uri = Services.io.newURI(PERMISSIONS_PAGE)
         SitePermissions.listPermissions().forEach(function(permission, index) {
-          SitePermissions.set(uri, permission, states[index % 3]);
+          SitePermissions.set(uri, permission, states[index % 2]);
         });
 
         yield loadPage(PERMISSIONS_PAGE);
         yield openIdentityPopup();
       }),
     },
 
     mixed: {
--- a/devtools/.eslintrc.js
+++ b/devtools/.eslintrc.js
@@ -25,17 +25,17 @@ module.exports = {
     "Node": true,
     "reportError": true,
     "require": true,
     "setInterval": true,
     "setTimeout": true,
     "uneval": true,
     "URL": true,
     "WebSocket": true,
-    "XMLHttpRequest": true,
+    "XMLHttpRequest": true
   },
   "rules": {
     // These are the rules that have been configured so far to match the
     // devtools coding style.
 
     // Rules from the mozilla plugin
     "mozilla/no-aArgs": "warn",
     "mozilla/no-cpows-in-tests": "error",
--- a/devtools/client/canvasdebugger/test/browser.ini
+++ b/devtools/client/canvasdebugger/test/browser.ini
@@ -21,20 +21,23 @@ support-files =
 [browser_canvas-actor-test-03.js]
 [browser_canvas-actor-test-04.js]
 [browser_canvas-actor-test-05.js]
 [browser_canvas-actor-test-06.js]
 [browser_canvas-actor-test-07.js]
 [browser_canvas-actor-test-08.js]
 [browser_canvas-actor-test-09.js]
 subsuite = gpu
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_canvas-actor-test-10.js]
 subsuite = gpu
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_canvas-actor-test-11.js]
 subsuite = gpu
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_canvas-actor-test-12.js]
 [browser_canvas-frontend-call-highlight.js]
 [browser_canvas-frontend-call-list.js]
 [browser_canvas-frontend-call-search.js]
 [browser_canvas-frontend-call-stack-01.js]
 [browser_canvas-frontend-call-stack-02.js]
 [browser_canvas-frontend-call-stack-03.js]
 [browser_canvas-frontend-clear.js]
--- a/devtools/client/commandline/test/browser.ini
+++ b/devtools/client/commandline/test/browser.ini
@@ -80,16 +80,17 @@ support-files =
 [browser_cmd_pref1.js]
 [browser_cmd_pref2.js]
 [browser_cmd_pref3.js]
 [browser_cmd_qsa.js]
 [browser_cmd_restart.js]
 [browser_cmd_rulers.js]
 [browser_cmd_screenshot.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 support-files =
   browser_cmd_screenshot.html
 [browser_cmd_settings.js]
 [browser_gcli_async.js]
 [browser_gcli_canon.js]
 [browser_gcli_cli1.js]
 [browser_gcli_cli2.js]
 [browser_gcli_completion1.js]
rename from devtools/client/debugger/new/styles.css
rename to devtools/client/debugger/new/debugger.css
rename from devtools/client/debugger/new/bundle.js
rename to devtools/client/debugger/new/debugger.js
--- a/devtools/client/debugger/new/index.html
+++ b/devtools/client/debugger/new/index.html
@@ -8,24 +8,24 @@
           type="text/css"
           href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css" />
     <link rel="stylesheet"
           type="text/css"
           href="chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css" />
     <link rel="stylesheet"
           type="text/css"
           href="chrome://devtools/content/sourceeditor/codemirror/mozilla.css" />
-    <link rel="stylesheet" type="text/css" href="resource://devtools/client/debugger/new/styles.css" />
+    <link rel="stylesheet" type="text/css" href="resource://devtools/client/debugger/new/debugger.css" />
   </head>
   <body>
     <div id="mount"></div>
     <script type="application/javascript;version=1.8"
             src="chrome://devtools/content/shared/theme-switching.js"></script>
     <script type="text/javascript">
       const { BrowserLoader } = Components.utils.import("resource://devtools/client/shared/browser-loader.js", {});
       const { require: devtoolsRequire } = BrowserLoader({
         baseURI: "resource://devtools/client/debugger/new/",
         window,
       });
     </script>
-    <script type="text/javascript" src="resource://devtools/client/debugger/new/bundle.js"></script>
+    <script type="text/javascript" src="resource://devtools/client/debugger/new/debugger.js"></script>
   </body>
 </html>
--- a/devtools/client/debugger/new/moz.build
+++ b/devtools/client/debugger/new/moz.build
@@ -1,12 +1,12 @@
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
-    'bundle.js',
+    'debugger.css',
+    'debugger.js',
     'panel.js',
     'pretty-print-worker.js',
-    'source-map-worker.js',
-    'styles.css'
+    'source-map-worker.js'
 )
--- a/devtools/client/debugger/test/mochitest/browser2.ini
+++ b/devtools/client/debugger/test/mochitest/browser2.ini
@@ -280,25 +280,26 @@ skip-if = e10s && debug
 skip-if = e10s && debug
 [browser_dbg_source-maps-03.js]
 skip-if = e10s && debug
 [browser_dbg_source-maps-04.js]
 skip-if = e10s # Bug 1093535
 [browser_dbg_sources-cache.js]
 [browser_dbg_sources-contextmenu-01.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_dbg_sources-contextmenu-02.js]
 skip-if = e10s && debug
 [browser_dbg_sources-eval-01.js]
 skip-if = true # non-named eval sources turned off for now, bug 1124106
 [browser_dbg_sources-eval-02.js]
 [browser_dbg_sources-iframe-reload.js]
 [browser_dbg_sources-keybindings.js]
 subsuite = clipboard
-skip-if = e10s && debug
+skip-if = (e10s && debug) || (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_dbg_sources-labels.js]
 skip-if = e10s && debug
 [browser_dbg_sources-large.js]
 [browser_dbg_sources-sorting.js]
 skip-if = e10s && debug
 [browser_dbg_sources-bookmarklet.js]
 skip-if = e10s && debug
 [browser_dbg_sources-webext-contentscript.js]
@@ -317,17 +318,17 @@ skip-if = e10s && (debug || asan) # time
 [browser_dbg_stack-06.js]
 skip-if = e10s && debug
 [browser_dbg_stack-07.js]
 skip-if = e10s && debug
 [browser_dbg_stack-contextmenu-01.js]
 skip-if = e10s && debug
 [browser_dbg_stack-contextmenu-02.js]
 subsuite = clipboard
-skip-if = e10s && debug
+skip-if = (e10s && debug) || (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_dbg_step-out.js]
 skip-if = e10s && debug
 [browser_dbg_tabactor-01.js]
 skip-if = e10s # TODO
 [browser_dbg_tabactor-02.js]
 skip-if = e10s # TODO
 [browser_dbg_terminate-on-tab-close.js]
 skip-if = e10s && debug
@@ -344,17 +345,17 @@ skip-if = e10s && debug
 [browser_dbg_variables-view-06.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-07.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-08.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-accessibility.js]
 subsuite = clipboard
-skip-if = e10s && debug
+skip-if = (e10s && debug) || (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_dbg_variables-view-data.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-edit-cancel.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-edit-click.js]
 skip-if = e10s || (os == 'mac' || os == 'win') && (debug == false) # Bug 986166
 [browser_dbg_variables-view-edit-getset-01.js]
 skip-if = e10s && debug
--- a/devtools/client/inspector/computed/test/browser.ini
+++ b/devtools/client/inspector/computed/test/browser.ini
@@ -29,13 +29,15 @@ support-files =
 [browser_computed_no-results-placeholder.js]
 [browser_computed_original-source-link.js]
 [browser_computed_pseudo-element_01.js]
 [browser_computed_refresh-on-style-change_01.js]
 [browser_computed_search-filter.js]
 [browser_computed_search-filter_clear.js]
 [browser_computed_search-filter_context-menu.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_computed_search-filter_escape-keypress.js]
 [browser_computed_search-filter_noproperties.js]
 [browser_computed_select-and-copy-styles.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_computed_style-editor-link.js]
--- a/devtools/client/inspector/markup/test/browser.ini
+++ b/devtools/client/inspector/markup/test/browser.ini
@@ -75,16 +75,17 @@ skip-if = os == "mac" # Full keyboard na
 [browser_markup_accessibility_semantics.js]
 [browser_markup_anonymous_01.js]
 [browser_markup_anonymous_02.js]
 skip-if = e10s # scratchpad.xul is not loading in e10s window
 [browser_markup_anonymous_03.js]
 [browser_markup_anonymous_04.js]
 [browser_markup_copy_image_data.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_markup_css_completion_style_attribute_01.js]
 [browser_markup_css_completion_style_attribute_02.js]
 [browser_markup_css_completion_style_attribute_03.js]
 [browser_markup_dragdrop_autoscroll_01.js]
 [browser_markup_dragdrop_autoscroll_02.js]
 [browser_markup_dragdrop_distance.js]
 [browser_markup_dragdrop_draggable.js]
 [browser_markup_dragdrop_dragRootNode.js]
@@ -113,16 +114,17 @@ skip-if = true # Bug 1177550
 [browser_markup_events_react_production_15.3.1.js]
 [browser_markup_events_react_production_15.3.1_jsx.js]
 [browser_markup_events-windowed-host.js]
 [browser_markup_links_01.js]
 [browser_markup_links_02.js]
 [browser_markup_links_03.js]
 [browser_markup_links_04.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_markup_links_05.js]
 [browser_markup_links_06.js]
 [browser_markup_links_07.js]
 [browser_markup_load_01.js]
 [browser_markup_html_edit_01.js]
 [browser_markup_html_edit_02.js]
 [browser_markup_html_edit_03.js]
 [browser_markup_image_tooltip.js]
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -85,16 +85,17 @@ support-files =
 [browser_rules_content_01.js]
 [browser_rules_content_02.js]
 skip-if = e10s && debug # Bug 1250058 - Docshell leak on debug e10s
 [browser_rules_context-menu-show-mdn-docs-01.js]
 [browser_rules_context-menu-show-mdn-docs-02.js]
 [browser_rules_context-menu-show-mdn-docs-03.js]
 [browser_rules_copy_styles.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_rules_cssom.js]
 [browser_rules_cubicbezier-appears-on-swatch-click.js]
 [browser_rules_cubicbezier-commit-on-ENTER.js]
 [browser_rules_cubicbezier-revert-on-ESC.js]
 [browser_rules_custom.js]
 [browser_rules_cycle-angle.js]
 [browser_rules_cycle-color.js]
 [browser_rules_edit-display-grid-property.js]
@@ -196,19 +197,21 @@ skip-if = (os == "win" && debug) # bug 9
 [browser_rules_search-filter_05.js]
 [browser_rules_search-filter_06.js]
 [browser_rules_search-filter_07.js]
 [browser_rules_search-filter_08.js]
 [browser_rules_search-filter_09.js]
 [browser_rules_search-filter_10.js]
 [browser_rules_search-filter_context-menu.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_rules_search-filter_escape-keypress.js]
 [browser_rules_select-and-copy-styles.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_rules_selector-highlighter-on-navigate.js]
 [browser_rules_selector-highlighter_01.js]
 [browser_rules_selector-highlighter_02.js]
 [browser_rules_selector-highlighter_03.js]
 [browser_rules_selector-highlighter_04.js]
 [browser_rules_selector_highlight.js]
 [browser_rules_strict-search-filter-computed-list_01.js]
 [browser_rules_strict-search-filter_01.js]
--- a/devtools/client/inspector/shared/test/browser.ini
+++ b/devtools/client/inspector/shared/test/browser.ini
@@ -17,18 +17,20 @@ support-files =
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_styleinspector_context-menu-copy-color_01.js]
 [browser_styleinspector_context-menu-copy-color_02.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_styleinspector_context-menu-copy-urls.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_styleinspector_csslogic-content-stylesheets.js]
 skip-if = e10s && debug # Bug 1250058 (docshell leak when opening 2 toolboxes)
 [browser_styleinspector_output-parser.js]
 [browser_styleinspector_refresh_when_active.js]
 [browser_styleinspector_tooltip-background-image.js]
 [browser_styleinspector_tooltip-closes-on-new-selection.js]
 skip-if = e10s # Bug 1111546 (e10s)
 [browser_styleinspector_tooltip-longhand-fontfamily.js]
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -72,16 +72,17 @@ skip-if = os == "mac" # Full keyboard na
 [browser_inspector_highlighter-cancel.js]
 [browser_inspector_highlighter-comments.js]
 [browser_inspector_highlighter-cssgrid_01.js]
 [browser_inspector_highlighter-csstransform_01.js]
 [browser_inspector_highlighter-csstransform_02.js]
 [browser_inspector_highlighter-embed.js]
 [browser_inspector_highlighter-eyedropper-clipboard.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_highlighter-eyedropper-csp.js]
 [browser_inspector_highlighter-eyedropper-events.js]
 [browser_inspector_highlighter-eyedropper-image.js]
 [browser_inspector_highlighter-eyedropper-label.js]
 [browser_inspector_highlighter-eyedropper-show-hide.js]
 [browser_inspector_highlighter-eyedropper-xul.js]
 [browser_inspector_highlighter-geometry_01.js]
 [browser_inspector_highlighter-geometry_02.js]
@@ -117,25 +118,30 @@ subsuite = clipboard
 [browser_inspector_infobar_03.js]
 [browser_inspector_infobar_textnode.js]
 [browser_inspector_initialization.js]
 skip-if = (e10s && debug) # Bug 1250058 - Docshell leak on debug e10s
 [browser_inspector_inspect-object-element.js]
 [browser_inspector_invalidate.js]
 [browser_inspector_keyboard-shortcuts-copy-outerhtml.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_keyboard-shortcuts.js]
 [browser_inspector_menu-01-sensitivity.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_menu-02-copy-items.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_menu-03-paste-items.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_menu-03-paste-items-svg.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_menu-04-use-in-console.js]
 [browser_inspector_menu-05-attribute-items.js]
 [browser_inspector_menu-06-other.js]
 [browser_inspector_navigation.js]
 [browser_inspector_navigate_to_errors.js]
 [browser_inspector_open_on_neterror.js]
 [browser_inspector_pane-toggle-01.js]
 [browser_inspector_pane-toggle-02.js]
@@ -156,16 +162,17 @@ skip-if = os == "mac" # Full keyboard na
 [browser_inspector_search-04.js]
 [browser_inspector_search-05.js]
 [browser_inspector_search-06.js]
 [browser_inspector_search-07.js]
 [browser_inspector_search-08.js]
 [browser_inspector_search-clear.js]
 [browser_inspector_search-filter_context-menu.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_search_keyboard_trap.js]
 [browser_inspector_search-label.js]
 [browser_inspector_search-reserved.js]
 [browser_inspector_search-selection.js]
 [browser_inspector_search-sidebar.js]
 [browser_inspector_select-docshell.js]
 [browser_inspector_select-last-selected.js]
 [browser_inspector_search-navigation.js]
--- a/devtools/client/jsonview/test/browser.ini
+++ b/devtools/client/jsonview/test/browser.ini
@@ -13,16 +13,19 @@ support-files =
   valid_json.json
   valid_json.json^headers^
   !/devtools/client/commandline/test/head.js
   !/devtools/client/framework/test/head.js
   !/devtools/client/framework/test/shared-head.js
 
 [browser_jsonview_copy_headers.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_copy_json.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_copy_rawdata.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_filter.js]
 [browser_jsonview_invalid_json.js]
 [browser_jsonview_valid_json.js]
 [browser_jsonview_save_json.js]
--- a/devtools/client/netmonitor/components/request-list-header.js
+++ b/devtools/client/netmonitor/components/request-list-header.js
@@ -9,24 +9,23 @@
 const { createClass, PropTypes, DOM } = require("devtools/client/shared/vendor/react");
 const { div, button } = DOM;
 const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { L10N } = require("../l10n");
 const { getWaterfallScale } = require("../selectors/index");
 const Actions = require("../actions/index");
 const WaterfallBackground = require("../waterfall-background");
+const { getFormattedTime } = require("../utils/format-utils");
 
 // ms
 const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5;
 // px
 const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60;
 
-const REQUEST_TIME_DECIMALS = 2;
-
 const HEADERS = [
   { name: "status", label: "status3" },
   { name: "method" },
   { name: "file", boxName: "icon-and-file" },
   { name: "domain", boxName: "security-and-domain" },
   { name: "cause" },
   { name: "type" },
   { name: "transferred" },
@@ -134,37 +133,26 @@ function waterfallDivisionLabels(waterfa
   // Ignore any divisions that would end up being too close to each other.
   while (scaledStep < REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN) {
     scaledStep *= 2;
   }
 
   // Insert one label for each division on the current scale.
   for (let x = 0; x < waterfallWidth; x += scaledStep) {
     let millisecondTime = x / scale;
-
-    let normalizedTime = millisecondTime;
     let divisionScale = "millisecond";
 
     // If the division is greater than 1 minute.
-    if (normalizedTime > 60000) {
-      normalizedTime /= 60000;
+    if (millisecondTime > 60000) {
       divisionScale = "minute";
-    } else if (normalizedTime > 1000) {
+    } else if (millisecondTime > 1000) {
       // If the division is greater than 1 second.
-      normalizedTime /= 1000;
       divisionScale = "second";
     }
 
-    // Showing too many decimals is bad UX.
-    if (divisionScale == "millisecond") {
-      normalizedTime |= 0;
-    } else {
-      normalizedTime = L10N.numberWithDecimals(normalizedTime, REQUEST_TIME_DECIMALS);
-    }
-
     let width = (x + scaledStep | 0) - (x | 0);
     // Adjust the first marker for the borders
     if (x == 0) {
       width -= 2;
     }
     // Last marker doesn't need a width specified at all
     if (x + scaledStep >= waterfallWidth) {
       width = undefined;
@@ -172,17 +160,17 @@ function waterfallDivisionLabels(waterfa
 
     labels.push(div(
       {
         key: labels.length,
         className: "requests-menu-timings-division",
         "data-division-scale": divisionScale,
         style: { width }
       },
-      L10N.getFormatStr("networkMenu." + divisionScale, normalizedTime)
+      getFormattedTime(millisecondTime)
     ));
   }
 
   return labels;
 }
 
 function WaterfallLabel(waterfallWidth, scale, label) {
   let className = "button-text requests-menu-waterfall-label-wrapper";
--- a/devtools/client/netmonitor/components/summary-button.js
+++ b/devtools/client/netmonitor/components/summary-button.js
@@ -1,37 +1,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {
-  CONTENT_SIZE_DECIMALS,
-  REQUEST_TIME_DECIMALS,
-} = require("../constants");
 const { DOM, PropTypes } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { PluralForm } = require("devtools/shared/plural-form");
 const { L10N } = require("../l10n");
 const { getDisplayedRequestsSummary } = require("../selectors/index");
 const Actions = require("../actions/index");
+const {
+  getSizeWithDecimals,
+  getTimeWithDecimals,
+} = require("../utils/format-utils");
 
 const { button, span } = DOM;
 
 function SummaryButton({
   summary,
   triggerSummary,
 }) {
   let { count, bytes, millis } = summary;
   const text = (count === 0) ? L10N.getStr("networkMenu.empty") :
     PluralForm.get(count, L10N.getStr("networkMenu.summary"))
     .replace("#1", count)
-    .replace("#2", L10N.numberWithDecimals(bytes / 1024, CONTENT_SIZE_DECIMALS))
-    .replace("#3", L10N.numberWithDecimals(millis / 1000, REQUEST_TIME_DECIMALS));
+    .replace("#2", getSizeWithDecimals(bytes / 1024))
+    .replace("#3", getTimeWithDecimals(millis / 1000));
 
   return button({
     id: "requests-menu-network-summary-button",
     className: "devtools-button",
     title: count ? text : L10N.getStr("netmonitor.toolbar.perf"),
     onClick: triggerSummary,
   },
   span({ className: "summary-info-icon" }),
--- a/devtools/client/netmonitor/constants.js
+++ b/devtools/client/netmonitor/constants.js
@@ -1,18 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const general = {
-  CONTENT_SIZE_DECIMALS: 2,
   FILTER_SEARCH_DELAY: 200,
-  REQUEST_TIME_DECIMALS: 2,
   // 100 KB in bytes
   SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE: 102400,
 };
 
 const actionTypes = {
   ADD_REQUEST: "ADD_REQUEST",
   ADD_TIMING_MARKER: "ADD_TIMING_MARKER",
   BATCH_ACTIONS: "BATCH_ACTIONS",
--- a/devtools/client/netmonitor/har/test/browser.ini
+++ b/devtools/client/netmonitor/har/test/browser.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 tags = devtools
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 support-files =
   head.js
   html_har_post-data-test-page.html
   !/devtools/client/netmonitor/test/head.js
   !/devtools/client/netmonitor/test/html_simple-test-page.html
 
 [browser_net_har_copy_all_as_har.js]
 [browser_net_har_post_data.js]
--- a/devtools/client/netmonitor/statistics-view.js
+++ b/devtools/client/netmonitor/statistics-view.js
@@ -11,19 +11,20 @@ const { PluralForm } = require("devtools
 const { Filters } = require("./filter-predicates");
 const { L10N } = require("./l10n");
 const { EVENTS } = require("./events");
 const { DOM } = require("devtools/client/shared/vendor/react");
 const { button } = DOM;
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const Actions = require("./actions/index");
 const { Chart } = require("devtools/client/shared/widgets/Chart");
-
-const REQUEST_TIME_DECIMALS = 2;
-const CONTENT_SIZE_DECIMALS = 2;
+const {
+  getSizeWithDecimals,
+  getTimeWithDecimals
+} = require("./utils/format-utils");
 
 // px
 const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200;
 
 /**
  * Functions handling the performance statistics view.
  */
 function StatisticsView() {
@@ -109,32 +110,32 @@ StatisticsView.prototype = {
   },
 
   /**
    * Common stringifier predicates used for items and totals in both the
    * "primed" and "empty" cache charts.
    */
   _commonChartStrings: {
     size: value => {
-      let string = L10N.numberWithDecimals(value / 1024, CONTENT_SIZE_DECIMALS);
+      let string = getSizeWithDecimals(value / 1024);
       return L10N.getFormatStr("charts.sizeKB", string);
     },
     time: value => {
-      let string = L10N.numberWithDecimals(value / 1000, REQUEST_TIME_DECIMALS);
+      let string = getTimeWithDecimals(value / 1000);
       return L10N.getFormatStr("charts.totalS", string);
     }
   },
   _commonChartTotals: {
     size: total => {
-      let string = L10N.numberWithDecimals(total / 1024, CONTENT_SIZE_DECIMALS);
+      let string = getSizeWithDecimals(total / 1024);
       return L10N.getFormatStr("charts.totalSize", string);
     },
     time: total => {
       let seconds = total / 1000;
-      let string = L10N.numberWithDecimals(seconds, REQUEST_TIME_DECIMALS);
+      let string = getTimeWithDecimals(seconds);
       return PluralForm.get(seconds,
         L10N.getStr("charts.totalSeconds")).replace("#1", string);
     },
     cached: total => {
       return L10N.getFormatStr("charts.totalCached", total);
     },
     count: total => {
       return L10N.getFormatStr("charts.totalCount", total);
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -68,28 +68,35 @@ skip-if = (toolkit == "cocoa" && e10s) #
 [browser_net_charts-07.js]
 [browser_net_clear.js]
 [browser_net_complex-params.js]
 [browser_net_content-type.js]
 [browser_net_brotli.js]
 [browser_net_curl-utils.js]
 [browser_net_copy_image_as_data_uri.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_copy_svg_image_as_data_uri.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_copy_url.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_copy_params.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_copy_response.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_copy_headers.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_copy_as_curl.js]
 subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_cors_requests.js]
 [browser_net_cyrillic-01.js]
 [browser_net_cyrillic-02.js]
 [browser_net_details-no-duplicated-content.js]
 skip-if = true # Test broken in React version, is too low-level
 [browser_net_frame.js]
 skip-if = (os == 'linux' && debug && bits == 32) # Bug 1321434
 [browser_net_filter-01.js]
--- a/devtools/client/netmonitor/utils/format-utils.js
+++ b/devtools/client/netmonitor/utils/format-utils.js
@@ -9,35 +9,64 @@ const { L10N } = require("../l10n");
 // Constants for formatting bytes.
 const BYTES_IN_KB = 1024;
 const BYTES_IN_MB = Math.pow(BYTES_IN_KB, 2);
 const BYTES_IN_GB = Math.pow(BYTES_IN_KB, 3);
 const MAX_BYTES_SIZE = 1000;
 const MAX_KB_SIZE = 1000 * BYTES_IN_KB;
 const MAX_MB_SIZE = 1000 * BYTES_IN_MB;
 
+// Constants for formatting time.
+const MAX_MILLISECOND = 1000;
+const MAX_SECOND = 60 * MAX_MILLISECOND;
+
+const CONTENT_SIZE_DECIMALS = 2;
+const REQUEST_TIME_DECIMALS = 2;
+
+function getSizeWithDecimals(size, decimals = REQUEST_TIME_DECIMALS) {
+  return L10N.numberWithDecimals(size, CONTENT_SIZE_DECIMALS);
+}
+
+function getTimeWithDecimals(time) {
+  return L10N.numberWithDecimals(time, REQUEST_TIME_DECIMALS);
+}
+
 /**
  * Get a human-readable string from a number of bytes, with the B, KB, MB, or
  * GB value. Note that the transition between abbreviations is by 1000 rather
  * than 1024 in order to keep the displayed digits smaller as "1016 KB" is
  * more awkward than 0.99 MB"
  */
-function getFormattedSize(bytes, decimals = 2) {
+function getFormattedSize(bytes, decimals = REQUEST_TIME_DECIMALS) {
   if (bytes < MAX_BYTES_SIZE) {
     return L10N.getFormatStr("networkMenu.sizeB", bytes);
   } else if (bytes < MAX_KB_SIZE) {
     let kb = bytes / BYTES_IN_KB;
-    let size = L10N.numberWithDecimals(kb, decimals);
-    return L10N.getFormatStr("networkMenu.sizeKB", size);
+    return L10N.getFormatStr("networkMenu.sizeKB", getSizeWithDecimals(kb, decimals));
   } else if (bytes < MAX_MB_SIZE) {
     let mb = bytes / BYTES_IN_MB;
-    let size = L10N.numberWithDecimals(mb, decimals);
-    return L10N.getFormatStr("networkMenu.sizeMB", size);
+    return L10N.getFormatStr("networkMenu.sizeMB", getSizeWithDecimals(mb, decimals));
   }
   let gb = bytes / BYTES_IN_GB;
-  let size = L10N.numberWithDecimals(gb, decimals);
-  return L10N.getFormatStr("networkMenu.sizeGB", size);
+  return L10N.getFormatStr("networkMenu.sizeGB", getSizeWithDecimals(gb, decimals));
+}
+
+/**
+ * Get a human-readable string from a number of time, with the ms, s, or min
+ * value.
+ */
+function getFormattedTime(ms) {
+  if (ms < MAX_MILLISECOND) {
+    return L10N.getFormatStr("networkMenu.millisecond", ms | 0);
+  } else if (ms < MAX_SECOND) {
+    let sec = ms / MAX_MILLISECOND;
+    return L10N.getFormatStr("networkMenu.second", getTimeWithDecimals(sec));
+  }
+  let min = ms / MAX_SECOND;
+  return L10N.getFormatStr("networkMenu.minute", getTimeWithDecimals(min));
 }
 
 module.exports = {
   getFormattedSize,
+  getFormattedTime,
+  getSizeWithDecimals,
+  getTimeWithDecimals,
 };
-
--- a/devtools/client/shared/vendor/REACT_REDUX_UPGRADING
+++ b/devtools/client/shared/vendor/REACT_REDUX_UPGRADING
@@ -1,12 +1,23 @@
 /* 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/. */
 
+Current version of react-redux: 5.0.1 (last upgrade in bug 1326137)
+
+How to upgrade:
+1. git clone https://github.com/reactjs/react-redux - clone the repo
+2. git checkout v5.0.1 - checkout the right version tag
+3. npm install - compile the sources to a JS module file
+4. cp dist/react-redux.js devtools/client/shared/vendor - copy the unminified JS file
+5. update the import path in the react-redux.js file - see below
+6. update the current version in this file
+
+UPDATING THE IMPORT PATHS
 
 "react-redux" uses UMD style loading to work in many different environments.
 It assumes that "react" and "redux" are both included via `require("react")`
 as in node or browserify, but the paths to our react and redux installation are different.
 
 If upgrading react-redux, define the correct paths and replace the require statements
 for the module.exports case with the correct paths.
 
--- a/devtools/client/shared/vendor/REDUX_UPGRADING
+++ b/devtools/client/shared/vendor/REDUX_UPGRADING
@@ -1,15 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-
-REDUX_UPGRADING
-
-Current version of redux : 3.3.0
+Current version of redux: 3.6.0 (last upgrade in bug 1326137)
 
-1 - grab the unminified version of redux on npm. For release 3.3.0 for instance,
-https://npmcdn.com/redux@3.3.0/dist/redux.js
-
-2 - replace the content of devtools/client/shared/vendor
-
-3 - update the current version in this file
\ No newline at end of file
+How to upgrade:
+1. git clone https://github.com/reactjs/redux - clone the repo
+2. git checkout v3.6.0 - checkout the right version tag
+3. npm install - compile the sources to a JS module file
+4. cp dist/redux.js devtools/client/shared/vendor - copy the unminified JS file
+5. update the current version in this file
--- a/devtools/client/shared/vendor/react-redux.js
+++ b/devtools/client/shared/vendor/react-redux.js
@@ -1,21 +1,21 @@
-var REACT_PATH = "devtools/client/shared/vendor/react";
-var REDUX_PATH = "devtools/client/shared/vendor/redux";
+const REACT_PATH = "devtools/client/shared/vendor/react";
+const REDUX_PATH = "devtools/client/shared/vendor/redux";
 
 (function webpackUniversalModuleDefinition(root, factory) {
 	if(typeof exports === 'object' && typeof module === 'object')
 		module.exports = factory(require(REACT_PATH), require(REDUX_PATH));
 	else if(typeof define === 'function' && define.amd)
 		define(["react", "redux"], factory);
 	else if(typeof exports === 'object')
-		exports["ReactRedux"] = factory(require("react"), require("redux"));
+		exports["ReactRedux"] = factory(require(REACT_PATH), require(REDUX_PATH));
 	else
 		root["ReactRedux"] = factory(root["React"], root["Redux"]);
-})(this, function(__WEBPACK_EXTERNAL_MODULE_10__, __WEBPACK_EXTERNAL_MODULE_11__) {
+})(this, function(__WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_22__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
 
 /******/ 	// The require function
 /******/ 	function __webpack_require__(moduleId) {
 
 /******/ 		// Check if module is in cache
@@ -55,563 +55,1115 @@ return /******/ (function(modules) { // 
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ function(module, exports, __webpack_require__) {
 
 	'use strict';
 
 	exports.__esModule = true;
+	exports.connect = exports.connectAdvanced = exports.Provider = undefined;
 
-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+	var _Provider = __webpack_require__(7);
 
-	var _react = __webpack_require__(10);
+	var _Provider2 = _interopRequireDefault(_Provider);
 
-	var _react2 = _interopRequireDefault(_react);
+	var _connectAdvanced = __webpack_require__(3);
 
-	var _componentsCreateAll = __webpack_require__(2);
+	var _connectAdvanced2 = _interopRequireDefault(_connectAdvanced);
 
-	var _componentsCreateAll2 = _interopRequireDefault(_componentsCreateAll);
+	var _connect = __webpack_require__(8);
 
-	var _createAll = _componentsCreateAll2['default'](_react2['default']);
+	var _connect2 = _interopRequireDefault(_connect);
 
-	var Provider = _createAll.Provider;
-	var connect = _createAll.connect;
-	exports.Provider = Provider;
-	exports.connect = connect;
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	exports.Provider = _Provider2.default;
+	exports.connectAdvanced = _connectAdvanced2.default;
+	exports.connect = _connect2.default;
 
 /***/ },
 /* 1 */
 /***/ function(module, exports) {
 
-	"use strict";
+	'use strict';
 
 	exports.__esModule = true;
-	exports["default"] = createStoreShape;
-
-	function createStoreShape(PropTypes) {
-	  return PropTypes.shape({
-	    subscribe: PropTypes.func.isRequired,
-	    dispatch: PropTypes.func.isRequired,
-	    getState: PropTypes.func.isRequired
-	  });
+	exports.default = warning;
+	/**
+	 * Prints a warning in the console if it exists.
+	 *
+	 * @param {String} message The warning message.
+	 * @returns {void}
+	 */
+	function warning(message) {
+	  /* eslint-disable no-console */
+	  if (typeof console !== 'undefined' && typeof console.error === 'function') {
+	    console.error(message);
+	  }
+	  /* eslint-enable no-console */
+	  try {
+	    // This error was thrown as a convenience so that if you enable
+	    // "break on all exceptions" in your console,
+	    // it would pause the execution at this line.
+	    throw new Error(message);
+	    /* eslint-disable no-empty */
+	  } catch (e) {}
+	  /* eslint-enable no-empty */
 	}
 
-	module.exports = exports["default"];
-
 /***/ },
 /* 2 */
-/***/ function(module, exports, __webpack_require__) {
-
-	'use strict';
-
-	exports.__esModule = true;
-	exports['default'] = createAll;
-
-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
-	var _createProvider = __webpack_require__(4);
-
-	var _createProvider2 = _interopRequireDefault(_createProvider);
+/***/ function(module, exports) {
 
-	var _createConnect = __webpack_require__(3);
-
-	var _createConnect2 = _interopRequireDefault(_createConnect);
-
-	function createAll(React) {
-	  var Provider = _createProvider2['default'](React);
-	  var connect = _createConnect2['default'](React);
-
-	  return { Provider: Provider, connect: connect };
-	}
-
-	module.exports = exports['default'];
+	module.exports = __WEBPACK_EXTERNAL_MODULE_2__;
 
 /***/ },
 /* 3 */
 /***/ function(module, exports, __webpack_require__) {
 
 	'use strict';
 
 	exports.__esModule = true;
 
 	var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
-	exports['default'] = createConnect;
-
-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
-	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
-
-	function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
-
-	var _utilsCreateStoreShape = __webpack_require__(1);
-
-	var _utilsCreateStoreShape2 = _interopRequireDefault(_utilsCreateStoreShape);
+	exports.default = connectAdvanced;
 
-	var _utilsShallowEqual = __webpack_require__(6);
-
-	var _utilsShallowEqual2 = _interopRequireDefault(_utilsShallowEqual);
-
-	var _utilsIsPlainObject = __webpack_require__(5);
-
-	var _utilsIsPlainObject2 = _interopRequireDefault(_utilsIsPlainObject);
-
-	var _utilsWrapActionCreators = __webpack_require__(7);
-
-	var _utilsWrapActionCreators2 = _interopRequireDefault(_utilsWrapActionCreators);
-
-	var _hoistNonReactStatics = __webpack_require__(8);
+	var _hoistNonReactStatics = __webpack_require__(16);
 
 	var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics);
 
-	var _invariant = __webpack_require__(9);
+	var _invariant = __webpack_require__(17);
 
 	var _invariant2 = _interopRequireDefault(_invariant);
 
-	var defaultMapStateToProps = function defaultMapStateToProps() {
-	  return {};
-	};
-	var defaultMapDispatchToProps = function defaultMapDispatchToProps(dispatch) {
-	  return { dispatch: dispatch };
-	};
-	var defaultMergeProps = function defaultMergeProps(stateProps, dispatchProps, parentProps) {
-	  return _extends({}, parentProps, stateProps, dispatchProps);
-	};
+	var _react = __webpack_require__(2);
+
+	var _Subscription = __webpack_require__(14);
+
+	var _Subscription2 = _interopRequireDefault(_Subscription);
+
+	var _storeShape = __webpack_require__(5);
+
+	var _storeShape2 = _interopRequireDefault(_storeShape);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+	function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
+
+	var hotReloadingVersion = 0;
+	function connectAdvanced(
+	/*
+	  selectorFactory is a func that is responsible for returning the selector function used to
+	  compute new props from state, props, and dispatch. For example:
+	     export default connectAdvanced((dispatch, options) => (state, props) => ({
+	      thing: state.things[props.thingId],
+	      saveThing: fields => dispatch(actionCreators.saveThing(props.thingId, fields)),
+	    }))(YourComponent)
+	   Access to dispatch is provided to the factory so selectorFactories can bind actionCreators
+	  outside of their selector as an optimization. Options passed to connectAdvanced are passed to
+	  the selectorFactory, along with displayName and WrappedComponent, as the second argument.
+	   Note that selectorFactory is responsible for all caching/memoization of inbound and outbound
+	  props. Do not use connectAdvanced directly without memoizing results between calls to your
+	  selector, otherwise the Connect component will re-render on every state or props change.
+	*/
+	selectorFactory) {
+	  var _contextTypes, _childContextTypes;
+
+	  var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+	  var _ref$getDisplayName = _ref.getDisplayName;
+	  var
+	  // the func used to compute this HOC's displayName from the wrapped component's displayName.
+	  // probably overridden by wrapper functions such as connect()
+	  getDisplayName = _ref$getDisplayName === undefined ? function (name) {
+	    return 'ConnectAdvanced(' + name + ')';
+	  } : _ref$getDisplayName;
+	  var _ref$methodName = _ref.methodName;
+	  var
+
+	  // shown in error messages
+	  // probably overridden by wrapper functions such as connect()
+	  methodName = _ref$methodName === undefined ? 'connectAdvanced' : _ref$methodName;
+	  var _ref$renderCountProp = _ref.renderCountProp;
+	  var
+
+	  // if defined, the name of the property passed to the wrapped element indicating the number of
+	  // calls to render. useful for watching in react devtools for unnecessary re-renders.
+	  renderCountProp = _ref$renderCountProp === undefined ? undefined : _ref$renderCountProp;
+	  var _ref$shouldHandleStat = _ref.shouldHandleStateChanges;
+	  var
+
+	  // determines whether this HOC subscribes to store changes
+	  shouldHandleStateChanges = _ref$shouldHandleStat === undefined ? true : _ref$shouldHandleStat;
+	  var _ref$storeKey = _ref.storeKey;
+	  var
 
-	function getDisplayName(Component) {
-	  return Component.displayName || Component.name || 'Component';
-	}
+	  // the key of props/context to get the store
+	  storeKey = _ref$storeKey === undefined ? 'store' : _ref$storeKey;
+	  var _ref$withRef = _ref.withRef;
+	  var
+
+	  // if true, the wrapped element is exposed by this HOC via the getWrappedInstance() function.
+	  withRef = _ref$withRef === undefined ? false : _ref$withRef;
+
+	  var connectOptions = _objectWithoutProperties(_ref, ['getDisplayName', 'methodName', 'renderCountProp', 'shouldHandleStateChanges', 'storeKey', 'withRef']);
+
+	  var subscriptionKey = storeKey + 'Subscription';
+	  var version = hotReloadingVersion++;
+
+	  var contextTypes = (_contextTypes = {}, _contextTypes[storeKey] = _storeShape2.default, _contextTypes[subscriptionKey] = _react.PropTypes.instanceOf(_Subscription2.default), _contextTypes);
+	  var childContextTypes = (_childContextTypes = {}, _childContextTypes[subscriptionKey] = _react.PropTypes.instanceOf(_Subscription2.default), _childContextTypes);
 
-	// Helps track hot reloading.
-	var nextVersion = 0;
+	  return function wrapWithConnect(WrappedComponent) {
+	    (0, _invariant2.default)(typeof WrappedComponent == 'function', 'You must pass a component to the function returned by ' + ('connect. Instead received ' + WrappedComponent));
+
+	    var wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
+
+	    var displayName = getDisplayName(wrappedComponentName);
+
+	    var selectorFactoryOptions = _extends({}, connectOptions, {
+	      getDisplayName: getDisplayName,
+	      methodName: methodName,
+	      renderCountProp: renderCountProp,
+	      shouldHandleStateChanges: shouldHandleStateChanges,
+	      storeKey: storeKey,
+	      withRef: withRef,
+	      displayName: displayName,
+	      wrappedComponentName: wrappedComponentName,
+	      WrappedComponent: WrappedComponent
+	    });
 
-	function createConnect(React) {
-	  var Component = React.Component;
-	  var PropTypes = React.PropTypes;
+	    var Connect = function (_Component) {
+	      _inherits(Connect, _Component);
+
+	      function Connect(props, context) {
+	        _classCallCheck(this, Connect);
+
+	        var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));
+
+	        _this.version = version;
+	        _this.state = {};
+	        _this.renderCount = 0;
+	        _this.store = _this.props[storeKey] || _this.context[storeKey];
+	        _this.parentSub = props[subscriptionKey] || context[subscriptionKey];
+
+	        _this.setWrappedInstance = _this.setWrappedInstance.bind(_this);
+
+	        (0, _invariant2.default)(_this.store, 'Could not find "' + storeKey + '" in either the context or ' + ('props of "' + displayName + '". ') + 'Either wrap the root component in a <Provider>, ' + ('or explicitly pass "' + storeKey + '" as a prop to "' + displayName + '".'));
 
-	  var storeShape = _utilsCreateStoreShape2['default'](PropTypes);
+	        // make sure `getState` is properly bound in order to avoid breaking
+	        // custom store implementations that rely on the store's context
+	        _this.getState = _this.store.getState.bind(_this.store);
+
+	        _this.initSelector();
+	        _this.initSubscription();
+	        return _this;
+	      }
 
-	  return function connect(mapStateToProps, mapDispatchToProps, mergeProps) {
-	    var options = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3];
+	      Connect.prototype.getChildContext = function getChildContext() {
+	        var _ref2;
+
+	        return _ref2 = {}, _ref2[subscriptionKey] = this.subscription, _ref2;
+	      };
+
+	      Connect.prototype.componentDidMount = function componentDidMount() {
+	        if (!shouldHandleStateChanges) return;
 
-	    var shouldSubscribe = Boolean(mapStateToProps);
-	    var finalMapStateToProps = mapStateToProps || defaultMapStateToProps;
-	    var finalMapDispatchToProps = _utilsIsPlainObject2['default'](mapDispatchToProps) ? _utilsWrapActionCreators2['default'](mapDispatchToProps) : mapDispatchToProps || defaultMapDispatchToProps;
-	    var finalMergeProps = mergeProps || defaultMergeProps;
-	    var shouldUpdateStateProps = finalMapStateToProps.length > 1;
-	    var shouldUpdateDispatchProps = finalMapDispatchToProps.length > 1;
-	    var _options$pure = options.pure;
-	    var pure = _options$pure === undefined ? true : _options$pure;
+	        // componentWillMount fires during server side rendering, but componentDidMount and
+	        // componentWillUnmount do not. Because of this, trySubscribe happens during ...didMount.
+	        // Otherwise, unsubscription would never take place during SSR, causing a memory leak.
+	        // To handle the case where a child component may have triggered a state change by
+	        // dispatching an action in its componentWillMount, we have to re-run the select and maybe
+	        // re-render.
+	        this.subscription.trySubscribe();
+	        this.selector.run(this.props);
+	        if (this.selector.shouldComponentUpdate) this.forceUpdate();
+	      };
+
+	      Connect.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
+	        this.selector.run(nextProps);
+	      };
+
+	      Connect.prototype.shouldComponentUpdate = function shouldComponentUpdate() {
+	        return this.selector.shouldComponentUpdate;
+	      };
+
+	      Connect.prototype.componentWillUnmount = function componentWillUnmount() {
+	        if (this.subscription) this.subscription.tryUnsubscribe();
+	        // these are just to guard against extra memory leakage if a parent element doesn't
+	        // dereference this instance properly, such as an async callback that never finishes
+	        this.subscription = null;
+	        this.store = null;
+	        this.parentSub = null;
+	        this.selector.run = function () {};
+	      };
 
-	    // Helps track hot reloading.
-	    var version = nextVersion++;
+	      Connect.prototype.getWrappedInstance = function getWrappedInstance() {
+	        (0, _invariant2.default)(withRef, 'To access the wrapped instance, you need to specify ' + ('{ withRef: true } in the options argument of the ' + methodName + '() call.'));
+	        return this.wrappedInstance;
+	      };
+
+	      Connect.prototype.setWrappedInstance = function setWrappedInstance(ref) {
+	        this.wrappedInstance = ref;
+	      };
+
+	      Connect.prototype.initSelector = function initSelector() {
+	        var dispatch = this.store.dispatch;
+	        var getState = this.getState;
+
+	        var sourceSelector = selectorFactory(dispatch, selectorFactoryOptions);
 
-	    function computeStateProps(store, props) {
-	      var state = store.getState();
-	      var stateProps = shouldUpdateStateProps ? finalMapStateToProps(state, props) : finalMapStateToProps(state);
+	        // wrap the selector in an object that tracks its results between runs
+	        var selector = this.selector = {
+	          shouldComponentUpdate: true,
+	          props: sourceSelector(getState(), this.props),
+	          run: function runComponentSelector(props) {
+	            try {
+	              var nextProps = sourceSelector(getState(), props);
+	              if (selector.error || nextProps !== selector.props) {
+	                selector.shouldComponentUpdate = true;
+	                selector.props = nextProps;
+	                selector.error = null;
+	              }
+	            } catch (error) {
+	              selector.shouldComponentUpdate = true;
+	              selector.error = error;
+	            }
+	          }
+	        };
+	      };
+
+	      Connect.prototype.initSubscription = function initSubscription() {
+	        var _this2 = this;
 
-	      _invariant2['default'](_utilsIsPlainObject2['default'](stateProps), '`mapStateToProps` must return an object. Instead received %s.', stateProps);
-	      return stateProps;
-	    }
+	        if (shouldHandleStateChanges) {
+	          (function () {
+	            var subscription = _this2.subscription = new _Subscription2.default(_this2.store, _this2.parentSub);
+	            var dummyState = {};
+
+	            subscription.onStateChange = function onStateChange() {
+	              this.selector.run(this.props);
 
-	    function computeDispatchProps(store, props) {
-	      var dispatch = store.dispatch;
+	              if (!this.selector.shouldComponentUpdate) {
+	                subscription.notifyNestedSubs();
+	              } else {
+	                this.componentDidUpdate = function componentDidUpdate() {
+	                  this.componentDidUpdate = undefined;
+	                  subscription.notifyNestedSubs();
+	                };
 
-	      var dispatchProps = shouldUpdateDispatchProps ? finalMapDispatchToProps(dispatch, props) : finalMapDispatchToProps(dispatch);
+	                this.setState(dummyState);
+	              }
+	            }.bind(_this2);
+	          })();
+	        }
+	      };
+
+	      Connect.prototype.isSubscribed = function isSubscribed() {
+	        return Boolean(this.subscription) && this.subscription.isSubscribed();
+	      };
 
-	      _invariant2['default'](_utilsIsPlainObject2['default'](dispatchProps), '`mapDispatchToProps` must return an object. Instead received %s.', dispatchProps);
-	      return dispatchProps;
-	    }
+	      Connect.prototype.addExtraProps = function addExtraProps(props) {
+	        if (!withRef && !renderCountProp) return props;
+	        // make a shallow copy so that fields added don't leak to the original selector.
+	        // this is especially important for 'ref' since that's a reference back to the component
+	        // instance. a singleton memoized selector would then be holding a reference to the
+	        // instance, preventing the instance from being garbage collected, and that would be bad
+	        var withExtras = _extends({}, props);
+	        if (withRef) withExtras.ref = this.setWrappedInstance;
+	        if (renderCountProp) withExtras[renderCountProp] = this.renderCount++;
+	        return withExtras;
+	      };
+
+	      Connect.prototype.render = function render() {
+	        var selector = this.selector;
+	        selector.shouldComponentUpdate = false;
 
-	    function _computeNextState(stateProps, dispatchProps, parentProps) {
-	      var mergedProps = finalMergeProps(stateProps, dispatchProps, parentProps);
-	      _invariant2['default'](_utilsIsPlainObject2['default'](mergedProps), '`mergeProps` must return an object. Instead received %s.', mergedProps);
-	      return mergedProps;
+	        if (selector.error) {
+	          throw selector.error;
+	        } else {
+	          return (0, _react.createElement)(WrappedComponent, this.addExtraProps(selector.props));
+	        }
+	      };
+
+	      return Connect;
+	    }(_react.Component);
+
+	    Connect.WrappedComponent = WrappedComponent;
+	    Connect.displayName = displayName;
+	    Connect.childContextTypes = childContextTypes;
+	    Connect.contextTypes = contextTypes;
+	    Connect.propTypes = contextTypes;
+
+	    if (true) {
+	      Connect.prototype.componentWillUpdate = function componentWillUpdate() {
+	        // We are hot reloading!
+	        if (this.version !== version) {
+	          this.version = version;
+	          this.initSelector();
+
+	          if (this.subscription) this.subscription.tryUnsubscribe();
+	          this.initSubscription();
+	          if (shouldHandleStateChanges) this.subscription.trySubscribe();
+	        }
+	      };
 	    }
 
-	    return function wrapWithConnect(WrappedComponent) {
-	      var Connect = (function (_Component) {
-	        _inherits(Connect, _Component);
-
-	        Connect.prototype.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) {
-	          if (!pure) {
-	            this.updateStateProps(nextProps);
-	            this.updateDispatchProps(nextProps);
-	            this.updateState(nextProps);
-	            return true;
-	          }
-
-	          var storeChanged = nextState.storeState !== this.state.storeState;
-	          var propsChanged = !_utilsShallowEqual2['default'](nextProps, this.props);
-	          var mapStateProducedChange = false;
-	          var dispatchPropsChanged = false;
-
-	          if (storeChanged || propsChanged && shouldUpdateStateProps) {
-	            mapStateProducedChange = this.updateStateProps(nextProps);
-	          }
-
-	          if (propsChanged && shouldUpdateDispatchProps) {
-	            dispatchPropsChanged = this.updateDispatchProps(nextProps);
-	          }
-
-	          if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
-	            this.updateState(nextProps);
-	            return true;
-	          }
-
-	          return false;
-	        };
-
-	        function Connect(props, context) {
-	          _classCallCheck(this, Connect);
-
-	          _Component.call(this, props, context);
-	          this.version = version;
-	          this.store = props.store || context.store;
-
-	          _invariant2['default'](this.store, 'Could not find "store" in either the context or ' + ('props of "' + this.constructor.displayName + '". ') + 'Either wrap the root component in a <Provider>, ' + ('or explicitly pass "store" as a prop to "' + this.constructor.displayName + '".'));
-
-	          this.stateProps = computeStateProps(this.store, props);
-	          this.dispatchProps = computeDispatchProps(this.store, props);
-	          this.state = { storeState: null };
-	          this.updateState();
-	        }
-
-	        Connect.prototype.computeNextState = function computeNextState() {
-	          var props = arguments.length <= 0 || arguments[0] === undefined ? this.props : arguments[0];
-
-	          return _computeNextState(this.stateProps, this.dispatchProps, props);
-	        };
-
-	        Connect.prototype.updateStateProps = function updateStateProps() {
-	          var props = arguments.length <= 0 || arguments[0] === undefined ? this.props : arguments[0];
-
-	          var nextStateProps = computeStateProps(this.store, props);
-	          if (_utilsShallowEqual2['default'](nextStateProps, this.stateProps)) {
-	            return false;
-	          }
-
-	          this.stateProps = nextStateProps;
-	          return true;
-	        };
-
-	        Connect.prototype.updateDispatchProps = function updateDispatchProps() {
-	          var props = arguments.length <= 0 || arguments[0] === undefined ? this.props : arguments[0];
-
-	          var nextDispatchProps = computeDispatchProps(this.store, props);
-	          if (_utilsShallowEqual2['default'](nextDispatchProps, this.dispatchProps)) {
-	            return false;
-	          }
-
-	          this.dispatchProps = nextDispatchProps;
-	          return true;
-	        };
-
-	        Connect.prototype.updateState = function updateState() {
-	          var props = arguments.length <= 0 || arguments[0] === undefined ? this.props : arguments[0];
-
-	          this.nextState = this.computeNextState(props);
-	        };
-
-	        Connect.prototype.isSubscribed = function isSubscribed() {
-	          return typeof this.unsubscribe === 'function';
-	        };
-
-	        Connect.prototype.trySubscribe = function trySubscribe() {
-	          if (shouldSubscribe && !this.unsubscribe) {
-	            this.unsubscribe = this.store.subscribe(this.handleChange.bind(this));
-	            this.handleChange();
-	          }
-	        };
-
-	        Connect.prototype.tryUnsubscribe = function tryUnsubscribe() {
-	          if (this.unsubscribe) {
-	            this.unsubscribe();
-	            this.unsubscribe = null;
-	          }
-	        };
-
-	        Connect.prototype.componentDidMount = function componentDidMount() {
-	          this.trySubscribe();
-	        };
-
-	        Connect.prototype.componentWillUnmount = function componentWillUnmount() {
-	          this.tryUnsubscribe();
-	        };
-
-	        Connect.prototype.handleChange = function handleChange() {
-	          if (!this.unsubscribe) {
-	            return;
-	          }
-
-	          this.setState({
-	            storeState: this.store.getState()
-	          });
-	        };
-
-	        Connect.prototype.getWrappedInstance = function getWrappedInstance() {
-	          return this.refs.wrappedInstance;
-	        };
-
-	        Connect.prototype.render = function render() {
-	          return React.createElement(WrappedComponent, _extends({ ref: 'wrappedInstance'
-	          }, this.nextState));
-	        };
-
-	        return Connect;
-	      })(Component);
-
-	      Connect.displayName = 'Connect(' + getDisplayName(WrappedComponent) + ')';
-	      Connect.WrappedComponent = WrappedComponent;
-	      Connect.contextTypes = {
-	        store: storeShape
-	      };
-	      Connect.propTypes = {
-	        store: storeShape
-	      };
-
-	      if (true) {
-	        Connect.prototype.componentWillUpdate = function componentWillUpdate() {
-	          if (this.version === version) {
-	            return;
-	          }
-
-	          // We are hot reloading!
-	          this.version = version;
-
-	          // Update the state and bindings.
-	          this.trySubscribe();
-	          this.updateStateProps();
-	          this.updateDispatchProps();
-	          this.updateState();
-	        };
-	      }
-
-	      return _hoistNonReactStatics2['default'](Connect, WrappedComponent);
-	    };
+	    return (0, _hoistNonReactStatics2.default)(Connect, WrappedComponent);
 	  };
 	}
 
-	module.exports = exports['default'];
-
 /***/ },
 /* 4 */
 /***/ function(module, exports, __webpack_require__) {
 
 	'use strict';
 
 	exports.__esModule = true;
-	exports['default'] = createProvider;
+	exports.wrapMapToPropsConstant = wrapMapToPropsConstant;
+	exports.getDependsOnOwnProps = getDependsOnOwnProps;
+	exports.wrapMapToPropsFunc = wrapMapToPropsFunc;
 
-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
-	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+	var _verifyPlainObject = __webpack_require__(6);
 
-	function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+	var _verifyPlainObject2 = _interopRequireDefault(_verifyPlainObject);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-	var _utilsCreateStoreShape = __webpack_require__(1);
-
-	var _utilsCreateStoreShape2 = _interopRequireDefault(_utilsCreateStoreShape);
+	function wrapMapToPropsConstant(getConstant) {
+	  return function initConstantSelector(dispatch, options) {
+	    var constant = getConstant(dispatch, options);
 
-	function isUsingOwnerContext(React) {
-	  var version = React.version;
+	    function constantSelector() {
+	      return constant;
+	    }
+	    constantSelector.dependsOnOwnProps = false;
+	    return constantSelector;
+	  };
+	}
 
-	  if (typeof version !== 'string') {
-	    return true;
-	  }
-
-	  var sections = version.split('.');
-	  var major = parseInt(sections[0], 10);
-	  var minor = parseInt(sections[1], 10);
-
-	  return major === 0 && minor === 13;
+	// dependsOnOwnProps is used by createMapToPropsProxy to determine whether to pass props as args
+	// to the mapToProps function being wrapped. It is also used by makePurePropsSelector to determine
+	// whether mapToProps needs to be invoked when props have changed.
+	//
+	// A length of one signals that mapToProps does not depend on props from the parent component.
+	// A length of zero is assumed to mean mapToProps is getting args via arguments or ...args and
+	// therefore not reporting its length accurately..
+	function getDependsOnOwnProps(mapToProps) {
+	  return mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined ? Boolean(mapToProps.dependsOnOwnProps) : mapToProps.length !== 1;
 	}
 
-	function createProvider(React) {
-	  var Component = React.Component;
-	  var PropTypes = React.PropTypes;
-	  var Children = React.Children;
-
-	  var storeShape = _utilsCreateStoreShape2['default'](PropTypes);
-	  var requireFunctionChild = isUsingOwnerContext(React);
-
-	  var didWarnAboutChild = false;
-	  function warnAboutFunctionChild() {
-	    if (didWarnAboutChild || requireFunctionChild) {
-	      return;
-	    }
+	// Used by whenMapStateToPropsIsFunction and whenMapDispatchToPropsIsFunction,
+	// this function wraps mapToProps in a proxy function which does several things:
+	//
+	//  * Detects whether the mapToProps function being called depends on props, which
+	//    is used by selectorFactory to decide if it should reinvoke on props changes.
+	//
+	//  * On first call, handles mapToProps if returns another function, and treats that
+	//    new function as the true mapToProps for subsequent calls.
+	//
+	//  * On first call, verifies the first result is a plain object, in order to warn
+	//    the developer that their mapToProps function is not returning a valid result.
+	//
+	function wrapMapToPropsFunc(mapToProps, methodName) {
+	  return function initProxySelector(dispatch, _ref) {
+	    var displayName = _ref.displayName;
 
-	    didWarnAboutChild = true;
-	    console.error( // eslint-disable-line no-console
-	    'With React 0.14 and later versions, you no longer need to ' + 'wrap <Provider> child into a function.');
-	  }
-	  function warnAboutElementChild() {
-	    if (didWarnAboutChild || !requireFunctionChild) {
-	      return;
-	    }
-
-	    didWarnAboutChild = true;
-	    console.error( // eslint-disable-line no-console
-	    'With React 0.13, you need to ' + 'wrap <Provider> child into a function. ' + 'This restriction will be removed with React 0.14.');
-	  }
-
-	  var didWarnAboutReceivingStore = false;
-	  function warnAboutReceivingStore() {
-	    if (didWarnAboutReceivingStore) {
-	      return;
-	    }
-
-	    didWarnAboutReceivingStore = true;
-	    console.error( // eslint-disable-line no-console
-	    '<Provider> does not support changing `store` on the fly. ' + 'It is most likely that you see this error because you updated to ' + 'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' + 'automatically. See https://github.com/rackt/react-redux/releases/' + 'tag/v2.0.0 for the migration instructions.');
-	  }
-
-	  var Provider = (function (_Component) {
-	    _inherits(Provider, _Component);
-
-	    Provider.prototype.getChildContext = function getChildContext() {
-	      return { store: this.store };
+	    var proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
+	      return proxy.dependsOnOwnProps ? proxy.mapToProps(stateOrDispatch, ownProps) : proxy.mapToProps(stateOrDispatch);
 	    };
 
-	    function Provider(props, context) {
-	      _classCallCheck(this, Provider);
+	    proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps);
 
-	      _Component.call(this, props, context);
-	      this.store = props.store;
-	    }
+	    proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
+	      proxy.mapToProps = mapToProps;
+	      var props = proxy(stateOrDispatch, ownProps);
 
-	    Provider.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
-	      var store = this.store;
-	      var nextStore = nextProps.store;
+	      if (typeof props === 'function') {
+	        proxy.mapToProps = props;
+	        proxy.dependsOnOwnProps = getDependsOnOwnProps(props);
+	        props = proxy(stateOrDispatch, ownProps);
+	      }
 
-	      if (store !== nextStore) {
-	        warnAboutReceivingStore();
-	      }
+	      if (true) (0, _verifyPlainObject2.default)(props, displayName, methodName);
+
+	      return props;
 	    };
 
-	    Provider.prototype.render = function render() {
-	      var children = this.props.children;
-
-	      if (typeof children === 'function') {
-	        warnAboutFunctionChild();
-	        children = children();
-	      } else {
-	        warnAboutElementChild();
-	      }
-
-	      return Children.only(children);
-	    };
-
-	    return Provider;
-	  })(Component);
-
-	  Provider.childContextTypes = {
-	    store: storeShape.isRequired
+	    return proxy;
 	  };
-	  Provider.propTypes = {
-	    store: storeShape.isRequired,
-	    children: (requireFunctionChild ? PropTypes.func : PropTypes.element).isRequired
-	  };
-
-	  return Provider;
 	}
 
-	module.exports = exports['default'];
-
 /***/ },
 /* 5 */
-/***/ function(module, exports) {
+/***/ function(module, exports, __webpack_require__) {
 
 	'use strict';
 
 	exports.__esModule = true;
-	exports['default'] = isPlainObject;
-	var fnToString = function fnToString(fn) {
-	  return Function.prototype.toString.call(fn);
-	};
 
-	/**
-	 * @param {any} obj The object to inspect.
-	 * @returns {boolean} True if the argument appears to be a plain object.
-	 */
+	var _react = __webpack_require__(2);
 
-	function isPlainObject(obj) {
-	  if (!obj || typeof obj !== 'object') {
-	    return false;
-	  }
-
-	  var proto = typeof obj.constructor === 'function' ? Object.getPrototypeOf(obj) : Object.prototype;
-
-	  if (proto === null) {
-	    return true;
-	  }
-
-	  var constructor = proto.constructor;
-
-	  return typeof constructor === 'function' && constructor instanceof constructor && fnToString(constructor) === fnToString(Object);
-	}
-
-	module.exports = exports['default'];
+	exports.default = _react.PropTypes.shape({
+	  subscribe: _react.PropTypes.func.isRequired,
+	  dispatch: _react.PropTypes.func.isRequired,
+	  getState: _react.PropTypes.func.isRequired
+	});
 
 /***/ },
 /* 6 */
-/***/ function(module, exports) {
+/***/ function(module, exports, __webpack_require__) {
 
-	"use strict";
+	'use strict';
 
 	exports.__esModule = true;
-	exports["default"] = shallowEqual;
+	exports.default = verifyPlainObject;
+
+	var _isPlainObject = __webpack_require__(21);
+
+	var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
+
+	var _warning = __webpack_require__(1);
 
-	function shallowEqual(objA, objB) {
-	  if (objA === objB) {
-	    return true;
-	  }
+	var _warning2 = _interopRequireDefault(_warning);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-	  var keysA = Object.keys(objA);
-	  var keysB = Object.keys(objB);
-
-	  if (keysA.length !== keysB.length) {
-	    return false;
+	function verifyPlainObject(value, displayName, methodName) {
+	  if (!(0, _isPlainObject2.default)(value)) {
+	    (0, _warning2.default)(methodName + '() in ' + displayName + ' must return a plain object. Instead received ' + value + '.');
 	  }
-
-	  // Test for A's keys different from B.
-	  var hasOwn = Object.prototype.hasOwnProperty;
-	  for (var i = 0; i < keysA.length; i++) {
-	    if (!hasOwn.call(objB, keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
-	      return false;
-	    }
-	  }
-
-	  return true;
 	}
 
-	module.exports = exports["default"];
-
 /***/ },
 /* 7 */
 /***/ function(module, exports, __webpack_require__) {
 
 	'use strict';
 
 	exports.__esModule = true;
-	exports['default'] = wrapActionCreators;
+	exports.default = undefined;
+
+	var _react = __webpack_require__(2);
+
+	var _storeShape = __webpack_require__(5);
+
+	var _storeShape2 = _interopRequireDefault(_storeShape);
+
+	var _warning = __webpack_require__(1);
+
+	var _warning2 = _interopRequireDefault(_warning);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+	var didWarnAboutReceivingStore = false;
+	function warnAboutReceivingStore() {
+	  if (didWarnAboutReceivingStore) {
+	    return;
+	  }
+	  didWarnAboutReceivingStore = true;
+
+	  (0, _warning2.default)('<Provider> does not support changing `store` on the fly. ' + 'It is most likely that you see this error because you updated to ' + 'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' + 'automatically. See https://github.com/reactjs/react-redux/releases/' + 'tag/v2.0.0 for the migration instructions.');
+	}
+
+	var Provider = function (_Component) {
+	  _inherits(Provider, _Component);
+
+	  Provider.prototype.getChildContext = function getChildContext() {
+	    return { store: this.store };
+	  };
+
+	  function Provider(props, context) {
+	    _classCallCheck(this, Provider);
+
+	    var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));
+
+	    _this.store = props.store;
+	    return _this;
+	  }
+
+	  Provider.prototype.render = function render() {
+	    return _react.Children.only(this.props.children);
+	  };
 
-	var _redux = __webpack_require__(11);
+	  return Provider;
+	}(_react.Component);
+
+	exports.default = Provider;
+
+
+	if (true) {
+	  Provider.prototype.componentWillReceiveProps = function (nextProps) {
+	    var store = this.store;
+	    var nextStore = nextProps.store;
+
+
+	    if (store !== nextStore) {
+	      warnAboutReceivingStore();
+	    }
+	  };
+	}
+
+	Provider.propTypes = {
+	  store: _storeShape2.default.isRequired,
+	  children: _react.PropTypes.element.isRequired
+	};
+	Provider.childContextTypes = {
+	  store: _storeShape2.default.isRequired
+	};
+	Provider.displayName = 'Provider';
+
+/***/ },
+/* 8 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+
+	var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+	exports.createConnect = createConnect;
+
+	var _connectAdvanced = __webpack_require__(3);
+
+	var _connectAdvanced2 = _interopRequireDefault(_connectAdvanced);
+
+	var _shallowEqual = __webpack_require__(15);
+
+	var _shallowEqual2 = _interopRequireDefault(_shallowEqual);
+
+	var _mapDispatchToProps = __webpack_require__(9);
+
+	var _mapDispatchToProps2 = _interopRequireDefault(_mapDispatchToProps);
+
+	var _mapStateToProps = __webpack_require__(10);
 
-	function wrapActionCreators(actionCreators) {
-	  return function (dispatch) {
-	    return _redux.bindActionCreators(actionCreators, dispatch);
+	var _mapStateToProps2 = _interopRequireDefault(_mapStateToProps);
+
+	var _mergeProps = __webpack_require__(11);
+
+	var _mergeProps2 = _interopRequireDefault(_mergeProps);
+
+	var _selectorFactory = __webpack_require__(12);
+
+	var _selectorFactory2 = _interopRequireDefault(_selectorFactory);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
+
+	/*
+	  connect is a facade over connectAdvanced. It turns its args into a compatible
+	  selectorFactory, which has the signature:
+
+	    (dispatch, options) => (nextState, nextOwnProps) => nextFinalProps
+
+	  connect passes its args to connectAdvanced as options, which will in turn pass them to
+	  selectorFactory each time a Connect component instance is instantiated or hot reloaded.
+
+	  selectorFactory returns a final props selector from its mapStateToProps,
+	  mapStateToPropsFactories, mapDispatchToProps, mapDispatchToPropsFactories, mergeProps,
+	  mergePropsFactories, and pure args.
+
+	  The resulting final props selector is called by the Connect component instance whenever
+	  it receives new props or store state.
+	 */
+
+	function match(arg, factories, name) {
+	  for (var i = factories.length - 1; i >= 0; i--) {
+	    var result = factories[i](arg);
+	    if (result) return result;
+	  }
+
+	  return function (dispatch, options) {
+	    throw new Error('Invalid value of type ' + typeof arg + ' for ' + name + ' argument when connecting component ' + options.wrappedComponentName + '.');
+	  };
+	}
+
+	function strictEqual(a, b) {
+	  return a === b;
+	}
+
+	// createConnect with default args builds the 'official' connect behavior. Calling it with
+	// different options opens up some testing and extensibility scenarios
+	function createConnect() {
+	  var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+	  var _ref$connectHOC = _ref.connectHOC;
+	  var connectHOC = _ref$connectHOC === undefined ? _connectAdvanced2.default : _ref$connectHOC;
+	  var _ref$mapStateToPropsF = _ref.mapStateToPropsFactories;
+	  var mapStateToPropsFactories = _ref$mapStateToPropsF === undefined ? _mapStateToProps2.default : _ref$mapStateToPropsF;
+	  var _ref$mapDispatchToPro = _ref.mapDispatchToPropsFactories;
+	  var mapDispatchToPropsFactories = _ref$mapDispatchToPro === undefined ? _mapDispatchToProps2.default : _ref$mapDispatchToPro;
+	  var _ref$mergePropsFactor = _ref.mergePropsFactories;
+	  var mergePropsFactories = _ref$mergePropsFactor === undefined ? _mergeProps2.default : _ref$mergePropsFactor;
+	  var _ref$selectorFactory = _ref.selectorFactory;
+	  var selectorFactory = _ref$selectorFactory === undefined ? _selectorFactory2.default : _ref$selectorFactory;
+
+	  return function connect(mapStateToProps, mapDispatchToProps, mergeProps) {
+	    var _ref2 = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3];
+
+	    var _ref2$pure = _ref2.pure;
+	    var pure = _ref2$pure === undefined ? true : _ref2$pure;
+	    var _ref2$areStatesEqual = _ref2.areStatesEqual;
+	    var areStatesEqual = _ref2$areStatesEqual === undefined ? strictEqual : _ref2$areStatesEqual;
+	    var _ref2$areOwnPropsEqua = _ref2.areOwnPropsEqual;
+	    var areOwnPropsEqual = _ref2$areOwnPropsEqua === undefined ? _shallowEqual2.default : _ref2$areOwnPropsEqua;
+	    var _ref2$areStatePropsEq = _ref2.areStatePropsEqual;
+	    var areStatePropsEqual = _ref2$areStatePropsEq === undefined ? _shallowEqual2.default : _ref2$areStatePropsEq;
+	    var _ref2$areMergedPropsE = _ref2.areMergedPropsEqual;
+	    var areMergedPropsEqual = _ref2$areMergedPropsE === undefined ? _shallowEqual2.default : _ref2$areMergedPropsE;
+
+	    var extraOptions = _objectWithoutProperties(_ref2, ['pure', 'areStatesEqual', 'areOwnPropsEqual', 'areStatePropsEqual', 'areMergedPropsEqual']);
+
+	    var initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps');
+	    var initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps');
+	    var initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps');
+
+	    return connectHOC(selectorFactory, _extends({
+	      // used in error messages
+	      methodName: 'connect',
+
+	      // used to compute Connect's displayName from the wrapped component's displayName.
+	      getDisplayName: function getDisplayName(name) {
+	        return 'Connect(' + name + ')';
+	      },
+
+	      // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
+	      shouldHandleStateChanges: Boolean(mapStateToProps),
+
+	      // passed through to selectorFactory
+	      initMapStateToProps: initMapStateToProps,
+	      initMapDispatchToProps: initMapDispatchToProps,
+	      initMergeProps: initMergeProps,
+	      pure: pure,
+	      areStatesEqual: areStatesEqual,
+	      areOwnPropsEqual: areOwnPropsEqual,
+	      areStatePropsEqual: areStatePropsEqual,
+	      areMergedPropsEqual: areMergedPropsEqual
+
+	    }, extraOptions));
 	  };
 	}
 
-	module.exports = exports['default'];
+	exports.default = createConnect();
+
+/***/ },
+/* 9 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports.whenMapDispatchToPropsIsFunction = whenMapDispatchToPropsIsFunction;
+	exports.whenMapDispatchToPropsIsMissing = whenMapDispatchToPropsIsMissing;
+	exports.whenMapDispatchToPropsIsObject = whenMapDispatchToPropsIsObject;
+
+	var _redux = __webpack_require__(22);
+
+	var _wrapMapToProps = __webpack_require__(4);
+
+	function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
+	  return typeof mapDispatchToProps === 'function' ? (0, _wrapMapToProps.wrapMapToPropsFunc)(mapDispatchToProps, 'mapDispatchToProps') : undefined;
+	}
+
+	function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
+	  return !mapDispatchToProps ? (0, _wrapMapToProps.wrapMapToPropsConstant)(function (dispatch) {
+	    return { dispatch: dispatch };
+	  }) : undefined;
+	}
+
+	function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
+	  return mapDispatchToProps && typeof mapDispatchToProps === 'object' ? (0, _wrapMapToProps.wrapMapToPropsConstant)(function (dispatch) {
+	    return (0, _redux.bindActionCreators)(mapDispatchToProps, dispatch);
+	  }) : undefined;
+	}
+
+	exports.default = [whenMapDispatchToPropsIsFunction, whenMapDispatchToPropsIsMissing, whenMapDispatchToPropsIsObject];
+
+/***/ },
+/* 10 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports.whenMapStateToPropsIsFunction = whenMapStateToPropsIsFunction;
+	exports.whenMapStateToPropsIsMissing = whenMapStateToPropsIsMissing;
+
+	var _wrapMapToProps = __webpack_require__(4);
+
+	function whenMapStateToPropsIsFunction(mapStateToProps) {
+	  return typeof mapStateToProps === 'function' ? (0, _wrapMapToProps.wrapMapToPropsFunc)(mapStateToProps, 'mapStateToProps') : undefined;
+	}
+
+	function whenMapStateToPropsIsMissing(mapStateToProps) {
+	  return !mapStateToProps ? (0, _wrapMapToProps.wrapMapToPropsConstant)(function () {
+	    return {};
+	  }) : undefined;
+	}
+
+	exports.default = [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing];
+
+/***/ },
+/* 11 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+
+	var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+	exports.defaultMergeProps = defaultMergeProps;
+	exports.wrapMergePropsFunc = wrapMergePropsFunc;
+	exports.whenMergePropsIsFunction = whenMergePropsIsFunction;
+	exports.whenMergePropsIsOmitted = whenMergePropsIsOmitted;
+
+	var _verifyPlainObject = __webpack_require__(6);
+
+	var _verifyPlainObject2 = _interopRequireDefault(_verifyPlainObject);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function defaultMergeProps(stateProps, dispatchProps, ownProps) {
+	  return _extends({}, ownProps, stateProps, dispatchProps);
+	}
+
+	function wrapMergePropsFunc(mergeProps) {
+	  return function initMergePropsProxy(dispatch, _ref) {
+	    var displayName = _ref.displayName;
+	    var pure = _ref.pure;
+	    var areMergedPropsEqual = _ref.areMergedPropsEqual;
+
+	    var hasRunOnce = false;
+	    var mergedProps = void 0;
+
+	    return function mergePropsProxy(stateProps, dispatchProps, ownProps) {
+	      var nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps);
+
+	      if (hasRunOnce) {
+	        if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps)) mergedProps = nextMergedProps;
+	      } else {
+	        hasRunOnce = true;
+	        mergedProps = nextMergedProps;
+
+	        if (true) (0, _verifyPlainObject2.default)(mergedProps, displayName, 'mergeProps');
+	      }
+
+	      return mergedProps;
+	    };
+	  };
+	}
+
+	function whenMergePropsIsFunction(mergeProps) {
+	  return typeof mergeProps === 'function' ? wrapMergePropsFunc(mergeProps) : undefined;
+	}
+
+	function whenMergePropsIsOmitted(mergeProps) {
+	  return !mergeProps ? function () {
+	    return defaultMergeProps;
+	  } : undefined;
+	}
+
+	exports.default = [whenMergePropsIsFunction, whenMergePropsIsOmitted];
 
 /***/ },
-/* 8 */
+/* 12 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports.impureFinalPropsSelectorFactory = impureFinalPropsSelectorFactory;
+	exports.pureFinalPropsSelectorFactory = pureFinalPropsSelectorFactory;
+	exports.default = finalPropsSelectorFactory;
+
+	var _verifySubselectors = __webpack_require__(13);
+
+	var _verifySubselectors2 = _interopRequireDefault(_verifySubselectors);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
+
+	function impureFinalPropsSelectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch) {
+	  return function impureFinalPropsSelector(state, ownProps) {
+	    return mergeProps(mapStateToProps(state, ownProps), mapDispatchToProps(dispatch, ownProps), ownProps);
+	  };
+	}
+
+	function pureFinalPropsSelectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, _ref) {
+	  var areStatesEqual = _ref.areStatesEqual;
+	  var areOwnPropsEqual = _ref.areOwnPropsEqual;
+	  var areStatePropsEqual = _ref.areStatePropsEqual;
+
+	  var hasRunAtLeastOnce = false;
+	  var state = void 0;
+	  var ownProps = void 0;
+	  var stateProps = void 0;
+	  var dispatchProps = void 0;
+	  var mergedProps = void 0;
+
+	  function handleFirstCall(firstState, firstOwnProps) {
+	    state = firstState;
+	    ownProps = firstOwnProps;
+	    stateProps = mapStateToProps(state, ownProps);
+	    dispatchProps = mapDispatchToProps(dispatch, ownProps);
+	    mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
+	    hasRunAtLeastOnce = true;
+	    return mergedProps;
+	  }
+
+	  function handleNewPropsAndNewState() {
+	    stateProps = mapStateToProps(state, ownProps);
+
+	    if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch, ownProps);
+
+	    mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
+	    return mergedProps;
+	  }
+
+	  function handleNewProps() {
+	    if (mapStateToProps.dependsOnOwnProps) stateProps = mapStateToProps(state, ownProps);
+
+	    if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch, ownProps);
+
+	    mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
+	    return mergedProps;
+	  }
+
+	  function handleNewState() {
+	    var nextStateProps = mapStateToProps(state, ownProps);
+	    var statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps);
+	    stateProps = nextStateProps;
+
+	    if (statePropsChanged) mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
+
+	    return mergedProps;
+	  }
+
+	  function handleSubsequentCalls(nextState, nextOwnProps) {
+	    var propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps);
+	    var stateChanged = !areStatesEqual(nextState, state);
+	    state = nextState;
+	    ownProps = nextOwnProps;
+
+	    if (propsChanged && stateChanged) return handleNewPropsAndNewState();
+	    if (propsChanged) return handleNewProps();
+	    if (stateChanged) return handleNewState();
+	    return mergedProps;
+	  }
+
+	  return function pureFinalPropsSelector(nextState, nextOwnProps) {
+	    return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps);
+	  };
+	}
+
+	// TODO: Add more comments
+
+	// If pure is true, the selector returned by selectorFactory will memoize its results,
+	// allowing connectAdvanced's shouldComponentUpdate to return false if final
+	// props have not changed. If false, the selector will always return a new
+	// object and shouldComponentUpdate will always return true.
+
+	function finalPropsSelectorFactory(dispatch, _ref2) {
+	  var initMapStateToProps = _ref2.initMapStateToProps;
+	  var initMapDispatchToProps = _ref2.initMapDispatchToProps;
+	  var initMergeProps = _ref2.initMergeProps;
+
+	  var options = _objectWithoutProperties(_ref2, ['initMapStateToProps', 'initMapDispatchToProps', 'initMergeProps']);
+
+	  var mapStateToProps = initMapStateToProps(dispatch, options);
+	  var mapDispatchToProps = initMapDispatchToProps(dispatch, options);
+	  var mergeProps = initMergeProps(dispatch, options);
+
+	  if (true) {
+	    (0, _verifySubselectors2.default)(mapStateToProps, mapDispatchToProps, mergeProps, options.displayName);
+	  }
+
+	  var selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory;
+
+	  return selectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options);
+	}
+
+/***/ },
+/* 13 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	exports.__esModule = true;
+	exports.default = verifySubselectors;
+
+	var _warning = __webpack_require__(1);
+
+	var _warning2 = _interopRequireDefault(_warning);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function verify(selector, methodName, displayName) {
+	  if (!selector) {
+	    throw new Error('Unexpected value for ' + methodName + ' in ' + displayName + '.');
+	  } else if (methodName === 'mapStateToProps' || methodName === 'mapDispatchToProps') {
+	    if (!selector.hasOwnProperty('dependsOnOwnProps')) {
+	      (0, _warning2.default)('The selector for ' + methodName + ' of ' + displayName + ' did not specify a value for dependsOnOwnProps.');
+	    }
+	  }
+	}
+
+	function verifySubselectors(mapStateToProps, mapDispatchToProps, mergeProps, displayName) {
+	  verify(mapStateToProps, 'mapStateToProps', displayName);
+	  verify(mapDispatchToProps, 'mapDispatchToProps', displayName);
+	  verify(mergeProps, 'mergeProps', displayName);
+	}
+
+/***/ },
+/* 14 */
+/***/ function(module, exports) {
+
+	"use strict";
+
+	exports.__esModule = true;
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	// encapsulates the subscription logic for connecting a component to the redux store, as
+	// well as nesting subscriptions of descendant components, so that we can ensure the
+	// ancestor components re-render before descendants
+
+	var CLEARED = null;
+	var nullListeners = {
+	  notify: function notify() {}
+	};
+
+	function createListenerCollection() {
+	  // the current/next pattern is copied from redux's createStore code.
+	  // TODO: refactor+expose that code to be reusable here?
+	  var current = [];
+	  var next = [];
+
+	  return {
+	    clear: function clear() {
+	      next = CLEARED;
+	      current = CLEARED;
+	    },
+	    notify: function notify() {
+	      var listeners = current = next;
+	      for (var i = 0; i < listeners.length; i++) {
+	        listeners[i]();
+	      }
+	    },
+	    subscribe: function subscribe(listener) {
+	      var isSubscribed = true;
+	      if (next === current) next = current.slice();
+	      next.push(listener);
+
+	      return function unsubscribe() {
+	        if (!isSubscribed || current === CLEARED) return;
+	        isSubscribed = false;
+
+	        if (next === current) next = current.slice();
+	        next.splice(next.indexOf(listener), 1);
+	      };
+	    }
+	  };
+	}
+
+	var Subscription = function () {
+	  function Subscription(store, parentSub) {
+	    _classCallCheck(this, Subscription);
+
+	    this.store = store;
+	    this.parentSub = parentSub;
+	    this.unsubscribe = null;
+	    this.listeners = nullListeners;
+	  }
+
+	  Subscription.prototype.addNestedSub = function addNestedSub(listener) {
+	    this.trySubscribe();
+	    return this.listeners.subscribe(listener);
+	  };
+
+	  Subscription.prototype.notifyNestedSubs = function notifyNestedSubs() {
+	    this.listeners.notify();
+	  };
+
+	  Subscription.prototype.isSubscribed = function isSubscribed() {
+	    return Boolean(this.unsubscribe);
+	  };
+
+	  Subscription.prototype.trySubscribe = function trySubscribe() {
+	    if (!this.unsubscribe) {
+	      // this.onStateChange is set by connectAdvanced.initSubscription()
+	      this.unsubscribe = this.parentSub ? this.parentSub.addNestedSub(this.onStateChange) : this.store.subscribe(this.onStateChange);
+
+	      this.listeners = createListenerCollection();
+	    }
+	  };
+
+	  Subscription.prototype.tryUnsubscribe = function tryUnsubscribe() {
+	    if (this.unsubscribe) {
+	      this.unsubscribe();
+	      this.unsubscribe = null;
+	      this.listeners.clear();
+	      this.listeners = nullListeners;
+	    }
+	  };
+
+	  return Subscription;
+	}();
+
+	exports.default = Subscription;
+
+/***/ },
+/* 15 */
+/***/ function(module, exports) {
+
+	"use strict";
+
+	exports.__esModule = true;
+	exports.default = shallowEqual;
+	var hasOwn = Object.prototype.hasOwnProperty;
+
+	function shallowEqual(a, b) {
+	  if (a === b) return true;
+
+	  var countA = 0;
+	  var countB = 0;
+
+	  for (var key in a) {
+	    if (hasOwn.call(a, key) && a[key] !== b[key]) return false;
+	    countA++;
+	  }
+
+	  for (var _key in b) {
+	    if (hasOwn.call(b, _key)) countB++;
+	  }
+
+	  return countA === countB;
+	}
+
+/***/ },
+/* 16 */
 /***/ function(module, exports) {
 
 	/**
 	 * Copyright 2015, Yahoo! Inc.
 	 * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 	 */
 	'use strict';
 
@@ -631,40 +1183,44 @@ return /******/ (function(modules) { // 
 	    length: true,
 	    prototype: true,
 	    caller: true,
 	    arguments: true,
 	    arity: true
 	};
 
 	module.exports = function hoistNonReactStatics(targetComponent, sourceComponent) {
-	    var keys = Object.getOwnPropertyNames(sourceComponent);
-	    for (var i=0; i<keys.length; ++i) {
-	        if (!REACT_STATICS[keys[i]] && !KNOWN_STATICS[keys[i]]) {
-	            targetComponent[keys[i]] = sourceComponent[keys[i]];
+	    if (typeof sourceComponent !== 'string') { // don't hoist over string (html) components
+	        var keys = Object.getOwnPropertyNames(sourceComponent);
+	        for (var i=0; i<keys.length; ++i) {
+	            if (!REACT_STATICS[keys[i]] && !KNOWN_STATICS[keys[i]]) {
+	                try {
+	                    targetComponent[keys[i]] = sourceComponent[keys[i]];
+	                } catch (error) {
+
+	                }
+	            }
 	        }
 	    }
 
 	    return targetComponent;
 	};
 
 
 /***/ },
-/* 9 */
+/* 17 */
 /***/ function(module, exports, __webpack_require__) {
 
 	/**
 	 * Copyright 2013-2015, Facebook, Inc.
 	 * All rights reserved.
 	 *
 	 * This source code is licensed under the BSD-style license found in the
 	 * LICENSE file in the root directory of this source tree. An additional grant
 	 * of patent rights can be found in the PATENTS file in the same directory.
-	 *
-	 * @providesModule invariant
 	 */
 
 	'use strict';
 
 	/**
 	 * Use invariant() to assert state which your program assumes to be true.
 	 *
 	 * Provide sprintf-style format (only %s is supported) and arguments
@@ -688,37 +1244,189 @@ return /******/ (function(modules) { // 
 	      error = new Error(
 	        'Minified exception occurred; use the non-minified dev environment ' +
 	        'for the full error message and additional helpful warnings.'
 	      );
 	    } else {
 	      var args = [a, b, c, d, e, f];
 	      var argIndex = 0;
 	      error = new Error(
-	        'Invariant Violation: ' +
 	        format.replace(/%s/g, function() { return args[argIndex++]; })
 	      );
+	      error.name = 'Invariant Violation';
 	    }
 
 	    error.framesToPop = 1; // we don't care about invariant's own frame
 	    throw error;
 	  }
 	};
 
 	module.exports = invariant;
 
 
 /***/ },
-/* 10 */
+/* 18 */
+/***/ function(module, exports) {
+
+	/* Built-in method references for those with the same name as other `lodash` methods. */
+	var nativeGetPrototype = Object.getPrototypeOf;
+
+	/**
+	 * Gets the `[[Prototype]]` of `value`.
+	 *
+	 * @private
+	 * @param {*} value The value to query.
+	 * @returns {null|Object} Returns the `[[Prototype]]`.
+	 */
+	function getPrototype(value) {
+	  return nativeGetPrototype(Object(value));
+	}
+
+	module.exports = getPrototype;
+
+
+/***/ },
+/* 19 */
+/***/ function(module, exports) {
+
+	/**
+	 * Checks if `value` is a host object in IE < 9.
+	 *
+	 * @private
+	 * @param {*} value The value to check.
+	 * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
+	 */
+	function isHostObject(value) {
+	  // Many host objects are `Object` objects that can coerce to strings
+	  // despite having improperly defined `toString` methods.
+	  var result = false;
+	  if (value != null && typeof value.toString != 'function') {
+	    try {
+	      result = !!(value + '');
+	    } catch (e) {}
+	  }
+	  return result;
+	}
+
+	module.exports = isHostObject;
+
+
+/***/ },
+/* 20 */
 /***/ function(module, exports) {
 
-	module.exports = __WEBPACK_EXTERNAL_MODULE_10__;
+	/**
+	 * Checks if `value` is object-like. A value is object-like if it's not `null`
+	 * and has a `typeof` result of "object".
+	 *
+	 * @static
+	 * @memberOf _
+	 * @since 4.0.0
+	 * @category Lang
+	 * @param {*} value The value to check.
+	 * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+	 * @example
+	 *
+	 * _.isObjectLike({});
+	 * // => true
+	 *
+	 * _.isObjectLike([1, 2, 3]);
+	 * // => true
+	 *
+	 * _.isObjectLike(_.noop);
+	 * // => false
+	 *
+	 * _.isObjectLike(null);
+	 * // => false
+	 */
+	function isObjectLike(value) {
+	  return !!value && typeof value == 'object';
+	}
+
+	module.exports = isObjectLike;
+
 
 /***/ },
-/* 11 */
+/* 21 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var getPrototype = __webpack_require__(18),
+	    isHostObject = __webpack_require__(19),
+	    isObjectLike = __webpack_require__(20);
+
+	/** `Object#toString` result references. */
+	var objectTag = '[object Object]';
+
+	/** Used for built-in method references. */
+	var objectProto = Object.prototype;
+
+	/** Used to resolve the decompiled source of functions. */
+	var funcToString = Function.prototype.toString;
+
+	/** Used to check objects for own properties. */
+	var hasOwnProperty = objectProto.hasOwnProperty;
+
+	/** Used to infer the `Object` constructor. */
+	var objectCtorString = funcToString.call(Object);
+
+	/**
+	 * Used to resolve the
+	 * [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+	 * of values.
+	 */
+	var objectToString = objectProto.toString;
+
+	/**
+	 * Checks if `value` is a plain object, that is, an object created by the
+	 * `Object` constructor or one with a `[[Prototype]]` of `null`.
+	 *
+	 * @static
+	 * @memberOf _
+	 * @since 0.8.0
+	 * @category Lang
+	 * @param {*} value The value to check.
+	 * @returns {boolean} Returns `true` if `value` is a plain object,
+	 *  else `false`.
+	 * @example
+	 *
+	 * function Foo() {
+	 *   this.a = 1;
+	 * }
+	 *
+	 * _.isPlainObject(new Foo);
+	 * // => false
+	 *
+	 * _.isPlainObject([1, 2, 3]);
+	 * // => false
+	 *
+	 * _.isPlainObject({ 'x': 0, 'y': 0 });
+	 * // => true
+	 *
+	 * _.isPlainObject(Object.create(null));
+	 * // => true
+	 */
+	function isPlainObject(value) {
+	  if (!isObjectLike(value) ||
+	      objectToString.call(value) != objectTag || isHostObject(value)) {
+	    return false;
+	  }
+	  var proto = getPrototype(value);
+	  if (proto === null) {
+	    return true;
+	  }
+	  var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
+	  return (typeof Ctor == 'function' &&
+	    Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
+	}
+
+	module.exports = isPlainObject;
+
+
+/***/ },
+/* 22 */
 /***/ function(module, exports) {
 
-	module.exports = __WEBPACK_EXTERNAL_MODULE_11__;
+	module.exports = __WEBPACK_EXTERNAL_MODULE_22__;
 
 /***/ }
 /******/ ])
 });
 ;
--- a/devtools/client/shared/vendor/redux.js
+++ b/devtools/client/shared/vendor/redux.js
@@ -58,103 +58,116 @@ return /******/ (function(modules) { // 
 
 	exports.__esModule = true;
 	exports.compose = exports.applyMiddleware = exports.bindActionCreators = exports.combineReducers = exports.createStore = undefined;
 
 	var _createStore = __webpack_require__(2);
 
 	var _createStore2 = _interopRequireDefault(_createStore);
 
-	var _combineReducers = __webpack_require__(7);
+	var _combineReducers = __webpack_require__(8);
 
 	var _combineReducers2 = _interopRequireDefault(_combineReducers);
 
-	var _bindActionCreators = __webpack_require__(6);
+	var _bindActionCreators = __webpack_require__(7);
 
 	var _bindActionCreators2 = _interopRequireDefault(_bindActionCreators);
 
-	var _applyMiddleware = __webpack_require__(5);
+	var _applyMiddleware = __webpack_require__(6);
 
 	var _applyMiddleware2 = _interopRequireDefault(_applyMiddleware);
 
 	var _compose = __webpack_require__(1);
 
 	var _compose2 = _interopRequireDefault(_compose);
 
 	var _warning = __webpack_require__(3);
 
 	var _warning2 = _interopRequireDefault(_warning);
 
-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
 
 	/*
 	* This is a dummy function to check if the function name has been altered by minification.
 	* If the function has been minified and NODE_ENV !== 'production', warn the user.
 	*/
 	function isCrushed() {}
 
 	if (("development") !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed') {
-	  (0, _warning2["default"])('You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.');
+	  (0, _warning2['default'])('You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.');
 	}
 
-	exports.createStore = _createStore2["default"];
-	exports.combineReducers = _combineReducers2["default"];
-	exports.bindActionCreators = _bindActionCreators2["default"];
-	exports.applyMiddleware = _applyMiddleware2["default"];
-	exports.compose = _compose2["default"];
+	exports.createStore = _createStore2['default'];
+	exports.combineReducers = _combineReducers2['default'];
+	exports.bindActionCreators = _bindActionCreators2['default'];
+	exports.applyMiddleware = _applyMiddleware2['default'];
+	exports.compose = _compose2['default'];
 
 /***/ },
 /* 1 */
 /***/ function(module, exports) {
 
 	"use strict";
 
 	exports.__esModule = true;
 	exports["default"] = compose;
 	/**
-	 * Composes single-argument functions from right to left.
+	 * Composes single-argument functions from right to left. The rightmost
+	 * function can take multiple arguments as it provides the signature for
+	 * the resulting composite function.
 	 *
 	 * @param {...Function} funcs The functions to compose.
-	 * @returns {Function} A function obtained by composing functions from right to
-	 * left. For example, compose(f, g, h) is identical to arg => f(g(h(arg))).
+	 * @returns {Function} A function obtained by composing the argument functions
+	 * from right to left. For example, compose(f, g, h) is identical to doing
+	 * (...args) => f(g(h(...args))).
 	 */
+
 	function compose() {
 	  for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
 	    funcs[_key] = arguments[_key];
 	  }
 
+	  if (funcs.length === 0) {
+	    return function (arg) {
+	      return arg;
+	    };
+	  }
+
+	  if (funcs.length === 1) {
+	    return funcs[0];
+	  }
+
+	  var last = funcs[funcs.length - 1];
+	  var rest = funcs.slice(0, -1);
 	  return function () {
-	    if (funcs.length === 0) {
-	      return arguments.length <= 0 ? undefined : arguments[0];
-	    }
-
-	    var last = funcs[funcs.length - 1];
-	    var rest = funcs.slice(0, -1);
-
 	    return rest.reduceRight(function (composed, f) {
 	      return f(composed);
 	    }, last.apply(undefined, arguments));
 	  };
 	}
 
 /***/ },
 /* 2 */
 /***/ function(module, exports, __webpack_require__) {
 
 	'use strict';
 
 	exports.__esModule = true;
 	exports.ActionTypes = undefined;
-	exports["default"] = createStore;
+	exports['default'] = createStore;
 
-	var _isPlainObject = __webpack_require__(4);
+	var _isPlainObject = __webpack_require__(5);
 
 	var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
 
-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+	var _symbolObservable = __webpack_require__(17);
+
+	var _symbolObservable2 = _interopRequireDefault(_symbolObservable);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
 
 	/**
 	 * These are private action types reserved by Redux.
 	 * For any unknown actions, you must return the current state.
 	 * If the current state is undefined, you must return the initial state.
 	 * Do not reference these action types directly in your code.
 	 */
 	var ActionTypes = exports.ActionTypes = {
@@ -167,50 +180,52 @@ return /******/ (function(modules) { // 
 	 *
 	 * There should only be a single store in your app. To specify how different
 	 * parts of the state tree respond to actions, you may combine several reducers
 	 * into a single reducer function by using `combineReducers`.
 	 *
 	 * @param {Function} reducer A function that returns the next state tree, given
 	 * the current state tree and the action to handle.
 	 *
-	 * @param {any} [initialState] The initial state. You may optionally specify it
+	 * @param {any} [preloadedState] The initial state. You may optionally specify it
 	 * to hydrate the state from the server in universal apps, or to restore a
 	 * previously serialized user session.
 	 * If you use `combineReducers` to produce the root reducer function, this must be
 	 * an object with the same shape as `combineReducers` keys.
 	 *
 	 * @param {Function} enhancer The store enhancer. You may optionally specify it
 	 * to enhance the store with third-party capabilities such as middleware,
 	 * time travel, persistence, etc. The only store enhancer that ships with Redux
 	 * is `applyMiddleware()`.
 	 *
 	 * @returns {Store} A Redux store that lets you read the state, dispatch actions
 	 * and subscribe to changes.
 	 */
-	function createStore(reducer, initialState, enhancer) {
-	  if (typeof initialState === 'function' && typeof enhancer === 'undefined') {
-	    enhancer = initialState;
-	    initialState = undefined;
+	function createStore(reducer, preloadedState, enhancer) {
+	  var _ref2;
+
+	  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
+	    enhancer = preloadedState;
+	    preloadedState = undefined;
 	  }
 
 	  if (typeof enhancer !== 'undefined') {
 	    if (typeof enhancer !== 'function') {
 	      throw new Error('Expected the enhancer to be a function.');
 	    }
 
-	    return enhancer(createStore)(reducer, initialState);
+	    return enhancer(createStore)(reducer, preloadedState);
 	  }
 
 	  if (typeof reducer !== 'function') {
 	    throw new Error('Expected the reducer to be a function.');
 	  }
 
 	  var currentReducer = reducer;
-	  var currentState = initialState;
+	  var currentState = preloadedState;
 	  var currentListeners = [];
 	  var nextListeners = currentListeners;
 	  var isDispatching = false;
 
 	  function ensureCanMutateNextListeners() {
 	    if (nextListeners === currentListeners) {
 	      nextListeners = currentListeners.slice();
 	    }
@@ -234,17 +249,17 @@ return /******/ (function(modules) { // 
 	   * caveats:
 	   *
 	   * 1. The subscriptions are snapshotted just before every `dispatch()` call.
 	   * If you subscribe or unsubscribe while the listeners are being invoked, this
 	   * will not have any effect on the `dispatch()` that is currently in progress.
 	   * However, the next `dispatch()` call, whether nested or not, will use a more
 	   * recent snapshot of the subscription list.
 	   *
-	   * 2. The listener should not expect to see all states changes, as the state
+	   * 2. The listener should not expect to see all state changes, as the state
 	   * might have been updated multiple times during a nested `dispatch()` before
 	   * the listener is called. It is, however, guaranteed that all subscribers
 	   * registered before the `dispatch()` started will be called with the latest
 	   * state by the time it exits.
 	   *
 	   * @param {Function} listener A callback to be invoked on every dispatch.
 	   * @returns {Function} A function to remove this change listener.
 	   */
@@ -292,17 +307,17 @@ return /******/ (function(modules) { // 
 	   * string constants for action types.
 	   *
 	   * @returns {Object} For convenience, the same action object you dispatched.
 	   *
 	   * Note that, if you use a custom middleware, it may wrap `dispatch()` to
 	   * return something else (for example, a Promise you can await).
 	   */
 	  function dispatch(action) {
-	    if (!(0, _isPlainObject2["default"])(action)) {
+	    if (!(0, _isPlainObject2['default'])(action)) {
 	      throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
 	    }
 
 	    if (typeof action.type === 'undefined') {
 	      throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
 	    }
 
 	    if (isDispatching) {
@@ -338,92 +353,141 @@ return /******/ (function(modules) { // 
 	    if (typeof nextReducer !== 'function') {
 	      throw new Error('Expected the nextReducer to be a function.');
 	    }
 
 	    currentReducer = nextReducer;
 	    dispatch({ type: ActionTypes.INIT });
 	  }
 
+	  /**
+	   * Interoperability point for observable/reactive libraries.
+	   * @returns {observable} A minimal observable of state changes.
+	   * For more information, see the observable proposal:
+	   * https://github.com/zenparsing/es-observable
+	   */
+	  function observable() {
+	    var _ref;
+
+	    var outerSubscribe = subscribe;
+	    return _ref = {
+	      /**
+	       * The minimal observable subscription method.
+	       * @param {Object} observer Any object that can be used as an observer.
+	       * The observer object should have a `next` method.
+	       * @returns {subscription} An object with an `unsubscribe` method that can
+	       * be used to unsubscribe the observable from the store, and prevent further
+	       * emission of values from the observable.
+	       */
+	      subscribe: function subscribe(observer) {
+	        if (typeof observer !== 'object') {
+	          throw new TypeError('Expected the observer to be an object.');
+	        }
+
+	        function observeState() {
+	          if (observer.next) {
+	            observer.next(getState());
+	          }
+	        }
+
+	        observeState();
+	        var unsubscribe = outerSubscribe(observeState);
+	        return { unsubscribe: unsubscribe };
+	      }
+	    }, _ref[_symbolObservable2['default']] = function () {
+	      return this;
+	    }, _ref;
+	  }
+
 	  // When a store is created, an "INIT" action is dispatched so that every
 	  // reducer returns their initial state. This effectively populates
 	  // the initial state tree.
 	  dispatch({ type: ActionTypes.INIT });
 
-	  return {
+	  return _ref2 = {
 	    dispatch: dispatch,
 	    subscribe: subscribe,
 	    getState: getState,
 	    replaceReducer: replaceReducer
-	  };
+	  }, _ref2[_symbolObservable2['default']] = observable, _ref2;
 	}
 
 /***/ },
 /* 3 */
 /***/ function(module, exports) {
 
 	'use strict';
 
 	exports.__esModule = true;
-	exports["default"] = warning;
+	exports['default'] = warning;
 	/**
 	 * Prints a warning in the console if it exists.
 	 *
 	 * @param {String} message The warning message.
 	 * @returns {void}
 	 */
 	function warning(message) {
 	  /* eslint-disable no-console */
 	  if (typeof console !== 'undefined' && typeof console.error === 'function') {
 	    console.error(message);
 	  }
 	  /* eslint-enable no-console */
 	  try {
-	    // This error was thrown as a convenience so that you can use this stack
-	    // to find the callsite that caused this warning to fire.
+	    // This error was thrown as a convenience so that if you enable
+	    // "break on all exceptions" in your console,
+	    // it would pause the execution at this line.
 	    throw new Error(message);
 	    /* eslint-disable no-empty */
 	  } catch (e) {}
 	  /* eslint-enable no-empty */
 	}
 
 /***/ },
 /* 4 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var isHostObject = __webpack_require__(8),
-	    isObjectLike = __webpack_require__(9);
+	var root = __webpack_require__(15);
+
+	/** Built-in value references. */
+	var Symbol = root.Symbol;
+
+	module.exports = Symbol;
+
+
+/***/ },
+/* 5 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var baseGetTag = __webpack_require__(9),
+	    getPrototype = __webpack_require__(11),
+	    isObjectLike = __webpack_require__(16);
 
 	/** `Object#toString` result references. */
 	var objectTag = '[object Object]';
 
 	/** Used for built-in method references. */
-	var objectProto = Object.prototype;
+	var funcProto = Function.prototype,
+	    objectProto = Object.prototype;
 
 	/** Used to resolve the decompiled source of functions. */
-	var funcToString = Function.prototype.toString;
+	var funcToString = funcProto.toString;
+
+	/** Used to check objects for own properties. */
+	var hasOwnProperty = objectProto.hasOwnProperty;
 
 	/** Used to infer the `Object` constructor. */
 	var objectCtorString = funcToString.call(Object);
 
 	/**
-	 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
-	 * of values.
-	 */
-	var objectToString = objectProto.toString;
-
-	/** Built-in value references. */
-	var getPrototypeOf = Object.getPrototypeOf;
-
-	/**
 	 * Checks if `value` is a plain object, that is, an object created by the
 	 * `Object` constructor or one with a `[[Prototype]]` of `null`.
 	 *
 	 * @static
 	 * @memberOf _
+	 * @since 0.8.0
 	 * @category Lang
 	 * @param {*} value The value to check.
 	 * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
 	 * @example
 	 *
 	 * function Foo() {
 	 *   this.a = 1;
 	 * }
@@ -436,50 +500,48 @@ return /******/ (function(modules) { // 
 	 *
 	 * _.isPlainObject({ 'x': 0, 'y': 0 });
 	 * // => true
 	 *
 	 * _.isPlainObject(Object.create(null));
 	 * // => true
 	 */
 	function isPlainObject(value) {
-	  if (!isObjectLike(value) || objectToString.call(value) != objectTag || isHostObject(value)) {
+	  if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
 	    return false;
 	  }
-	  var proto = objectProto;
-	  if (typeof value.constructor == 'function') {
-	    proto = getPrototypeOf(value);
-	  }
+	  var proto = getPrototype(value);
 	  if (proto === null) {
 	    return true;
 	  }
-	  var Ctor = proto.constructor;
-	  return (typeof Ctor == 'function' &&
-	    Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
+	  var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
+	  return typeof Ctor == 'function' && Ctor instanceof Ctor &&
+	    funcToString.call(Ctor) == objectCtorString;
 	}
 
 	module.exports = isPlainObject;
 
 
 /***/ },
-/* 5 */
+/* 6 */
 /***/ function(module, exports, __webpack_require__) {
 
 	'use strict';
 
+	exports.__esModule = true;
+
 	var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
-	exports.__esModule = true;
-	exports["default"] = applyMiddleware;
+	exports['default'] = applyMiddleware;
 
 	var _compose = __webpack_require__(1);
 
 	var _compose2 = _interopRequireDefault(_compose);
 
-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
 
 	/**
 	 * Creates a store enhancer that applies middleware to the dispatch method
 	 * of the Redux store. This is handy for a variety of tasks, such as expressing
 	 * asynchronous actions in a concise manner, or logging every action payload.
 	 *
 	 * See `redux-thunk` package as an example of the Redux middleware.
 	 *
@@ -493,47 +555,47 @@ return /******/ (function(modules) { // 
 	 * @returns {Function} A store enhancer applying the middleware.
 	 */
 	function applyMiddleware() {
 	  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
 	    middlewares[_key] = arguments[_key];
 	  }
 
 	  return function (createStore) {
-	    return function (reducer, initialState, enhancer) {
-	      var store = createStore(reducer, initialState, enhancer);
+	    return function (reducer, preloadedState, enhancer) {
+	      var store = createStore(reducer, preloadedState, enhancer);
 	      var _dispatch = store.dispatch;
 	      var chain = [];
 
 	      var middlewareAPI = {
 	        getState: store.getState,
 	        dispatch: function dispatch(action) {
 	          return _dispatch(action);
 	        }
 	      };
 	      chain = middlewares.map(function (middleware) {
 	        return middleware(middlewareAPI);
 	      });
-	      _dispatch = _compose2["default"].apply(undefined, chain)(store.dispatch);
+	      _dispatch = _compose2['default'].apply(undefined, chain)(store.dispatch);
 
 	      return _extends({}, store, {
 	        dispatch: _dispatch
 	      });
 	    };
 	  };
 	}
 
 /***/ },
-/* 6 */
+/* 7 */
 /***/ function(module, exports) {
 
 	'use strict';
 
 	exports.__esModule = true;
-	exports["default"] = bindActionCreators;
+	exports['default'] = bindActionCreators;
 	function bindActionCreator(actionCreator, dispatch) {
 	  return function () {
 	    return dispatch(actionCreator.apply(undefined, arguments));
 	  };
 	}
 
 	/**
 	 * Turns an object whose values are action creators, into an object with the
@@ -573,57 +635,61 @@ return /******/ (function(modules) { // 
 	    if (typeof actionCreator === 'function') {
 	      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
 	    }
 	  }
 	  return boundActionCreators;
 	}
 
 /***/ },
-/* 7 */
+/* 8 */
 /***/ function(module, exports, __webpack_require__) {
 
 	'use strict';
 
 	exports.__esModule = true;
-	exports["default"] = combineReducers;
+	exports['default'] = combineReducers;
 
 	var _createStore = __webpack_require__(2);
 
-	var _isPlainObject = __webpack_require__(4);
+	var _isPlainObject = __webpack_require__(5);
 
 	var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
 
 	var _warning = __webpack_require__(3);
 
 	var _warning2 = _interopRequireDefault(_warning);
 
-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
 
 	function getUndefinedStateErrorMessage(key, action) {
 	  var actionType = action && action.type;
 	  var actionName = actionType && '"' + actionType.toString() + '"' || 'an action';
 
-	  return 'Reducer "' + key + '" returned undefined handling ' + actionName + '. ' + 'To ignore an action, you must explicitly return the previous state.';
+	  return 'Given action ' + actionName + ', reducer "' + key + '" returned undefined. ' + 'To ignore an action, you must explicitly return the previous state.';
 	}
 
-	function getUnexpectedStateShapeWarningMessage(inputState, reducers, action) {
+	function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
 	  var reducerKeys = Object.keys(reducers);
-	  var argumentName = action && action.type === _createStore.ActionTypes.INIT ? 'initialState argument passed to createStore' : 'previous state received by the reducer';
+	  var argumentName = action && action.type === _createStore.ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer';
 
 	  if (reducerKeys.length === 0) {
 	    return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.';
 	  }
 
-	  if (!(0, _isPlainObject2["default"])(inputState)) {
+	  if (!(0, _isPlainObject2['default'])(inputState)) {
 	    return 'The ' + argumentName + ' has unexpected type of "' + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected argument to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"');
 	  }
 
 	  var unexpectedKeys = Object.keys(inputState).filter(function (key) {
-	    return !reducers.hasOwnProperty(key);
+	    return !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key];
+	  });
+
+	  unexpectedKeys.forEach(function (key) {
+	    unexpectedKeyCache[key] = true;
 	  });
 
 	  if (unexpectedKeys.length > 0) {
 	    return 'Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + ' ' + ('"' + unexpectedKeys.join('", "') + '" found in ' + argumentName + '. ') + 'Expected to find one of the known reducer keys instead: ' + ('"' + reducerKeys.join('", "') + '". Unexpected keys will be ignored.');
 	  }
 	}
 
 	function assertReducerSanity(reducers) {
@@ -658,41 +724,52 @@ return /******/ (function(modules) { // 
 	 * @returns {Function} A reducer function that invokes every reducer inside the
 	 * passed object, and builds a state object with the same shape.
 	 */
 	function combineReducers(reducers) {
 	  var reducerKeys = Object.keys(reducers);
 	  var finalReducers = {};
 	  for (var i = 0; i < reducerKeys.length; i++) {
 	    var key = reducerKeys[i];
+
+	    if (true) {
+	      if (typeof reducers[key] === 'undefined') {
+	        (0, _warning2['default'])('No reducer provided for key "' + key + '"');
+	      }
+	    }
+
 	    if (typeof reducers[key] === 'function') {
 	      finalReducers[key] = reducers[key];
 	    }
 	  }
 	  var finalReducerKeys = Object.keys(finalReducers);
 
+	  if (true) {
+	    var unexpectedKeyCache = {};
+	  }
+
 	  var sanityError;
 	  try {
 	    assertReducerSanity(finalReducers);
 	  } catch (e) {
 	    sanityError = e;
 	  }
 
 	  return function combination() {
-	    var state = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+	    var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
 	    var action = arguments[1];
 
 	    if (sanityError) {
 	      throw sanityError;
 	    }
 
 	    if (true) {
-	      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action);
+	      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
 	      if (warningMessage) {
-	        (0, _warning2["default"])(warningMessage);
+	        (0, _warning2['default'])(warningMessage);
 	      }
 	    }
 
 	    var hasChanged = false;
 	    var nextState = {};
 	    for (var i = 0; i < finalReducerKeys.length; i++) {
 	      var key = finalReducerKeys[i];
 	      var reducer = finalReducers[key];
@@ -705,51 +782,199 @@ return /******/ (function(modules) { // 
 	      nextState[key] = nextStateForKey;
 	      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
 	    }
 	    return hasChanged ? nextState : state;
 	  };
 	}
 
 /***/ },
-/* 8 */
-/***/ function(module, exports) {
+/* 9 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var Symbol = __webpack_require__(4),
+	    getRawTag = __webpack_require__(12),
+	    objectToString = __webpack_require__(13);
+
+	/** `Object#toString` result references. */
+	var nullTag = '[object Null]',
+	    undefinedTag = '[object Undefined]';
+
+	/** Built-in value references. */
+	var symToStringTag = Symbol ? Symbol.toStringTag : undefined;
 
 	/**
-	 * Checks if `value` is a host object in IE < 9.
+	 * The base implementation of `getTag` without fallbacks for buggy environments.
 	 *
 	 * @private
-	 * @param {*} value The value to check.
-	 * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
+	 * @param {*} value The value to query.
+	 * @returns {string} Returns the `toStringTag`.
 	 */
-	function isHostObject(value) {
-	  // Many host objects are `Object` objects that can coerce to strings
-	  // despite having improperly defined `toString` methods.
-	  var result = false;
-	  if (value != null && typeof value.toString != 'function') {
-	    try {
-	      result = !!(value + '');
-	    } catch (e) {}
+	function baseGetTag(value) {
+	  if (value == null) {
+	    return value === undefined ? undefinedTag : nullTag;
+	  }
+	  return (symToStringTag && symToStringTag in Object(value))
+	    ? getRawTag(value)
+	    : objectToString(value);
+	}
+
+	module.exports = baseGetTag;
+
+
+/***/ },
+/* 10 */
+/***/ function(module, exports) {
+
+	/* WEBPACK VAR INJECTION */(function(global) {/** Detect free variable `global` from Node.js. */
+	var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
+
+	module.exports = freeGlobal;
+
+	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
+
+/***/ },
+/* 11 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var overArg = __webpack_require__(14);
+
+	/** Built-in value references. */
+	var getPrototype = overArg(Object.getPrototypeOf, Object);
+
+	module.exports = getPrototype;
+
+
+/***/ },
+/* 12 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var Symbol = __webpack_require__(4);
+
+	/** Used for built-in method references. */
+	var objectProto = Object.prototype;
+
+	/** Used to check objects for own properties. */
+	var hasOwnProperty = objectProto.hasOwnProperty;
+
+	/**
+	 * Used to resolve the
+	 * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+	 * of values.
+	 */
+	var nativeObjectToString = objectProto.toString;
+
+	/** Built-in value references. */
+	var symToStringTag = Symbol ? Symbol.toStringTag : undefined;
+
+	/**
+	 * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
+	 *
+	 * @private
+	 * @param {*} value The value to query.
+	 * @returns {string} Returns the raw `toStringTag`.
+	 */
+	function getRawTag(value) {
+	  var isOwn = hasOwnProperty.call(value, symToStringTag),
+	      tag = value[symToStringTag];
+
+	  try {
+	    value[symToStringTag] = undefined;
+	    var unmasked = true;
+	  } catch (e) {}
+
+	  var result = nativeObjectToString.call(value);
+	  if (unmasked) {
+	    if (isOwn) {
+	      value[symToStringTag] = tag;
+	    } else {
+	      delete value[symToStringTag];
+	    }
 	  }
 	  return result;
 	}
 
-	module.exports = isHostObject;
+	module.exports = getRawTag;
+
+
+/***/ },
+/* 13 */
+/***/ function(module, exports) {
+
+	/** Used for built-in method references. */
+	var objectProto = Object.prototype;
+
+	/**
+	 * Used to resolve the
+	 * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+	 * of values.
+	 */
+	var nativeObjectToString = objectProto.toString;
+
+	/**
+	 * Converts `value` to a string using `Object.prototype.toString`.
+	 *
+	 * @private
+	 * @param {*} value The value to convert.
+	 * @returns {string} Returns the converted string.
+	 */
+	function objectToString(value) {
+	  return nativeObjectToString.call(value);
+	}
+
+	module.exports = objectToString;
 
 
 /***/ },
-/* 9 */
+/* 14 */
+/***/ function(module, exports) {
+
+	/**
+	 * Creates a unary function that invokes `func` with its argument transformed.
+	 *
+	 * @private
+	 * @param {Function} func The function to wrap.
+	 * @param {Function} transform The argument transform.
+	 * @returns {Function} Returns the new function.
+	 */
+	function overArg(func, transform) {
+	  return function(arg) {
+	    return func(transform(arg));
+	  };
+	}
+
+	module.exports = overArg;
+
+
+/***/ },
+/* 15 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var freeGlobal = __webpack_require__(10);
+
+	/** Detect free variable `self`. */
+	var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
+
+	/** Used as a reference to the global object. */
+	var root = freeGlobal || freeSelf || Function('return this')();
+
+	module.exports = root;
+
+
+/***/ },
+/* 16 */
 /***/ function(module, exports) {
 
 	/**
 	 * Checks if `value` is object-like. A value is object-like if it's not `null`
 	 * and has a `typeof` result of "object".
 	 *
 	 * @static
 	 * @memberOf _
+	 * @since 4.0.0
 	 * @category Lang
 	 * @param {*} value The value to check.
 	 * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
 	 * @example
 	 *
 	 * _.isObjectLike({});
 	 * // => true
 	 *
@@ -758,18 +983,104 @@ return /******/ (function(modules) { // 
 	 *
 	 * _.isObjectLike(_.noop);
 	 * // => false
 	 *
 	 * _.isObjectLike(null);
 	 * // => false
 	 */
 	function isObjectLike(value) {
-	  return !!value && typeof value == 'object';
+	  return value != null && typeof value == 'object';
 	}
 
 	module.exports = isObjectLike;
 
 
+/***/ },
+/* 17 */
+/***/ function(module, exports, __webpack_require__) {
+
+	module.exports = __webpack_require__(18);
+
+
+/***/ },
+/* 18 */
+/***/ function(module, exports, __webpack_require__) {
+
+	/* WEBPACK VAR INJECTION */(function(global, module) {'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _ponyfill = __webpack_require__(19);
+
+	var _ponyfill2 = _interopRequireDefault(_ponyfill);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+	var root; /* global window */
+
+
+	if (typeof self !== 'undefined') {
+	  root = self;
+	} else if (typeof window !== 'undefined') {
+	  root = window;
+	} else if (typeof global !== 'undefined') {
+	  root = global;
+	} else if (true) {
+	  root = module;
+	} else {
+	  root = Function('return this')();
+	}
+
+	var result = (0, _ponyfill2['default'])(root);
+	exports['default'] = result;
+	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(20)(module)))
+
+/***/ },
+/* 19 */
+/***/ function(module, exports) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+		value: true
+	});
+	exports['default'] = symbolObservablePonyfill;
+	function symbolObservablePonyfill(root) {
+		var result;
+		var _Symbol = root.Symbol;
+
+		if (typeof _Symbol === 'function') {
+			if (_Symbol.observable) {
+				result = _Symbol.observable;
+			} else {
+				result = _Symbol('observable');
+				_Symbol.observable = result;
+			}
+		} else {
+			result = '@@observable';
+		}
+
+		return result;
+	};
+
+/***/ },
+/* 20 */
+/***/ function(module, exports) {
+
+	module.exports = function(module) {
+		if(!module.webpackPolyfill) {
+			module.deprecate = function() {};
+			module.paths = [];
+			// module.parent = undefined by default
+			module.children = [];
+			module.webpackPolyfill = 1;
+		}
+		return module;
+	}
+
+
 /***/ }
 /******/ ])
 });
 ;
\ No newline at end of file
--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -4,27 +4,29 @@ subsuite = devtools
 support-files =
   storage-cache-error.html
   storage-complex-values.html
   storage-cookies.html
   storage-empty-objectstores.html
   storage-idb-delete-blocked.html
   storage-indexeddb-duplicate-names.html
   storage-listings.html
+  storage-listings-with-fragment.html
   storage-localstorage.html
   storage-overflow.html
   storage-search.html
   storage-secured-iframe.html
   storage-sessionstorage.html
   storage-unsecured-iframe.html
   storage-updates.html
   head.js
   !/devtools/client/framework/test/shared-head.js
 
 [browser_storage_basic.js]
+[browser_storage_basic_with_fragment.js]
 [browser_storage_cache_delete.js]
 [browser_storage_cache_error.js]
 [browser_storage_cookies_delete_all.js]
 [browser_storage_cookies_domain.js]
 [browser_storage_cookies_edit.js]
 [browser_storage_cookies_edit_keyboard.js]
 [browser_storage_cookies_tab_navigation.js]
 [browser_storage_delete.js]
--- a/devtools/client/storage/test/browser_storage_basic.js
+++ b/devtools/client/storage/test/browser_storage_basic.js
@@ -19,26 +19,26 @@
 // These entries are formed by the cookies, local storage, session storage and
 // indexedDB entries created in storage-listings.html,
 // storage-secured-iframe.html and storage-unsecured-iframe.html
 
 "use strict";
 
 const testCases = [
   [
-    ["cookies", "test1.example.org"],
+    ["cookies", "http://test1.example.org"],
     [
       getCookieId("c1", "test1.example.org", "/browser"),
       getCookieId("cs2", ".example.org", "/"),
       getCookieId("c3", "test1.example.org", "/"),
       getCookieId("uc1", ".example.org", "/")
     ]
   ],
   [
-    ["cookies", "sectest1.example.org"],
+    ["cookies", "https://sectest1.example.org"],
     [
       getCookieId("uc1", ".example.org", "/"),
       getCookieId("cs2", ".example.org", "/"),
       getCookieId("sc1", "sectest1.example.org", "/browser/devtools/client/storage/test/")
     ]
   ],
   [["localStorage", "http://test1.example.org"],
    ["ls1", "ls2"]],
@@ -81,18 +81,18 @@ const testCases = [
     MAIN_DOMAIN + "browser_storage_basic.js"]],
 ];
 
 /**
  * Test that the desired number of tree items are present
  */
 function testTree() {
   let doc = gPanelWindow.document;
-  for (let item of testCases) {
-    ok(doc.querySelector("[data-id='" + JSON.stringify(item[0]) + "']"),
+  for (let [item] of testCases) {
+    ok(doc.querySelector("[data-id='" + JSON.stringify(item) + "']"),
        "Tree item " + item[0] + " should be present in the storage tree");
   }
 }
 
 /**
  * Test that correct table entries are shown for each of the tree item
  */
 function* testTables() {
@@ -102,26 +102,26 @@ function* testTables() {
 
   // First tree item is already selected so no clicking and waiting for update
   for (let id of testCases[0][1]) {
     ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"),
        "Table item " + id + " should be present");
   }
 
   // Click rest of the tree items and wait for the table to be updated
-  for (let item of testCases.slice(1)) {
-    yield selectTreeItem(item[0]);
+  for (let [treeItem, items] of testCases.slice(1)) {
+    yield selectTreeItem(treeItem);
 
     // Check whether correct number of items are present in the table
     is(doc.querySelectorAll(
          ".table-widget-wrapper:first-of-type .table-widget-cell"
-       ).length, item[1].length, "Number of items in table is correct");
+       ).length, items.length, "Number of items in table is correct");
 
     // Check if all the desired items are present in the table
-    for (let id of item[1]) {
+    for (let id of items) {
       ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"),
          "Table item " + id + " should be present");
     }
   }
 }
 
 add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_basic_with_fragment.js
@@ -0,0 +1,139 @@
+/* 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/. */
+
+/* import-globals-from head.js */
+
+// A second basic test to assert that the storage tree and table corresponding
+// to each item in the storage tree is correctly displayed.
+
+// This test differs from browser_storage_basic.js because the URLs we load
+// include fragments e.g. http://example.com/test.js#abcdefg
+//                                                  ^^^^^^^^
+//                                                  fragment
+
+// Entries that should be present in the tree for this test
+// Format for each entry in the array :
+// [
+//   ["path", "to", "tree", "item"], - The path to the tree item to click formed
+//                                     by id of each item
+//   ["key_value1", "key_value2", ...] - The value of the first (unique) column
+//                                       for each row in the table corresponding
+//                                       to the tree item selected.
+// ]
+// These entries are formed by the cookies, local storage, session storage and
+// indexedDB entries created in storage-listings.html,
+// storage-secured-iframe.html and storage-unsecured-iframe.html
+
+"use strict";
+
+const testCases = [
+  [
+    ["cookies", "http://test1.example.org"],
+    [
+      getCookieId("c1", "test1.example.org", "/browser"),
+      getCookieId("cs2", ".example.org", "/"),
+      getCookieId("c3", "test1.example.org", "/"),
+      getCookieId("uc1", ".example.org", "/")
+    ]
+  ],
+  [
+    ["cookies", "https://sectest1.example.org"],
+    [
+      getCookieId("uc1", ".example.org", "/"),
+      getCookieId("cs2", ".example.org", "/"),
+      getCookieId("sc1", "sectest1.example.org", "/browser/devtools/client/storage/test/")
+    ]
+  ],
+  [["localStorage", "http://test1.example.org"],
+   ["ls1", "ls2"]],
+  [["localStorage", "http://sectest1.example.org"],
+   ["iframe-u-ls1"]],
+  [["localStorage", "https://sectest1.example.org"],
+   ["iframe-s-ls1"]],
+  [["sessionStorage", "http://test1.example.org"],
+   ["ss1"]],
+  [["sessionStorage", "http://sectest1.example.org"],
+   ["iframe-u-ss1", "iframe-u-ss2"]],
+  [["sessionStorage", "https://sectest1.example.org"],
+   ["iframe-s-ss1"]],
+  [["indexedDB", "http://test1.example.org"],
+   ["idb1 (default)", "idb2 (default)"]],
+  [["indexedDB", "http://test1.example.org", "idb1 (default)"],
+   ["obj1", "obj2"]],
+  [["indexedDB", "http://test1.example.org", "idb2 (default)"],
+   ["obj3"]],
+  [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
+   [1, 2, 3]],
+  [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"],
+   [1]],
+  [["indexedDB", "http://test1.example.org", "idb2 (default)", "obj3"],
+   []],
+  [["indexedDB", "http://sectest1.example.org"],
+   []],
+  [["indexedDB", "https://sectest1.example.org"],
+   ["idb-s1 (default)", "idb-s2 (default)"]],
+  [["indexedDB", "https://sectest1.example.org", "idb-s1 (default)"],
+   ["obj-s1"]],
+  [["indexedDB", "https://sectest1.example.org", "idb-s2 (default)"],
+   ["obj-s2"]],
+  [["indexedDB", "https://sectest1.example.org", "idb-s1 (default)", "obj-s1"],
+   [6, 7]],
<