Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 17 Jan 2017 09:01:06 -0500
changeset 342116 c52fa5ad5b28d10afb19beb3d2d9e7769d3b74d6
parent 342115 87aa6ecc99aa5cb7c7f8c1f38d610093b8a2845c (current diff)
parent 329647 3e275d37a06236981bff399b7d7aa0646be3fee7 (diff)
child 342117 de76905b9d39d63a17ec45e44dfaaea6c9f0d90d
push id31345
push userkwierso@gmail.com
push dateFri, 10 Feb 2017 20:35:09 +0000
treeherdermozilla-central@a288fe35e494 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to 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]],
+  [["indexedDB", "https://sectest1.example.org", "idb-s2 (default)", "obj-s2"],
+   [16]],
+  [["Cache", "http://test1.example.org", "plop"],
+   [MAIN_DOMAIN + "404_cached_file.js",
+    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) + "']"),
+       "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() {
+  let doc = gPanelWindow.document;
+  // Expand all nodes so that the synthesized click event actually works
+  gUI.tree.expandAll();
+
+  // 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 [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, items.length, "Number of items in table is correct");
+
+    // Check if all the desired items are present in the table
+    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-with-fragment.html#abc");
+
+  testTree();
+  yield testTables();
+
+  yield finishTests();
+});
--- a/devtools/client/storage/test/browser_storage_cookies_delete_all.js
+++ b/devtools/client/storage/test/browser_storage_cookies_delete_all.js
@@ -39,66 +39,66 @@ function* performDelete(store, rowName, 
 }
 
 add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
 
   info("test state before delete");
   yield checkState([
     [
-      ["cookies", "test1.example.org"], [
+      ["cookies", "http://test1.example.org"], [
         getCookieId("c1", "test1.example.org", "/browser"),
         getCookieId("c3", "test1.example.org", "/"),
         getCookieId("cs2", ".example.org", "/"),
         getCookieId("uc1", ".example.org", "/")
       ]
     ],
     [
-      ["cookies", "sectest1.example.org"], [
+      ["cookies", "https://sectest1.example.org"], [
         getCookieId("cs2", ".example.org", "/"),
         getCookieId("sc1", "sectest1.example.org",
                     "/browser/devtools/client/storage/test/"),
         getCookieId("uc1", ".example.org", "/")
       ]
     ],
   ]);
 
   info("delete all from domain");
   // delete only cookies that match the host exactly
   let id = getCookieId("c1", "test1.example.org", "/browser");
-  yield performDelete(["cookies", "test1.example.org"], id, false);
+  yield performDelete(["cookies", "http://test1.example.org"], id, false);
 
   info("test state after delete all from domain");
   yield checkState([
     // Domain cookies (.example.org) must not be deleted.
     [
-      ["cookies", "test1.example.org"],
+      ["cookies", "http://test1.example.org"],
       [
         getCookieId("cs2", ".example.org", "/"),
         getCookieId("uc1", ".example.org", "/")
       ]
     ],
     [
-      ["cookies", "sectest1.example.org"],
+      ["cookies", "https://sectest1.example.org"],
       [
         getCookieId("cs2", ".example.org", "/"),
         getCookieId("uc1", ".example.org", "/"),
         getCookieId("sc1", "sectest1.example.org",
                     "/browser/devtools/client/storage/test/"),
       ]
     ],
   ]);
 
   info("delete all");
   // delete all cookies for host, including domain cookies
   id = getCookieId("uc1", ".example.org", "/");
-  yield performDelete(["cookies", "sectest1.example.org"], id, true);
+  yield performDelete(["cookies", "http://sectest1.example.org"], id, true);
 
   info("test state after delete all");
   yield checkState([
     // Domain cookies (.example.org) are deleted too, so deleting in sectest1
     // also removes stuff from test1.
-    [["cookies", "test1.example.org"], []],
-    [["cookies", "sectest1.example.org"], []],
+    [["cookies", "http://test1.example.org"], []],
+    [["cookies", "https://sectest1.example.org"], []],
   ]);
 
   yield finishTests();
 });
--- a/devtools/client/storage/test/browser_storage_cookies_domain.js
+++ b/devtools/client/storage/test/browser_storage_cookies_domain.js
@@ -9,17 +9,17 @@
 // Test that cookies with domain equal to full host name are listed.
 // E.g., ".example.org" vs. example.org). Bug 1149497.
 
 add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
 
   yield checkState([
     [
-      ["cookies", "test1.example.org"],
+      ["cookies", "http://test1.example.org"],
       [
         getCookieId("test1", ".test1.example.org", "/browser"),
         getCookieId("test2", "test1.example.org", "/browser"),
         getCookieId("test3", ".test1.example.org", "/browser"),
         getCookieId("test4", "test1.example.org", "/browser"),
         getCookieId("test5", ".test1.example.org", "/browser")
       ]
     ],
--- a/devtools/client/storage/test/browser_storage_delete.js
+++ b/devtools/client/storage/test/browser_storage_delete.js
@@ -9,17 +9,17 @@
 // Test deleting storage items
 
 const TEST_CASES = [
   [["localStorage", "http://test1.example.org"],
    "ls1", "name"],
   [["sessionStorage", "http://test1.example.org"],
    "ss1", "name"],
   [
-    ["cookies", "test1.example.org"],
+    ["cookies", "http://test1.example.org"],
     getCookieId("c1", "test1.example.org", "/browser"), "name"
   ],
   [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
    1, "name"],
   [["Cache", "http://test1.example.org", "plop"],
    MAIN_DOMAIN + "404_cached_file.js", "url"],
 ];
 
--- a/devtools/client/storage/test/browser_storage_delete_tree.js
+++ b/devtools/client/storage/test/browser_storage_delete_tree.js
@@ -13,34 +13,34 @@ add_task(function* () {
 
   let contextMenu = gPanelWindow.document.getElementById("storage-tree-popup");
   let menuDeleteAllItem = contextMenu.querySelector(
     "#storage-tree-popup-delete-all");
 
   info("test state before delete");
   yield checkState([
     [
-      ["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", "/")
       ]
     ],
     [["localStorage", "http://test1.example.org"], ["ls1", "ls2"]],
     [["sessionStorage", "http://test1.example.org"], ["ss1"]],
     [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], [1, 2, 3]],
     [["Cache", "http://test1.example.org", "plop"],
       [MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]],
   ]);
 
   info("do the delete");
   const deleteHosts = [
-    ["cookies", "test1.example.org"],
+    ["cookies", "http://test1.example.org"],
     ["localStorage", "http://test1.example.org"],
     ["sessionStorage", "http://test1.example.org"],
     ["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
     ["Cache", "http://test1.example.org", "plop"],
   ];
 
   for (let store of deleteHosts) {
     let storeName = store.join(" > ");
@@ -59,17 +59,17 @@ add_task(function* () {
       menuDeleteAllItem.click();
     });
 
     yield eventWait;
   }
 
   info("test state after delete");
   yield checkState([
-    [["cookies", "test1.example.org"], []],
+    [["cookies", "http://test1.example.org"], []],
     [["localStorage", "http://test1.example.org"], []],
     [["sessionStorage", "http://test1.example.org"], []],
     [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], []],
     [["Cache", "http://test1.example.org", "plop"], []],
   ]);
 
   yield finishTests();
 });
--- a/devtools/client/storage/test/browser_storage_dynamic_updates_cookies.js
+++ b/devtools/client/storage/test/browser_storage_dynamic_updates_cookies.js
@@ -38,34 +38,34 @@ add_task(function* () {
   // Check that sidebar shows correct initial value
   yield findVariableViewProperties(initialValue[0], false);
 
   yield findVariableViewProperties(initialValue[1], true);
   // Check if table shows correct initial value
 
   yield checkState([
     [
-      ["cookies", "test1.example.org"],
+      ["cookies", "http://test1.example.org"],
       [
         getCookieId("c1", "test1.example.org", "/browser"),
         getCookieId("c2", "test1.example.org", "/browser")
       ]
     ],
   ]);
   checkCell(c1id, "value", "1.2.3.4.5.6.7");
 
   gWindow.addCookie("c1", '{"foo": 4,"bar":6}', "/browser");
   yield gUI.once("sidebar-updated");
 
   yield findVariableViewProperties(finalValue[0], false);
   yield findVariableViewProperties(finalValue[1], true);
 
   yield checkState([
     [
-      ["cookies", "test1.example.org"],
+      ["cookies", "http://test1.example.org"],
       [
         getCookieId("c1", "test1.example.org", "/browser"),
         getCookieId("c2", "test1.example.org", "/browser")
       ]
     ],
   ]);
   checkCell(c1id, "value", '{"foo": 4,"bar":6}');
 
@@ -73,17 +73,17 @@ add_task(function* () {
   gWindow.addCookie("c3", "booyeah");
 
   // Wait once for update and another time for value fetching
   yield gUI.once("store-objects-updated");
   yield gUI.once("store-objects-updated");
 
   yield checkState([
     [
-      ["cookies", "test1.example.org"],
+      ["cookies", "http://test1.example.org"],
       [
         getCookieId("c1", "test1.example.org", "/browser"),
         getCookieId("c2", "test1.example.org", "/browser"),
         getCookieId("c3", "test1.example.org",
                     "/browser/devtools/client/storage/test/")
       ]
     ],
   ]);
@@ -95,17 +95,17 @@ add_task(function* () {
   gWindow.addCookie("c4", "booyeah");
 
   // Wait once for update and another time for value fetching
   yield gUI.once("store-objects-updated");
   yield gUI.once("store-objects-updated");
 
   yield checkState([
     [
-      ["cookies", "test1.example.org"],
+      ["cookies", "http://test1.example.org"],
       [
         getCookieId("c1", "test1.example.org", "/browser"),
         getCookieId("c2", "test1.example.org", "/browser"),
         getCookieId("c3", "test1.example.org",
                     "/browser/devtools/client/storage/test/"),
         getCookieId("c4", "test1.example.org",
                     "/browser/devtools/client/storage/test/")
       ]
@@ -117,17 +117,17 @@ add_task(function* () {
 
   // Removing