Merge m-c to inbound a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 08 May 2015 10:39:27 -0700
changeset 272889 863caf540d9a1762e8aafbc4193ab60b40eb960f
parent 272888 82bbc0b0d516c7d69d931121443513537fcee8e5 (current diff)
parent 272851 cef2b76b082d36c329e94dfded65ead78ce026f2 (diff)
child 272890 4963ecd92915f1eb341ec813a52dc4181f5cc711
push id4830
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:18:48 +0000
treeherdermozilla-beta@4c2175bb0420 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound a=merge
b2g/app/b2g.icns
b2g/app/b2g.ico
b2g/app/default.png
browser/themes/linux/downloads/contentAreaDownloadsView.css
browser/themes/osx/downloads/contentAreaDownloadsView.css
browser/themes/windows/downloads/contentAreaDownloadsView.css
mobile/android/base/animation/BounceAnimator.java
--- a/addon-sdk/source/test/addons/places/lib/places-helper.js
+++ b/addon-sdk/source/test/addons/places/lib/places-helper.js
@@ -138,16 +138,21 @@ function createBookmark (data) {
       undefined),
     type: data.type || 'bookmark',
     group: data.group
   };
   return send('sdk-places-bookmarks-create', item);
 }
 exports.createBookmark = createBookmark;
 
+function historyBatch () {
+  hsrv.runInBatchMode(() => {}, null);
+}
+exports.historyBatch = historyBatch;
+
 function createBookmarkItem (data) {
   let deferred = defer();
   data = data || {};
   save({
     title: data.title || 'Moz',
     url: data.url || 'http://moz.com/',
     tags: data.tags || (!data.type || data.type === 'bookmark' ?
       ['firefox'] :
--- a/addon-sdk/source/test/addons/places/lib/test-places-events.js
+++ b/addon-sdk/source/test/addons/places/lib/test-places-events.js
@@ -24,17 +24,17 @@ const isOSX10_6 = (() => {
   let vString = release();
   return vString && /darwin/.test(platform()) && /10\.6/.test(vString);
 })();
 
 const { search } = require('sdk/places/history');
 const {
   invalidResolve, invalidReject, createTree, createBookmark,
   compareWithHost, addVisits, resetPlaces, createBookmarkItem,
-  removeVisits
+  removeVisits, historyBatch
 } = require('./places-helper');
 const { save, MENU, UNSORTED } = require('sdk/places/bookmarks');
 const { promisedEmitter } = require('sdk/places/utils');
 
 exports['test bookmark-item-added'] = function (assert, done) {
   events.on('data', function handler ({type, data}) {
     if (type !== 'bookmark-item-added') return;
     if (data.title !== 'bookmark-added-title') return;
@@ -231,19 +231,18 @@ exports['test history-start-batch, histo
       'history-start-clear has correct type');
     off(clearEvent, 'data', clearHandler);
     complete();
   }
 
   on(startEvent, 'data', startHandler);
   on(clearEvent, 'data', clearHandler);
 
-  createBookmark().then(() => {
-    resetPlaces(complete);
-  })
+  historyBatch();
+  resetPlaces(complete);
 };
 
 exports['test history-visit, history-title-changed'] = function (assert, done) {
   let complete = makeCompleted(() => {
     off(titleEvents, 'data', titleHandler);
     off(visitEvents, 'data', visitHandler);
     done();
   }, 6);
--- a/b2g/app/Makefile.in
+++ b/b2g/app/Makefile.in
@@ -15,17 +15,17 @@ UA_UPDATE_FILES = $(UA_UPDATE_FILE)
 UA_UPDATE_DEST  = $(FINAL_TARGET)
 INSTALL_TARGETS += UA_UPDATE
 
 # Make sure the standalone glue doesn't try to get libxpcom.so from b2g/app.
 NSDISTMODE = copy
 
 include $(topsrcdir)/config/rules.mk
 
-APP_ICON = b2g
+APP_ICON = app
 
 ifeq ($(OS_ARCH),WINNT)
 REDIT_PATH = $(LIBXUL_DIST)/bin
 endif
 
 APP_BINARY = $(MOZ_APP_NAME)$(BIN_SUFFIX)
 
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
@@ -59,31 +59,31 @@ tools repackage:: $(libs-preqs)
 	rsync -a --include-from='$(srcdir)/macbuild/Contents/MacOS-files.in' --exclude '*' $(DIST)/bin/ $(DIST)/$(APP_NAME).app/Contents/MacOS
 ifdef LIBXUL_SDK
 	cp $(LIBXUL_DIST)/bin/xulrunner$(BIN_SUFFIX) $(DIST)/$(APP_NAME).app/Contents/MacOS/$(APP_BINARY)
 	rsync -a --exclude nsinstall --copy-unsafe-links $(LIBXUL_DIST)/XUL.framework $(DIST)/$(APP_NAME).app/Contents/Frameworks
 else
 	$(RM) $(DIST)/$(APP_NAME).app/Contents/MacOS/$(PROGRAM)
 	rsync -aL $(PROGRAM) $(DIST)/$(APP_NAME).app/Contents/MacOS
 endif
-	cp -RL $(srcdir)/b2g.icns $(DIST)/$(APP_NAME).app/Contents/Resources/$(MOZ_APP_NAME).icns
+	cp -RL $(DIST)/branding/app.icns $(DIST)/$(APP_NAME).app/Contents/Resources/$(MOZ_APP_NAME).icns
 	printf APPLMOZB > $(DIST)/$(APP_NAME).app/Contents/PkgInfo
 
 else # MOZ_WIDGET_TOOLKIT != cocoa
 
 libs::
 ifdef LIBXUL_SDK
 	cp $(LIBXUL_DIST)/bin/xulrunner-stub$(BIN_SUFFIX) $(DIST)/bin/$(APP_BINARY)
 	$(NSINSTALL) -D $(DIST)/bin/xulrunner
 	(cd $(LIBXUL_SDK)/bin && tar $(TAR_CREATE_FLAGS) - .) | (cd $(DIST)/bin/xulrunner && tar -xf -)
 endif
 	$(NSINSTALL) -D $(DIST)/bin/chrome/icons/default
 
 # Copy the app icon for b2g-desktop
 ifeq ($(OS_ARCH),WINNT)
-	cp $(srcdir)/$(APP_ICON).ico $(DIST)/bin/chrome/icons/default/$(APP_ICON).ico
-	$(REDIT_PATH)/redit$(HOST_BIN_SUFFIX) $(DIST)/bin/$(APP_BINARY) $(srcdir)/$(APP_ICON).ico
-	cp $(srcdir)/$(APP_ICON).ico $(DIST)/bin/chrome/icons/default/default.ico
+	cp $(DIST)/branding/$(APP_ICON).ico $(DIST)/bin/chrome/icons/default/$(APP_ICON).ico
+	$(REDIT_PATH)/redit$(HOST_BIN_SUFFIX) $(DIST)/bin/$(APP_BINARY) $(DIST)/branding/$(APP_ICON).ico
+	cp $(DIST)/branding/$(APP_ICON).ico $(DIST)/bin/chrome/icons/default/default.ico
 else ifneq (gonk,$(MOZ_WIDGET_TOOLKIT))
-	cp $(srcdir)/default.png $(DIST)/bin/chrome/icons/default/default.png
+	cp $(DIST)/branding/default.png $(DIST)/bin/chrome/icons/default/default.png
 endif
 
 endif
deleted file mode 100644
index eba850aaee10662fa3bc796ac97ec59e4c3b5df8..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 5d4a61dc92ba5b08b2ff0587fb2877e7007c940e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index c4307fc8418436bb6b2fd3a6afc702c2db28aa77..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
--- /dev/null
+++ b/b2g/branding/official/Makefile.in
@@ -0,0 +1,27 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include $(topsrcdir)/config/config.mk
+
+ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
+BRANDING_FILES := \
+	app.ico \
+	$(NULL)
+endif
+
+ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
+BRANDING_FILES := \
+	app.icns \
+	$(NULL)
+endif
+
+ifdef MOZ_WIDGET_GTK
+BRANDING_FILES := \
+	default.png \
+	$(NULL)
+endif
+
+BRANDING_DEST := $(DIST)/branding
+BRANDING_TARGET := export
+INSTALL_TARGETS += BRANDING
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..eba850aaee10662fa3bc796ac97ec59e4c3b5df8
GIT binary patch
literal 11953
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5d4a61dc92ba5b08b2ff0587fb2877e7007c940e
GIT binary patch
literal 4286
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
E1?Jf~jsO4v
--- a/b2g/branding/official/configure.sh
+++ b/b2g/branding/official/configure.sh
@@ -1,7 +1,6 @@
 # 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/.
 
 MOZ_APP_DISPLAYNAME=B2G
-ANDROID_PACKAGE_NAME=org.mozilla.b2g
 MOZ_UPDATER=
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c4307fc8418436bb6b2fd3a6afc702c2db28aa77
GIT binary patch
literal 4762
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
new file mode 100644
--- /dev/null
+++ b/b2g/branding/unofficial/Makefile.in
@@ -0,0 +1,27 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include $(topsrcdir)/config/config.mk
+
+ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
+BRANDING_FILES := \
+	app.ico \
+	$(NULL)
+endif
+
+ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
+BRANDING_FILES := \
+	app.icns \
+	$(NULL)
+endif
+
+ifdef MOZ_WIDGET_GTK
+BRANDING_FILES := \
+	default.png \
+	$(NULL)
+endif
+
+BRANDING_DEST := $(DIST)/branding
+BRANDING_TARGET := export
+INSTALL_TARGETS += BRANDING
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..eba850aaee10662fa3bc796ac97ec59e4c3b5df8
GIT binary patch
literal 11953
zc%02z2UJu^v#yx|6hT2TfF#L^<eW2-k(`4Hk_1FlBq>NzK+Iv+MO@<=5CuU%5tOJ1
zDnW9PoI#K{!1TL=2?TfdKkuFM{^lH*J5^m>)qTJEy1H?9^4<?1l<dA=TnvDKTM2@0
zhVuMOUJ;3ofYS(vWv(XNF75y;jzY+K1u0Plj?j;V2Q=aOT56DIkz6Y*;#6?Wb+5U$
zyNi(AX5bSQ(>9eC*yjfB+Z9DN1COu(5A*7+&UzZR!VwoMtmEY8<K&WZj!DeUcozzh
zyaY~JF(GyD^t@MeDi68<9vchGsTu4%p7Asv^`qfPZ8=pfL(3f&KF=PWHVo+>&oB~F
z)HXCUj(D8l0s%RwFu-3?O3CqN(h){RGX~2m={|+cK4Gcn)t|JVQMlo_**|-TRDI12
z;Jiyv@Xl}m<%r9^3_NE;0E9zf#hzn~RJ69}yrisID69m-Yx^B`!nV}%Xkc~?gXOEP
z#hIKw2Ox<>?<(c*Vqx~Ezy7K#)CsKyH*wF6MmwYH>XI%7UA%I5Z-f?@xVRolxOnjT
zX}D&8(Mc2Z9jsh}%}wJE+h$xrN&>9BG6oh}>MnoWdR_3MEE-O7Q%gA8Y(Mf_?)wk6
zfpgKDs_gP~^*H%BGY5-)BtlzG-N4M!(cCBF!P&X!jfEAr85kNzK2C5WNB?5)L8W=o
zC#3GqZrS@S`kRiM1F}DTiT<2RP|%m?|M+$E_fTJ6Chw#Ev6`@iTr7GaG<CP83E(Og
z{k1zKDapy*#EI5iK)2oC<HLdTC3St1(<5!~Yf&=Fs&dG$ZS5Q!Xc}mB9(pmFo&!0x
zl{EvE`2#hHtqmVPo+v!?w5BHiLFsVqU}IZpEi|nP%jqo5?P;oQZ$gM1ea6u{HT6%!
zE~eLwJjK@jn69*zjjg%8g~z+RqgeD4$=dvi>U!HvB2s_1hJcr|q~ch4X(flR(OX}x
ze2uDnzeRuDXRA+UFIuqnDv`fj`~5OrdjN3D0c&{tw_o2Hy&%i?&o}~aROie{k_6!9
z9`SUK(9HA<>Xe)N0tdnkGn3QAF%&|9pL<pxm?ln76H&rS0;Yh&@ztQI84?K<$N&ZQ
z1;sLt$ypLYB-Cq#NiiM!G3)^$?8`?CIN(d2d%_fmvq+mVc!De-i)T@f6J7_+OwB#b
zpy|sS0Y`sf7R}6(W=N<=0rL$TUbz@ygY~#m0s&9<9;iG%e!9r?JwWU)11lN(b5HJ`
z<V3K7^ES{(%c)4HnRp!7VP;@`3g~E-Q_?fxaC8tH6|oCI^#rW;+OBs>6o6XP*bJK=
zKL8W#OJ-dV5>&rbT3eT3Yr=S579BhW8&(6zi->EoZ}Cm<?C-BW6|`MOM@9@}BY}wp
zR`auOk=P=y9$H$N8LaPjV4Jq0jwI$$I52Vp^ClKP5h(@bJ@KjcW4)c@d|h>|?P*p<
z03(6G!?8(JSVTh7+IiP*Wj-#KBYO?)cN3^0VZ%yx_RZoV;!>K9s+!W=n-pCSIT}f-
zQ&NNj6E)DVu<(lti*DVrX)PT!9i4)&i=MSO7adhNFt31>EE~A_`2{$*7}ss$TFory
zX{PDz3wqc*VSOo?Hf-eO7Zwx|5|U65-mJXS+h=FYIgkbFFkr?}0u9{;CLUoyCOQU&
z^#X>PUYDOG241Krc>Uohun=GwJ^cnYc3weQnav!6Dh9jGL|=VZ_GeaG-Q;u_Y+MFZ
zY&6Sw)ijkAm6X->t%CF4-A%*bhtje`Aw&(}@<n77WU_g)gp8bm+IHLBA-T2bNyR7|
z4FD7u=C$jh(Pz#=qGA%VO55~J9o*er!m3BZ!+NrFt6mf~Pa;$uCoim|s-dmF-NeG$
z-qFd;ZBOFeq>4X_GExf89_qTKvsqkTUP(<;M}NDirH!Mr+b-{0eT8S;-?<<NdDBk(
zOM0`UtgNDny0)I7iG|G$JNwgR<?%t*fC8hchNmZHhhv4eC@3mp77UCmEVu7_UiT=<
zZ3`k%Ctj<VocKIDSXu18MOsx=L(9Ne+wxjn?wS3n0G>V}Nv`J$22d^cR`24(Z9*!V
zx*F<7O3SVWTdrFFhYP^+7-4(apqn*A$F_0v41J1{m6EZ0QlA{@x*1Re4K+8GpqJ5T
zxJIUbu8e;(HHJ#|ovX_`<4d-!4zMy5HlhazI8A-dl+KcdCnkuW-v1e7Nym8E0ka(e
z>lLg3#O(e5tQ?#eD7zc!!UrgvdO24ltX~FTb#CnnjomRRFH+7PPyujO#4H|%H9S-+
z0ugL7^*HEfMYBHM4zm;sbaYsX^{0bP{2K>R9Q87;Q$R~Lfu;ae3j)x_00u0N1Afk4
zA?L3|haL{cfF&&^plYyjqYUf~*zXH`wit#oZeDwbsf)RU9n%MB8XG6Z!Wsp}2I^hg
z!*ZKCy9%ORl};Q&$1!BZrk)Af=^mi%oK!XRd88}-dXTHXXW&y$3|ZX3BPb-LWw>Wg
zVt04*>w9<OckMQ_kiw9K#muUzq@n8+xPR}3tjZ5XX$ct#Cw;^Iki=ecED>xq(NlM{
zwcl>y8IhFz<dRQ(a>9|=6Yf#4Ue8wFc#o@(|H(TEe@403d&l2BzJI5!IR?kI6gD<G
z=AO=;W>$VjL*4XMTyDkt`rD|RMgTJ-h$xDx=xLhEnp&Ig*sY=Jdh7hri+@1e7g#Au
zOUde)J3G4X@!sd>sC)40t%R7=bWnp8;joc*1>IUkb`fneNlq@VE!$5VJ$<Jz|4x2=
zL4M^?U}FMCL2dyFS)*P1-1Oy410pYF#^0~~__(4ELlr<Mej|oE7W{HyOJ5&%SJ$AZ
z=!<2wPm0<xT%~4VxY7bhxQv`5?G&}lU0j^q_xX4qds}fcyBNJdgFkTPBqXh&zthFt
z$$F>f9`~SE<AMI&3vi{Px!uCr*4D<Bj4O}5iP!E_{)8)KbuC>(6H8k%uDovb7tO<!
z)EurbWc~tIBXPo;{|;CErLXq<4X&ygn4R(#;%eo(%kywGcvyvHZSU}}aW(q(Q~_}T
zt}qnfzTwJj@0qux{{mb+PE1XG`L4dLvFvRXhN~ObZ`^p8mXQKAHFd@JvkOl^D54=n
znHt|L`q21F7=}U-hN?z7q?2-|bYOgPYPjZQ+P$nBe_^P){qSn;-K65P&xa>xNV79T
zEhRb6vx`tp6fho3On#JE@w%;J7$fz`(ZP<Ayr(IjW)TU?@khYw(T%icd3C*A?Y*BT
zrY6REN81~U-wowE!Ld+aQB+cUlbV}RQC(Z#+}!?Ye0;d6ceJOjsiRhcUksCfLZ~b~
zKfkfDrF(dEcyyqpw!C+wwc>4RLu|;YFxYUq=*5eIhPvkR()yn6hNA2@!$a>&GOl)n
zB8))d^6or*Qd(L2vb?_TW8=$=lHuO!#wN$^4%C8~mX}bxbms0~FUp$hKDPIEG!#8+
z8y)GW8Tlj=3~HgkN&pDB7#EiCsJy8@=kBdPe$VXc>-^Bs*U{9I_o5~Y<n^A0hn=~Z
zR@2s5@$h_FQ(O1Y%NIQpWn&X_K%)SVKH#(WNL*G^S5H@0M_XreL2qwC?Pv#}=VSsC
z2gu1l*RzsSimDqMo0_YyAG=iFnp%=Onl;h!1!#t7JlkV?<fDR$@;7PG`wsYc9WEZK
zMm<?ie*jHHN!{GZ%f-Rc($0Ylw7X+N@2(fl2b!`n1|$R1Z$N9zJm>!YJAv^_$;v6I
zk{Rwh(5@c&2DEX~K&ZghUjuD)avIe+{t{?IXreu_VD|p639M>rz$bhP^&k5M(4tbi
z5NT?1{MQ6_uHe)3r(Xdr@MhI7f#&jm1~kvPoifF8_zxNmPaqKRzm(z@{{2}Du#8v7
zScwS_ODYf;cjOF`&~)1sC0fEFAq|h$)k$oVJcaF^+fND5Q7kY`py6;GnITSM8}#WZ
zY<ZJq)=}bd^W-bJ{Kv+oroXhwv8hAcv_IrH9OXLk2eacu;+&XB9G^|#UrqQC2cDYE
zsQHWaF9xOyx2>c4J`p%PHM?c|)Hm~(3&c)Ow&e@t)*X}I<TJCx*>~RJ6h9^szl=%s
z)y%{f`&d;li{3b>(|#Wxhoe{{aG5klmd~Out$aTxP|O>E&8&?yiIp$k8}R`vd|NB5
z99gv`uy1PihZrk6T5WK+Z$1zxL9`gnlOuBNktOoAIKtM$qH%%vYptR=*BKA|Y2@nU
z=QfVw<~py69K))apQR+s2Osi#Vb(yl^>f3deZlkq8&c<dSu7>qlOT_N7)>o<nuOk3
zE}MH5W6{*ly|K={AsfHGsFLsJ)E7vn$)>-EaljlgNd9iVX^Z7Q-f|q^WRs@9R&!rW
zGcCXX_L<tQg4~fb=e77Eh~UVB3}E#k36;(_JzoJQb8m2}@h8&@G^n=7F#i&L@O32@
zN!{JZl_G;Tf_R`278Tnns$pzxpsA+&2G)fi4!#5oj9b^QVVBl45D~Dx>LR(y^b)Mc
z1Kl!4AsJD<)7ceu)p>{XwbT_h8f?7)%)ERX+4%VQ`3)~r_YaTs_h%eDVyt4N?_jwB
zp_9Ol!zn1qZc^7{;}rLce^A%n(b-aW`&g{GilO>8c?Uej3E;p3(`G(yHc3f#9!YtP
z@Z8e!+#5mK`iFwm6jfy;l`Uao3~*yFE;IY4O<Y^WWRwlGt?%WRmS&#ZVQU+9%E!)8
zSKmnU6vc9yXy70K7aKb#r-+ECsHD<1ubAsusj1gbyIP+M@!Mx?ZDDY9Lkw`?VFfQU
zC!3I{peR;$394<^)H9OjS<l4j9u(+nZffD^N`Z?8ZfX$W<ly3#5)l!TP*Ao?i10cR
zWUef^S=jVou!pOmsfh(O;Ew}06|ggMaPUcClPMuBYbMJq%(szo9V?rK$DTlIYXb>6
zDJrraCl#=5+{D2ziH$0<b&DA18V1@GE2!z$>-l^5Xz1DTZRT7N^TirF+g1@lVd1Sj
zTuiLIoGi?&*pKoaM+a>iPfIp641aT@axiXS<lyJtBFN3mw~2NIH4Eo*YDTVY-i`)Z
zwmygXby&Z8$Hvag%Cd!DL|jBjR6<fhUfo<?NJK!>)z8aV-}_WF+6#ne;KTzh4a3?^
zY@D073X5=a@vyIF<Q3o+(lb^My?!zH<gIgH7!5plEC~^4X&Kh8W8)JL<XcTi%e0Yh
z4X5FDMO*)ftb)WdS89H{^amEk^0n)kHgI$E35$t}O2~?^ZD19a(Kp`Zf6CAA*~gN5
zt5JRJ+pgoliwDATD*83F^h~@8x|*_5(iqRH>6rT6$ar}6dR^1Y%%>lkog-cz170ei
zUbU8ufk8q~TR~PqQBht=Ma#(2(J#IAW6Fu7`u-Y#=g+gBhr<@^onJu!HCEW>VI#sX
zCMzeeps1puYi8}Z+wVcsVBYWfmzoEj0lIZR0yf97LsD#9)XOv{9(HVXBqU`p54LT$
za@n`vD<UVew?4S#Q~&!jC=D~5oR``TP}y@&S3p`pL0(2$PFYjm*v!gdm$U00XV>$s
zsMVJ7>8G@3&yGUKy#rP`2<7ybx@{IyR#H~g)Y3EBZenU~Wn<^CW7lpE&)q)HDt4V~
zEl)!T;Fh<?tF{oAzNQ!v9$7hA1!YxrO)Xu0Lt`^@D_e)1yLP*J?DI5BN8N88gaO1q
zxsmV!LHh6`Wo}7nNohGOKs605T|EOM6LTwTJI9^Q?ji1u2eTSpKfW0415Y1k-$Uqb
zO)t73!7n8(AtfuXh>fsKM^Asdv6-c{t^H0HJEtqPO)sy<ggC-V*L{f({mVPX$Hz!5
zK>}N3q@~FoVf!at14AQIb4%MDcIHRlw3pmH749hnI@nZ3pDh`lm?lm3PohE-E>U@`
zFw#-MM$pzXz(%mKF>*<1{g8Gc`j9s0BXB_Xuiq^j9UUJYnI>n!`*J+8*n)nI*~CKM
z?0j8w&efPu8=%WBL+B{bpG<w%KQ%dp>io6`G!2Cc35P1LQ`6Ma);H4E*k9OQa_3CA
z4+l6Sz?8Qj*cEXjt9g2MQU@fY6)uzV?FD2s)YP`==_*?$wpOQJJa$MC#5z$8wyL3^
zxqJV~y9GTcLqX9-9}b|go0`I^TG|R~F*PkOuErd-Caiih45%#}cnt-C!^%DA(&P72
zy;0J<DjA4~K8DGONxA2CU_ONH5rF;Es5J^W!qI6a5O?qlzn=b))LPz$h@X0iq`aV8
z9W~hS{u&gVq3Aepr6DMNuzZECi|?sBxx;97cwlsPa(H^I>BZIPP+J;!gYnD>U~WNu
zyMWisHSoft($BqPXnb_2ChyLvFb`pH4MnGb1q=N78cJD5@9^t6&Ao#|ZKdhI9rx1$
zLj=C)EHDlsQYNg>ao%?(x#)fAv+L2={(03*CPEis?Epe5z-#X2AARmzY?wD2)&tP*
zu=+F5Oo)TLy}OU!K4p-uMHN?o4xxb;u#%aFbs5!oC!p2@2uJsTpslCn5Ndx3=6J=<
z!O>#3Us!CQt(DJJ2*-y)c<g0Z7ysMEzkpsxgip!RGa%gAFd+Zp(R2P$KuH04Xa+?A
zGq%NXKkcLIX5$)@UfJ5&S$i+U-P=PuFjqoQ{}iyF%t<rlHWQOGF*Q9^HaIphG4{Ff
z_Sr*Dd%VJ|%mJcefdkv%9tzukz%V#m*COz7ZgJPZ;9yT{QEGC;z9W8nkMAdtIfnpJ
z5<=nvTH3N&9?qVrSXI<fQ=EMH#$6{%M`ImRP1ua3FhLOEX5<%^5!X9#Fu?tI*~iZI
z#*(a*D-W{H1%<kLIPJOvK!fG={orA0WMgmXef02=ppe6t9_CbiD1Viobm4i*t@x10
z@R(aCVt^9|+HztN{JPr|4CMqRMU9*zGoC(4{oUXFkB9dXLXL(Vjk^&GT(Yi?cJ@|>
z{QX0M!=f+dKKd&y_15LHfd}I5-;Y0YG(5oL39LB=JXFBrXso|Y&vlo*+r9%431?$3
zUkcuBY-x1(%C*FZL;g1AYTk2-crZ3I&{5ZMwKH<CRnl<@4h;|Y(v_Bw_PlfDw&(66
zI(iD`K#9==MWCgPrJ3WQgTY}3?RL5O1_c~Acwo1aga6GdzXcsT6`|x22n?7(jEDs}
zIazsR*g0gkFv(cRD$AQ#N^uCtc-_2wGBWzoZMz-8uqGO~6!kRJwrSYy@C?|$Hz*=9
zEc*0?!!A~4kvEbPPX;HXrJyueiP6qFNp{{X{4$cNx(3F!%9@7ia_R=gS`M*s!8bDR
zUP>)Wh8@_t6R4KauIE$G(%nQ&&&oo-QQ~ln|M3gw3*S9^Qg|cb(iMzIC_za{Sx#2Z
zz|1QoDk}1*m$8C|$C0xau0O~)b*!kbHdhPPRlVvs27EZIx}e6>(JZ4~%cJG6LtjVF
zz}D04px@Q!g;`e~wD-JCPX6$5SNO{);9Cat^s5<}7<qL)0{jp7?(=r?2#G#(_IPZ;
zKu=aePWPt;q(cX+CY)e#$V1o6Wp7|WaFBmsU}%8nzR)K<#FXD_t~L)m|3*5TkQ8$w
zGADHhJ0r__772Atjcr)&w(&d^us=3GxUA!Fdq0_Uo`l1OucXs^UtdH?%huV|H!wIf
zEWpbt)Mb^sr?bm%?Wje6<-?IBNC)E_eG@Ai8*2*-OItGOxa`45=lMJ1=$4A5Ne3el
zj1vs76hDu2GEi^HFG)vJTTg!x>Ad<sBptPXOFEqY0qGFQq?0JSH0gvaCY=wi3U0NZ
zLIf9+PT1Z}i%4f|8}JEkJu$l^>F5lh<^`mavQ^GO2X-$-I<GOqXd&rz1@o*Fc!;L{
z1?kK-m3IFd(iuTB|4ce1pZosXNhkSHa!OulWn;s;_ca*lyv9i94MsWzxw)lck!fd6
z|CL=_URRZyJJT@PHxdqnb-Xk^VPw+raNarClJ<LMVrqHs=)~mIWLH^XVR6cl2MoY_
z3TTz()m2?i96OtMEv0EHbe0aQ>WJO7uL_=Lmwt>!$Rid)(aKx7nNQCZl%Kp>G4^?k
z7&=2YJv}ziT~<(#mHG_ZwT(_-J6Md|w9#)b9%bGttxbL1+15HtJ|mc#93SZKdH*Ii
z_epj;szC@#p1MiD-A&5O%&YC|>u>8LpHmPg#>WTxCfnXu=08injiwRiI742feD|Hl
zPx6ajRJXTvbac0M508XS(oal|w2cmTwtc9qXhB_n-AIXqwd+KgT9O~!&wQ9)QCeM9
z-B4NE+V(kgjP_H1bKmGlUsFR}>-0z{iUgkH=VBY)WEZ|IXl!X}ZSCkD8toq%8yguM
zY^-P*8SZXusCku${G)+4W#(~8?#rCUhWh6AzTvUKq0b}zZPnE!l|92FZ8aa>zP$FS
zJ>_9x6mUQ3xS#Xlc}{&zbz@Wc+v@g#fx*_dMftfEpNB@uUcF6wc=b6_#d7$wk56Cb
z=e=rbXlko^Sy)ol{IRLw!^`KHZ$1qUye+RP`Qs)D+a_R<^CphR#oxaDS3yBZZBcf4
zYjt&Pb5m)0M%IVn!LFwI=B}KS^y!+m9xT<e%5WBjUr9`Tl>ffIy|KQ%e|T_su=7Ly
zi`N6AW1m~9yZb*ObOeAl0{CcFt_qBgzViF!r1YYihWf_h;;MHqa|$vZ7WWSfj`Y-(
zjZ8E{JgXhXml|)PLP8G5CEP8js;jR_{w@Am{E4%P^<ACa?WLXF_1!aBPakDO!WL{h
zB8KjrJbnKBgOr?x_U_*9s%IDcBhu@7x(EAu({I$z^mqJ;nre%?FlLg!=jh?%8F(h{
z&g;sL-MuZ<RrO6D8=C662YM?XJ|CX`c>T|JH34TaW}*>H_uc2aKj!@9+~)SsHpce0
z=H~Y9p5E8VEu^V8NoB+_jG40Mn28P#pp+kZ@`UoAMTL*^Ynnsb*0;8{G&QB3JbpTF
z92F!EJZR}n`N~X3so-W}{O{$35rG$PrM!GoR$gB5zT{r`vExyP&zDA4_xV?$!FT89
zl|vu;9vI0PAH1DiSW#15S(1Lv&)3u4!)y2MOYLa1|1)ZR_U!4;%%rNSW53%!V6Us4
zmAQ?r{TF8P&Ft?V?|q-X6f>!)s;XnmffZX8-<fHTdCF`}QPR@Pgk^OdeFH<|@64n_
zW~Qc+^xyuM%oHrJB~&_ij+r#Ie`coBVT+lm$dY%Cnf_fhG(CyNt|{~WyJ~29W^`zV
zgjx;@|C?%P22FN#-Hu2{vqcX7SPd2Sk)~@bWDiYDqRCtTq8b`Qt;KzlQ&ThlL`>H*
zn@DJCa+Wys_r!GaUVay8cJv>J>0-*e|LSVUZSK^a`ae2#|Nm-uJb{4!<!S%V4Y%mS
zuT`-fH^p*B1!q6MU2;szC<qG<OXu1GZ35LAR;}}uBeMwGnN-B7vd~fCe|^k}r=;VS
zJKTU~r>C$+oFP>OD=@F5z%SVWPoQ4M=5qDw#LW2A-0?eca$<J0)Ln>$W{Dn{E6djk
zs3*^iO-+2gESM$|CnjckgJd{XQZDL%!%<K(ikK8mfBvpc?l3hv-RvmNOhfT=5Sac7
zMp4W6#L=lA`qRXz$<e7xioA=W#^LZ(t2kwkd>+Nj&(p^SAU@OD%CLNqJ{GN@e%8$B
ze08$^81a>rz<O%JPZN)$pkbD<Ynb>tH$OT|j<?4tvi=nDoc=~}o4U!d`R>n6>iFax
z3m$qZ!oqWL90e_llvDfoPxi_BV^^GnS5YmpPs<{)qlxU#j}9{=((Ke^Z=9>vs-ISX
z9Pe5|{ioBPCx7VA9n+IglaIY62mGv$Uq;WTe4hAYxxOF!Pi4F7s;+>a&+RE_*d#qV
zC%y(e@0x;y`cIp%<0*d9CoE^!s_|fY1k)!jyb+ltp$ac590l&@0hq-d8)rtz_g;%G
zF{ZC=1Oj1!K8|uFyKG?F#G*ToFFl%^@G$S<X>Lwev+-X<OQ@6YfDp=-T?j`C3ShbP
zX=(krJE4(Snjh0dpaih1Tk3A;Tbr3dr@!B1VXlciMreA`h0eS-jq<<Rryu}R<RIC?
z-!&q7{pH^QDFA0(gJ!1xR-1g)6ibbtyVMd&L!_nVabCk#t@{`U6*=pzIX?PNu3!r=
zo~%ue%?TbP>;8=^?>sim-7^v(4Nd%mE3>HkgedjbWJ+0t{?-@LEFwPAWB4}lIGWtW
z*JSgwG1!#4v;VmA#8W%;&ASv^$`!E{^Yc0dy<Qq|=^ogZqe=Q}C>LT2PsQO~IXCd)
zS$XTmO0_sS6IM!}TjUgZfli3i5SGgRI0^=%$K&&RU?0aG>e5pzq5S1_Ue2-a1NP36
zzPs|ygN^EMI1)#ol{|wvg(WExhWDNr(WPCP*MpM5=n46MK}m?1q`T&quKZ}^naUd+
i@6X0&<=@dYVc8~CLlw4VOLO8<PW?;0c|HEV%l`oIl7D6Z
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5d4a61dc92ba5b08b2ff0587fb2877e7007c940e
GIT binary patch
literal 4286
zc%0Q&3s{ZW8ead7ahqx=DVc1QilXbT=%Nc@cU3CgBuyz2rp&1`(}>*0ka(COlySO>
zG)j$2rMpWRl1?h!>0&4`?Jn&1tbZFG%#6c1<2=uK_w%*ZUjO>n`>pSLzwg-*!q5+w
zOW408<r6|=2q9|J=n^p_&(}qu&&G{;ZvT%seE2W{AToCTzW~dnp`r0|K7amv;^^r3
z+8#eYzYoIA^0J)n?(RfVQj%Q~)6~@b#K6E{9K}a%ZEZwCLPEsP?)$5GEi5cvEHTQ;
z%3K>8n_$XyYVF#!+5Z0iTP!Utb?CF<>^^q?!i5V-Y;5db!q4JXbv2Qal9HS=XU-yT
zZ}04=sHn$@iHXR_$UuC2{9t5cWQC`vXONnj+N2lnhK7cU#{V=P?0YUQF6?xKo}Qk*
zi>vE__3PKS$Hm2=yu2J8?H$604<DkTp#izMxj1m(KrelM+TPyYR!&Y%!ra{arAD`I
z-TF>`uzOfdDJUqgUl}f!t6*(o6S8vU%Ind)cR$U|&qwFoyATKjc>LsvXzcIrM^{&u
z$fwh%Ph;oKoh^ZZfibgZ&(@-64409SArTP~@669?jA}`o=F-W>$0ujgrcHe()6-E`
za~sWh1!(#D8wg?(G0^=8g1$afRaIg8_U#A`4n|5!im<4t2+7IGLu^jGyu8-v>guvN
zVYIZgUgP;Q{xM_5h&eeqd4+|AHPhNgMMVXAy6y{mw(r3aLvuuH8R3D`3Ut-fA~7iu
zt}ZSxF)@LenHg+tZ9)CT`Sa(6Y)%dyJot#}AcL;kN=r+Nv**jo%D&9Yn_%Na!P!NT
zmzO7OZEbzV#(1cI0KG?&(CWP!4KW9CAvFyv0s>()+X%+S#-Oo+m6a8!?!o8tv258g
zu)edg$jr<X(&O4_4rAy(T{b7vr%xC0)0n;H#|zEUca}6aH+KvS46ykXvOWwB4x)y}
zdh?boFr$1tJv|XOdv98Qp#2Ee*4Ci<!=gotK(&kT@NkhPii?Xu>xHe8YcwZ8>gwt*
z>*Q5_6%`fHNb@+Nva)hvXJ_Z>zP`R+>4?LJ4hh+_X&%Aj@j&$@iaXUg=FOcex;q<x
z2L}f@J3GV8%?(sbpnU>+_UysMix*$yr1y*U>ocj&t(BCN#=Oc;-w};8UW~7=FDWT0
znb6eKw7RUUtZM1frB8KqbU^haG&D4Xk&zK-KcKO<gcZFzpKlF2J3BZzIf^vFa?@S`
zdXC5kHWo=qNw{25jvuqLv1?a!-SXwj?f*!J)PItmp3bnoQQtY$)z#{Sg@rLx=l5vu
zgMq%j(A3lz3(Za8Xl?}exyJC}o5R;(0lZxu;py%MFE3A#4*dQ75D*Z6HNmT~ci%oV
zW}ib>`k7~CG$&LeY{Hp0`B@#%zCw2F*g>fNncws}s`mD6{)ywqe+pR{*k`S+C3K$h
zDMBX8AbP3{j?9$D5uPGqjA!6$y9EgKTaJ*RKzts&3Xu_CBJNNs>h3fO>k^Mc5FIP*
zsI99hC@Y&o_3($a$b$A+(YW8kgE?kDPb}o)5hIiR{S6X4T+c?&P!(?GO5q6i6J)54
zLypFHWNXMEM{5GIOqCGlV}re4t;3m=cr;dCL19)ds!H-vai!$J{Ra=@>gwzCO8!|b
z_V4=AM>Ia+qX{Q$+~|=J6UR-8l95`sSAIfssg9bk#zGx8%oK5Uh75jCmO`qk3~~)7
zqS!(KrOq>OGj0PK%ky!o^gQY-@`oPY`T0tFW9^a)=W`{dSy>YK(WBqm7v-siQT`3G
zV#Bt|jg{ItQQAB~S^n}>!)Z@j9ktQssfBXBD!$i{gXefjxQ-XcA$tFt&T43Jn}+%@
zM_f4(Cv2-Mz4!P|`QgXa`P$nyN0SF-rA%U4nuz0V{5;BicFJ(h9#zSahg78}B~FtG
zEizR4rP*2=tqwY9TBL~^3)E0LPZe2a%Gjqh5#K6}$4wIj{9><yj#U=VT2CD)>nu3_
zw?_qWykCnCk0{B=Vr~}}ksmWN-x_xoXFn#)3EpH*iovJirwkR;@@H}5YUXJOniuP$
zn#R6mi3ZwTRnY0ChW0=nuJM(TG<5>XW=}!S4nFR0GwZ93Sa_f#b)$7h#x}X5s|+~T
zb;tis?|+6EX~G=$oFrCYJzb*EMs;STqe@!S60Lz&Z(USbs-wn19j$)K=m=88-OsqF
z;wzv)Umg|Izn*{ip)=Y}aCNy+a_!!ACYe6Q5@!NsIlbK9*V~^VO`b3pyc9VlwyNUA
zcGJ{uIjY39cxVbb{dnl|(LwiD=IGsLg(u$`;L$hwxVKCV^?WYs-SyCYYz4Z1_^kJK
z@ci915gsb%9JELMykDII#dqfqlpxF{hjE;28~KrWe5Fa(Y!pN47OQvOTgDUKbJ9hJ
z$86mH(hEJ`dq8mB7lPB)=-sP{F8^tGbktwilNxllEymloA<9$kdbr0&4F^q_P?dM*
zpFyodpD+o=Q-_~1Rglg#=2}%+DP3>(*B-pT+z|Jd&O&cu1O(SkVz4d?gEziGe~v32
z$GV{Bc-Y{bEiNThU%Sq^8SXsx`tE>X!XRU2r{ue`6B0n}AgzIN)0tvvhKiEe2C6zm
zJjK+kSqhEy?wUj0p?vgTj>prk8frH&bSDx0=iJeg;5&3DYDrb;VuQe3@451qoGeB(
zMj0?LruFvj^djg}8%k4UlHE;)o$xdiFAUJv+%iknGsj-xLRW%s&p>10vuBSwgwJ}r
zghTf#pLU<w*xtC`FX7T^3x2+jiPTBoxx<UTGhtBmocn$GJ*jp3>5w#Q4WdnBQTPSn
zWH_pfN}9`6zZA*~Xi3=^_n_oNPVe<|nGf?~V`{hAIb991l`r<0H9S>)3a65ANV&HW
zLHyYF=6(}XCbRvDkT3Lz=q9E_T<oX$lgCtVc9v{85+ZRcoIh&1u^V%Ahcc6HMz=i4
zAeY8Zdi$a80W(=bdQ+dr@SG)|9Q(TqQ=f2#2_l9By9A`JGK;y8AI{jg`IF}!ei*Rl
z=2%&gV{2_i{rlVB)*1?qlDy2%$<k%MABg#nkxc&kyW$0|QJazvVWc;Ph!lPyqtHV*
z8zKpbr?%!IArp?$->`JfT%y)Qt)Kos`t<oInp2zH^NRJqZgUVxf6_#b0Gg-?H;8Ee
E1?Jf~jsO4v
--- a/b2g/branding/unofficial/configure.sh
+++ b/b2g/branding/unofficial/configure.sh
@@ -1,7 +1,6 @@
 # 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/.
 
-ANDROID_PACKAGE_NAME=org.mozilla.b2g_`echo $USER | sed 's/-/_/g'`
 MOZ_APP_DISPLAYNAME=B2G
 MOZ_UPDATER=
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c4307fc8418436bb6b2fd3a6afc702c2db28aa77
GIT binary patch
literal 4762
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
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="2eda36a4795012a5d1275b77ebb20ac377c8cd26">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="bc5bfa18f795919b56b952bbf3637c235d0e13dc"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3692ad2cf3a047cc66f58f655a56b6ec559ef253"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
@@ -123,22 +123,22 @@
   <project name="platform/system/security" path="system/security" revision="ee8068b9e7bfb2770635062fc9c2035be2142bd8"/>
   <project name="platform/system/vold" path="system/vold" revision="bb33b1ce8ad9cd3fc4311801b4d56db1d5c8175b"/>
   <!--original fetch url was http://sprdsource.spreadtrum.com:8085/b2g/android-->
   <remote fetch="https://git.mozilla.org/external/sprd-aosp" name="sprd-aosp"/>
   <default remote="sprd-aosp" revision="sprdb2g_gonk4.4" sync-j="4"/>
   <!-- Stock Android things -->
   <project name="platform/external/icu4c" path="external/icu4c" revision="2bb01561780583cc37bc667f0ea79f48a122d8a2"/>
   <!-- dolphin specific things -->
-  <project name="device/sprd" path="device/sprd" revision="a26ba0ab998133ad590102be1e5950818b86ce82"/>
+  <project name="device/sprd" path="device/sprd" revision="e73ddda9fede64134d949050e3fc53bd4121cb30"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="39a5b5bcadad745df3e6882316fce426f98b1669"/>
   <project name="platform/frameworks/av" path="frameworks/av" revision="8bb69db127112fc66da75f8ca7a1158614b919f6"/>
   <project name="platform/hardware/akm" path="hardware/akm" revision="6d3be412647b0eab0adff8a2768736cf4eb68039"/>
   <project groups="invensense" name="platform/hardware/invensense" path="hardware/invensense" revision="e6d9ab28b4f4e7684f6c07874ee819c9ea0002a2"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="865ce3b4a2ba0b3a31421ca671f4d6c5595f8690"/>
-  <project name="kernel/common" path="kernel" revision="8696b132455224412a6725022acf672be80a0641"/>
+  <project name="kernel/common" path="kernel" revision="0f36762ab0c1d8ce10c6a5eda948b05d5d6cc379"/>
   <project name="platform/system/core" path="system/core" revision="7992618bd4ee33ce96897675a5c0a9b619122f13"/>
   <project name="u-boot" path="u-boot" revision="f1502910977ac88f43da7bf9277c3523ad4b0b2f"/>
   <project name="vendor/sprd/gps" path="vendor/sprd/gps" revision="7d6e1269be7186b2073fa568958b357826692c4b"/>
   <project name="vendor/sprd/open-source" path="vendor/sprd/open-source" revision="e503b1d14d7fdee532b8f391407299da193c1b2d"/>
   <project name="vendor/sprd/partner" path="vendor/sprd/partner" revision="8649c7145972251af11b0639997edfecabfc7c2e"/>
   <project name="vendor/sprd/proprietories" path="vendor/sprd/proprietories" revision="d2466593022f7078aaaf69026adf3367c2adb7bb"/>
 </manifest>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="bc5bfa18f795919b56b952bbf3637c235d0e13dc"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3692ad2cf3a047cc66f58f655a56b6ec559ef253"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9a9797062c6001d6346504161c51187a2968466b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="8c2d32bccc7061e9ca0165135457c3fd53e7107e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="bc5bfa18f795919b56b952bbf3637c235d0e13dc"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3692ad2cf3a047cc66f58f655a56b6ec559ef253"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="2eda36a4795012a5d1275b77ebb20ac377c8cd26">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="bc5bfa18f795919b56b952bbf3637c235d0e13dc"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3692ad2cf3a047cc66f58f655a56b6ec559ef253"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="bc5bfa18f795919b56b952bbf3637c235d0e13dc"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3692ad2cf3a047cc66f58f655a56b6ec559ef253"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="bc5bfa18f795919b56b952bbf3637c235d0e13dc"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3692ad2cf3a047cc66f58f655a56b6ec559ef253"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9a9797062c6001d6346504161c51187a2968466b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="8c2d32bccc7061e9ca0165135457c3fd53e7107e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="2eda36a4795012a5d1275b77ebb20ac377c8cd26">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="bc5bfa18f795919b56b952bbf3637c235d0e13dc"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3692ad2cf3a047cc66f58f655a56b6ec559ef253"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
@@ -120,17 +120,17 @@
   <project name="platform/hardware/libhardware_legacy" path="hardware/libhardware_legacy" revision="76c4bf4bc430a1b8317f2f21ef735867733e50cc"/>
   <project name="platform/system/media" path="system/media" revision="c1332c21c608f4932a6d7e83450411cde53315ef"/>
   <!--original fetch url was git://github.com/t2m-foxfone/-->
   <remote fetch="https://git.mozilla.org/external/t2m-foxfone" name="t2m"/>
   <default remote="caf" revision="LNX.LA.3.5.2.1.1" sync-j="4"/>
   <!-- Flame specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="1bb28abbc215f45220620af5cd60a8ac1be93722"/>
   <project name="device/qcom/common" path="device/qcom/common" revision="2501e5940ba69ece7654ff85611c76ae5bda299c"/>
-  <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="a9f3f8fb8b0844724de32426b7bcc4e6dc4fa2ed"/>
+  <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="040bb1e9ac8a5b6dd756fdd696aa37a8868b5c67"/>
   <project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="0865bc4134b67220df4058625fba29305d6b10c3"/>
   <project name="kernel_lk" path="bootable/bootloader/lk" remote="b2g" revision="fda40423ffa573dc6cafd3780515010cb2a086be"/>
   <project name="platform_bootable_recovery" path="bootable/recovery" remote="b2g" revision="d5e53ed6f22fa06052351dc03510af9473af01ea"/>
   <project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="d61fc97258c8b0c362430dd2eb195dcc4d266f14"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="5b71e40213f650459e95d35b6f14af7e88d8ab62"/>
   <project name="platform_external_libnfc-nci" path="external/libnfc-nci" remote="t2m" revision="4186bdecb4dae911b39a8202252cc2310d91b0be"/>
   <project name="platform_external_libnfc-pn547" path="external/libnfc-pn547" remote="b2g" revision="5bb999b84b8adc14f6bea004d523ba258dea8188"/>
   <project name="platform/frameworks/av" path="frameworks/av" revision="65f5144987afff35a932262c0c5fad6ecce0c04a"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="bc5bfa18f795919b56b952bbf3637c235d0e13dc"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3692ad2cf3a047cc66f58f655a56b6ec559ef253"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "bc5bfa18f795919b56b952bbf3637c235d0e13dc", 
+        "git_revision": "3692ad2cf3a047cc66f58f655a56b6ec559ef253", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "12e33f70aeccca27a275b3fa172876a07b891fe8", 
+    "revision": "5cf33f632627cefff6e9809144064600fed4c088", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="bc5bfa18f795919b56b952bbf3637c235d0e13dc"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3692ad2cf3a047cc66f58f655a56b6ec559ef253"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="bc5bfa18f795919b56b952bbf3637c235d0e13dc"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3692ad2cf3a047cc66f58f655a56b6ec559ef253"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1856,17 +1856,17 @@ pref("media.gmp-provider.enabled", true)
 
 pref("browser.apps.URL", "https://marketplace.firefox.com/discovery/");
 
 #ifdef NIGHTLY_BUILD
 pref("browser.polaris.enabled", false);
 pref("privacy.trackingprotection.ui.enabled", false);
 #endif
 
-#ifdef E10S_TESTING_ONLY
+#ifdef NIGHTLY_BUILD
 // At the moment, autostart.2 is used, while autostart.1 is unused.
 // We leave it here set to false to reset users' defaults and allow
 // us to change everybody to true in the future, when desired.
 pref("browser.tabs.remote.autostart.1", false);
 pref("browser.tabs.remote.autostart.2", true);
 #endif
 
 #ifdef E10S_TESTING_ONLY
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -159,35 +159,49 @@ const gXPInstallObserver = {
         messageString = messageString.replace("#4", Services.appinfo.version);
 
         PopupNotifications.show(browser, notificationID, messageString, anchorID,
                                 action, null, options);
       }
       this._removeProgressNotification(browser);
       break; }
     case "addon-install-confirmation": {
+      let unsigned = installInfo.installs.filter(i => i.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING);
+      let someUnsigned = unsigned.length > 0 && unsigned.length < installInfo.installs.length;
+
       options.eventCallback = (aEvent) => {
         switch (aEvent) {
           case "removed":
             if (installInfo) {
               for (let install of installInfo.installs)
                 install.cancel();
             }
             this.acceptInstallation = null;
             break;
           case "shown":
             let addonList = document.getElementById("addon-install-confirmation-content");
             while (addonList.firstChild)
               addonList.firstChild.remove();
 
             for (let install of installInfo.installs) {
+              let container = document.createElement("hbox");
+
               let name = document.createElement("label");
               name.setAttribute("value", install.addon.name);
               name.setAttribute("class", "addon-install-confirmation-name");
-              addonList.appendChild(name);
+              container.appendChild(name);
+
+              if (someUnsigned && install.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
+                let unsigned = document.createElement("label");
+                unsigned.setAttribute("value", gNavigatorBundle.getString("addonInstall.unsigned"));
+                unsigned.setAttribute("class", "addon-install-confirmation-unsigned");
+                container.appendChild(unsigned);
+              }
+
+              addonList.appendChild(container);
             }
 
             this.acceptInstallation = () => {
               for (let install of installInfo.installs)
                 install.install();
               installInfo = null;
 
               Services.telemetry
@@ -196,17 +210,30 @@ const gXPInstallObserver = {
             };
             break;
         }
       };
 
       options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") +
                              "find-and-install-add-ons";
 
-      messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
+      if (unsigned.length == installInfo.installs.length) {
+        // None of the add-ons are verified
+        messageString = gNavigatorBundle.getString("addonConfirmInstallUnsigned.message");
+      }
+      else if (unsigned.length == 0) {
+        // All add-ons are verified or don't need to be verified
+        messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
+      }
+      else {
+        // Some of the add-ons are unverified, the list of names will indicate
+        // which
+        messageString = gNavigatorBundle.getString("addonConfirmInstallSomeUnsigned.message");
+      }
+
       messageString = PluralForm.get(installInfo.installs.length, messageString);
       messageString = messageString.replace("#1", brandShortName);
       messageString = messageString.replace("#2", installInfo.installs.length);
 
       let cancelButton = document.getElementById("addon-install-confirmation-cancel");
       cancelButton.label = gNavigatorBundle.getString("addonInstall.cancelButton.label");
       cancelButton.accessKey = gNavigatorBundle.getString("addonInstall.cancelButton.accesskey");
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -768,16 +768,24 @@ window[chromehidden~="toolbar"] toolbar:
 #bad-content-notification {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#bad-content-notification");
 }
 
 #click-to-play-plugins-notification {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#click-to-play-plugins-notification");
 }
 
+#password-fill-notification {
+  -moz-binding: url("chrome://browser/content/urlbarBindings.xml#password-fill-notification");
+}
+
+.login-fill-item {
+  -moz-binding: url("chrome://passwordmgr/content/login.xml#login");
+}
+
 .plugin-popupnotification-centeritem {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#plugin-popupnotification-center-item");
 }
 
 browser[tabmodalPromptShowing] {
   -moz-user-focus: none !important;
 }
 
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -57,16 +57,23 @@
     <popupnotification id="password-notification" hidden="true">
       <popupnotificationcontent orient="vertical">
         <textbox id="password-notification-username"/>
         <textbox id="password-notification-password" type="password"
                  disabled="true"/>
       </popupnotificationcontent>
     </popupnotification>
 
+    <vbox id="login-fill-doorhanger" hidden="true">
+      <description id="login-fill-testing"
+                   value="Thanks for testing the login fill doorhanger!"/>
+      <textbox id="login-fill-filter"/>
+      <richlistbox id="login-fill-list"/>
+    </vbox>
+
 #ifdef E10S_TESTING_ONLY
     <popupnotification id="enable-e10s-notification" hidden="true">
       <popupnotificationcontent orient="vertical"/>
     </popupnotification>
 #endif
 
     <popupnotification id="addon-progress-notification" hidden="true">
       <popupnotificationcontent orient="vertical">
--- a/browser/base/content/test/general/browser_bug553455.js
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -450,16 +450,120 @@ function test_multiple() {
   var triggers = encodeURIComponent(JSON.stringify({
     "Unsigned XPI": "unsigned.xpi",
     "Restartless XPI": "restartless.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
+function test_someunverified() {
+  // This test is only relevant if using the new doorhanger UI and allowing
+  // unsigned add-ons
+  if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
+      Preferences.get("xpinstall.signatures.required", true)) {
+    runNextTest();
+    return;
+  }
+
+  // Wait for the progress notification
+  wait_for_progress_notification(function(aPanel) {
+    // Wait for the install confirmation dialog
+    wait_for_install_dialog(function() {
+      let notification = document.getElementById("addon-install-confirmation-notification");
+      let message = notification.getAttribute("label");
+      is(message, "Caution: This site would like to install 2 add-ons in " + gApp + ", some of which are unverified. Proceed at your own risk.",
+         "Should see the right message");
+
+      let container = document.getElementById("addon-install-confirmation-content");
+      is(container.childNodes.length, 2, "Should be two items listed");
+      is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+      is(container.childNodes[0].lastChild.getAttribute("class"),
+         "addon-install-confirmation-unsigned", "Should have the unverified marker");
+      is(container.childNodes[1].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on");
+      is(container.childNodes[1].childNodes.length, 1, "Shouldn't have the unverified marker");
+
+      // Wait for the complete notification
+      wait_for_notification("addon-install-complete", function(aPanel) {
+        AddonManager.getAddonsByIDs(["restartless-xpi@tests.mozilla.org",
+                                     "theme-xpi@tests.mozilla.org"], function([a, t]) {
+          a.uninstall();
+          // Installing a new theme tries to switch to it, switch back to the
+          // default theme.
+          t.userDisabled = true;
+          t.uninstall();
+
+          Services.perms.remove("example.com", "install");
+          wait_for_notification_close(runNextTest);
+          gBrowser.removeTab(gBrowser.selectedTab);
+        });
+      });
+
+      accept_install_dialog();
+    });
+  });
+
+  var pm = Services.perms;
+  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+  var triggers = encodeURIComponent(JSON.stringify({
+    "Extension XPI": "restartless.xpi",
+    "Theme XPI": "theme.xpi"
+  }));
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_allunverified() {
+  // This test is only relevant if using the new doorhanger UI and allowing
+  // unsigned add-ons
+  if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
+      Preferences.get("xpinstall.signatures.required", true)) {
+    runNextTest();
+    return;
+  }
+
+  // Wait for the progress notification
+  wait_for_progress_notification(function(aPanel) {
+    // Wait for the install confirmation dialog
+    wait_for_install_dialog(function() {
+      let notification = document.getElementById("addon-install-confirmation-notification");
+      let message = notification.getAttribute("label");
+      is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk.");
+
+      let container = document.getElementById("addon-install-confirmation-content");
+      is(container.childNodes.length, 1, "Should be one item listed");
+      is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+      is(container.childNodes[0].childNodes.length, 1, "Shouldn't have the unverified marker");
+
+      // Wait for the complete notification
+      wait_for_notification("addon-install-complete", function(aPanel) {
+        AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(aAddon) {
+          aAddon.uninstall();
+
+          Services.perms.remove("example.com", "install");
+          wait_for_notification_close(runNextTest);
+          gBrowser.removeTab(gBrowser.selectedTab);
+        });
+      });
+
+      accept_install_dialog();
+    });
+  });
+
+  var pm = Services.perms;
+  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+  var triggers = encodeURIComponent(JSON.stringify({
+    "Extension XPI": "restartless.xpi"
+  }));
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
 function test_url() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the install confirmation dialog
     wait_for_install_dialog(function() {
       // Wait for the complete notification
       wait_for_notification("addon-install-complete", function(aPanel) {
         let notification = aPanel.childNodes[0];
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -2768,16 +2768,26 @@ file, You can obtain one at http://mozil
     </implementation>
     <handlers>
       <!-- The _accept method checks for .defaultPrevented so that if focus is in a button,
            enter activates the button and not this default action -->
       <handler event="keypress" keycode="VK_RETURN" group="system" action="this._accept(event);"/>
     </handlers>
   </binding>
 
+  <!-- This is the XBL notification definition for the login fill doorhanger,
+       which is empty because the actual panel is not implemented inside an XBL
+       binding, but made of elements added to the notification panel. This
+       allows accessing the full structure while the panel is hidden. -->
+  <binding id="password-fill-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
+    <content>
+      <children/>
+    </content>
+  </binding>
+
   <binding id="splitmenu">
     <content>
       <xul:hbox anonid="menuitem" flex="1"
                 class="splitmenu-menuitem"
                 xbl:inherits="iconic,label,disabled,onclick=oncommand,_moz-menuactive=active"/>
       <xul:menu anonid="menu" class="splitmenu-menu"
                 xbl:inherits="disabled,_moz-menuactive=active"
                 oncommand="event.stopPropagation();">
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -327,16 +327,17 @@ const PanelUI = {
       if (evt.defaultPrevented) {
         return;
       }
 
       let tempPanel = document.createElement("panel");
       tempPanel.setAttribute("type", "arrow");
       tempPanel.setAttribute("id", "customizationui-widget-panel");
       tempPanel.setAttribute("class", "cui-widget-panel");
+      tempPanel.setAttribute("viewId", aViewId);
       if (this._disableAnimations) {
         tempPanel.setAttribute("animate", "false");
       }
       tempPanel.setAttribute("context", "");
       document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
       // If the view has a footer, set a convenience class on the panel.
       tempPanel.classList.toggle("cui-widget-panelWithFooter",
                                  viewNode.querySelector(".panel-subview-footer"));
--- a/browser/components/loop/.eslintignore
+++ b/browser/components/loop/.eslintignore
@@ -6,9 +6,20 @@ modules/MozLoopWorker.js
 modules/MozLoopAPI.jsm
 # Libs we don't need to check
 content/libs
 content/shared/libs
 standalone/content/libs
 standalone/node_modules
 # Libs we don't need to check
 test/shared/vendor
-
+# These are generated react files that we don't need to check
+content/js/contacts.js
+content/js/conversation.js
+content/js/conversationViews.js
+content/js/panel.js
+content/js/roomViews.js
+content/shared/js/feedbackViews.js
+content/shared/js/views.js
+standalone/content/js/fxOSMarketplace.js
+standalone/content/js/standaloneRoomViews.js
+standalone/content/js/webapp.js
+ui/ui-showcase.js
--- a/browser/components/loop/.eslintrc
+++ b/browser/components/loop/.eslintrc
@@ -48,17 +48,16 @@
     "no-extra-bind": 0,           // Leave as 0
     "no-extra-boolean-cast": 0,   // TODO: Remove (use default)
     "no-multi-spaces": 0,         // TBD.
     "no-new": 0,                  // TODO: Remove (use default)
     "no-redeclare": 0,            // TODO: Remove (use default)
     "no-return-assign": 0,        // TODO: Remove (use default)
     "no-shadow": 0,               // TODO: Remove (use default)
     "no-spaced-func": 0,          // TODO: Remove (use default)
-    "no-trailing-spaces": 0,      // TODO: Remove (use default)
     "no-undef": 0,                // TODO: Remove (use default)
     "no-underscore-dangle": 0,    // Leave as 0. Commonly used for private variables.
     "no-unused-expressions": 0,   // TODO: Remove (use default)
     "no-unused-vars": 0,          // TODO: Remove (use default)
     "no-use-before-define": 0,    // TODO: Remove (use default)
     "no-wrap-func": 0,            // TODO: Remove (use default)
     "quotes": 0,                  // [2, "double", "avoid-escape"],
     "space-infix-ops": 0,         // TODO: Remove (use default)
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -507,17 +507,18 @@ loop.roomViews = (function(mozL10n) {
                   valueLink: this.linkState("newRoomURL")}), 
                 React.createElement("textarea", {rows: "4", type: "text", className: "room-context-comments", 
                   onBlur: this.handleFormSubmit, 
                   onKeyDown: this.handleTextareaKeyDown, 
                   placeholder: mozL10n.get("context_edit_comments_placeholder"), 
                   valueLink: this.linkState("newRoomDescription")})
               ), 
               React.createElement("button", {className: "room-context-btn-close", 
-                      onClick: this.handleCloseClick})
+                      onClick: this.handleCloseClick, 
+                      title: mozL10n.get("cancel_button")})
             )
           )
         );
       }
 
       if (!locationData) {
         return null;
       }
@@ -532,19 +533,21 @@ loop.roomViews = (function(mozL10n) {
             React.createElement("a", {className: "room-context-url", 
                href: location, 
                target: "_blank", 
                title: locationData.location}, locationData.hostname), 
             this.props.roomData.roomDescription ?
               React.createElement("div", {className: "room-context-comment"}, this.props.roomData.roomDescription) :
               null, 
             React.createElement("button", {className: "room-context-btn-close", 
-                    onClick: this.handleCloseClick}), 
+                    onClick: this.handleCloseClick, 
+                    title: mozL10n.get("context_hide_tooltip")}), 
             React.createElement("button", {className: "room-context-btn-edit", 
-                    onClick: this.handleEditClick})
+                    onClick: this.handleEditClick, 
+                    title: mozL10n.get("context_edit_tooltip")})
           )
         )
       );
     }
   });
 
   /**
    * Desktop room conversation view.
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -507,17 +507,18 @@ loop.roomViews = (function(mozL10n) {
                   valueLink={this.linkState("newRoomURL")} />
                 <textarea rows="4" type="text" className="room-context-comments"
                   onBlur={this.handleFormSubmit}
                   onKeyDown={this.handleTextareaKeyDown}
                   placeholder={mozL10n.get("context_edit_comments_placeholder")}
                   valueLink={this.linkState("newRoomDescription")} />
               </form>
               <button className="room-context-btn-close"
-                      onClick={this.handleCloseClick}/>
+                      onClick={this.handleCloseClick}
+                      title={mozL10n.get("cancel_button")}/>
             </div>
           </div>
         );
       }
 
       if (!locationData) {
         return null;
       }
@@ -532,19 +533,21 @@ loop.roomViews = (function(mozL10n) {
             <a className="room-context-url"
                href={location}
                target="_blank"
                title={locationData.location}>{locationData.hostname}</a>
             {this.props.roomData.roomDescription ?
               <div className="room-context-comment">{this.props.roomData.roomDescription}</div> :
               null}
             <button className="room-context-btn-close"
-                    onClick={this.handleCloseClick}/>
+                    onClick={this.handleCloseClick}
+                    title={mozL10n.get("context_hide_tooltip")}/>
             <button className="room-context-btn-edit"
-                    onClick={this.handleEditClick}/>
+                    onClick={this.handleEditClick}
+                    title={mozL10n.get("context_edit_tooltip")}/>
           </div>
         </div>
       );
     }
   });
 
   /**
    * Desktop room conversation view.
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -728,17 +728,19 @@ BrowserGlue.prototype = {
 
 #ifndef RELEASE_BUILD
     let themeName = gBrowserBundle.GetStringFromName("deveditionTheme.name");
     let vendorShortName = gBrandBundle.GetStringFromName("vendorShortName");
 
     LightweightThemeManager.addBuiltInTheme({
       id: "firefox-devedition@mozilla.org",
       name: themeName,
+#ifdef XP_WIN
       accentcolor: "transparent",
+#endif
       headerURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.header.png",
       iconURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.icon.png",
       author: vendorShortName,
     });
 #endif
 
 #ifdef MOZ_CRASHREPORTER
     TabCrashReporter.init();
@@ -2784,18 +2786,21 @@ let E10SUINotification = {
   // e10s testing period to Nightly users.
   CURRENT_NOTICE_COUNT: 4,
   CURRENT_PROMPT_PREF: "browser.displayedE10SPrompt.1",
   PREVIOUS_PROMPT_PREF: "browser.displayedE10SPrompt",
 
   checkStatus: function() {
     let skipE10sChecks = false;
     try {
-      skipE10sChecks = (UpdateChannel.get() != "nightly") ||
-                       Services.prefs.getBoolPref("browser.tabs.remote.autostart.disabled-because-using-a11y");
+      let updateChannel = UpdateChannel.get();
+      let channelAuthorized = updateChannel == "nightly" || updateChannel == "aurora";
+
+      skipE10sChecks = !channelAuthorized ||
+                       UpdateServices.prefs.getBoolPref("browser.tabs.remote.autostart.disabled-because-using-a11y");
     } catch(e) {}
 
     if (skipE10sChecks) {
       return;
     }
 
     if (Services.appinfo.browserTabsRemoteAutostart) {
       let notice = 0;
@@ -2904,17 +2909,17 @@ let E10SUINotification = {
 
   _showE10SPrompt: function BG__showE10SPrompt() {
     let win = RecentWindow.getMostRecentBrowserWindow();
     if (!win)
       return;
 
     let browser = win.gBrowser.selectedBrowser;
 
-    let promptMessage = "Would you like to help us test multiprocess Nightly (e10s)? You can also enable e10s in Nightly preferences. Notable fixes:";
+    let promptMessage = "Multi-process is coming soon to Firefox. You can start using it now to get early access to some of the benefits:";
     let mainAction = {
       label: "Enable and Restart",
       accessKey: "E",
       callback: function () {
         Services.prefs.setBoolPref("browser.tabs.remote.autostart", true);
         Services.prefs.setBoolPref("browser.enabledE10SFromPrompt", true);
         // Restart the app
         let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
@@ -2937,19 +2942,18 @@ let E10SUINotification = {
       popupIconURL: "chrome://browser/skin/e10s-64@2x.png",
       learnMoreURL: "https://wiki.mozilla.org/Electrolysis",
       persistWhileVisible: true
     };
 
     win.PopupNotifications.show(browser, "enable-e10s", promptMessage, null, mainAction, secondaryActions, options);
 
     let highlights = [
-      "Less crashing!",
-      "Improved add-on compatibility and DevTools",
-      "PDF.js, Web Console, Spellchecking, WebRTC now work"
+      "Improved responsiveness",
+      "Fewer crashes"
     ];
 
     let doorhangerExtraContent = win.document.getElementById("enable-e10s-notification")
                                              .querySelector("popupnotificationcontent");
     for (let highlight of highlights) {
       let highlightLabel = win.document.createElement("label");
       highlightLabel.setAttribute("value", highlight);
       doorhangerExtraContent.appendChild(highlightLabel);
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -77,22 +77,28 @@ let gEditItemOverlay = {
   },
 
   get multiEdit() {
     return this.initialized && this._paneInfo.bulkTagging;
   },
 
   // Check if the pane is initialized to show only read-only fields.
   get readOnly() {
-    // Bug 1120314 - Folder shortcuts are read-only due to some quirky implementation
-    // details (the most important being the "smart" semantics of node.title).
-    return (!this.initialized ||
-            (!this._paneInfo.visibleRows.has("tagsRow") &&
-             (this._paneInfo.isFolderShortcut ||
-              this._paneInfo.isParentReadOnly)));
+    // TODO (Bug 1120314): Folder shortcuts are currently read-only due to some
+    // quirky implementation details (the most important being the "smart"
+    // semantics of node.title that makes hard to edit the right entry).
+    // This pane is read-only if:
+    //  * the panel is not initialized
+    //  * the node is a folder shortcut
+    //  * the node is not bookmarked
+    //  * the node is child of a read-only container and is not a bookmarked URI
+    return !this.initialized ||
+           this._paneInfo.isFolderShortcut ||
+           !this._paneInfo.isItem ||
+           (this._paneInfo.isParentReadOnly && !this._paneInfo.isBookmark);
   },
 
   // the first field which was edited after this panel was initialized for
   // a certain item
   _firstEditedField: "",
 
   _initNamePicker() {
     if (this._paneInfo.bulkTagging)
@@ -179,29 +185,35 @@ let gEditItemOverlay = {
         return !(this._element(rowId).collapsed = !visible);
       };
 
     if (showOrCollapse("nameRow", !bulkTagging, "name")) {
       this._initNamePicker();
       this._namePicker.readOnly = this.readOnly;
     }
 
-    if (showOrCollapse("locationRow", isURI, "location")) {
+    // In some cases we want to hide the location field, since it's not
+    // human-readable, but we still want to initialize it.
+    showOrCollapse("locationRow", isURI, "location");
+    if (isURI) {
       this._initLocationField();
-      this._locationField.readOnly = !this._paneInfo.isItem;
+      this._locationField.readOnly = !this.readOnly;
     }
 
-    if (showOrCollapse("descriptionRow",
-                       this._paneInfo.isItem && !this.readOnly,
+    // hide the description field for
+    if (showOrCollapse("descriptionRow", isItem && !this.readOnly,
                        "description")) {
       this._initDescriptionField();
+      this._descriptionField.readOnly = this.readOnly;
     }
 
-    if (showOrCollapse("keywordRow", isBookmark, "keyword"))
+    if (showOrCollapse("keywordRow", isBookmark, "keyword")) {
       this._initKeywordField();
+      this._keywordField.readOnly = this.readOnly;
+    }
 
     // Collapse the tag selector if the item does not accept tags.
     if (showOrCollapse("tagsRow", isURI || bulkTagging, "tags"))
       this._initTagsField().catch(Components.utils.reportError);
     else if (!this._element("tagsSelectorRow").collapsed)
       this.toggleTagsSelector().catch(Components.utils.reportError);
 
     // Load in sidebar.
@@ -350,17 +362,17 @@ let gEditItemOverlay = {
     this._folderMenuList.selectedItem = defaultItem;
 
     // Set a selectedIndex attribute to show special icons
     this._folderMenuList.setAttribute("selectedIndex",
                                       this._folderMenuList.selectedIndex);
 
     // Hide the folders-separator if no folder is annotated as recently-used
     this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6);
-    this._folderMenuList.disabled = this._readOnly;
+    this._folderMenuList.disabled = this.readOnly;
   },
 
   QueryInterface:
   XPCOMUtils.generateQI([Components.interfaces.nsIDOMEventListener,
                          Components.interfaces.nsINavBookmarkObserver]),
 
   _element(aID) document.getElementById("editBMPanel_" + aID),
 
@@ -382,17 +394,17 @@ let gEditItemOverlay = {
       this._observersAdded = false;
     }
 
     this._setPaneInfo(null);
     this._firstEditedField = "";
   },
 
   onTagsFieldChange() {
-    if (!this.readOnly) {
+    if (this._paneInfo.isURI || this._paneInfo.bulkTagging) {
       this._updateTags().then(
         anyChanges => {
           if (anyChanges)
             this._mayUpdateFirstEditField("tagsField");
         }, Components.utils.reportError);
     }
   },
 
--- a/browser/components/pocket/Pocket.jsm
+++ b/browser/components/pocket/Pocket.jsm
@@ -10,23 +10,38 @@ this.EXPORTED_SYMBOLS = ["Pocket"];
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
   "resource://gre/modules/ReaderMode.jsm");
 
 let Pocket = {
+  get site() Services.prefs.getCharPref("browser.pocket.site"),
+  get listURL() { return "https://" + Pocket.site; },
+
   /**
    * Functions related to the Pocket panel UI.
    */
   onPanelViewShowing(event) {
     let window = event.target.ownerDocument.defaultView;
+    window.addEventListener("popupshowing", Pocket.onPocketPanelShowing, true);
+    window.addEventListener("popupshown", Pocket.onPocketPanelShown, true);
+  },
+
+  onPocketPanelShowing(event) {
+    let window = event.target.ownerDocument.defaultView;
+    window.removeEventListener("popupshowing", Pocket.onPocketPanelShowing, true);
     window.pktUI.pocketButtonOnCommand(event);
-    window.pktUI.pocketPanelDidShow(event)
+  },
+
+  onPocketPanelShown(event) {
+    let window = event.target.ownerDocument.defaultView;
+    window.removeEventListener("popupshown", Pocket.onPocketPanelShown, true);
+    window.pktUI.pocketPanelDidShow(event);
   },
 
   onPanelViewHiding(event) {
     let window = event.target.ownerDocument.defaultView;
     window.pktUI.pocketPanelDidHide(event);
   },
 
   // Called on tab/urlbar/location changes and after customization. Update
--- a/browser/components/pocket/main.js
+++ b/browser/components/pocket/main.js
@@ -296,22 +296,20 @@ var pktUI = (function() {
             {
                 if (fxasignedin == '1')
                 {
                     startheight = 436;
                 }
             }
            showPanel("chrome://browser/content/pocket/panels/signup.html?pockethost=" + Services.prefs.getCharPref("browser.pocket.site") + "&fxasignedin=" + fxasignedin + "&variant=" + pktApi.getSignupAB(), {
                onShow: function() {
-                    resizePanel({
-                        width: 300,
-                        height: startheight
-                    });
                 },
                onHide: panelDidHide,
+               width: 300,
+               height: startheight
            });
         });
     }
 
     /**
      * Show the logged-out state / sign-up panel
      */
     function saveAndShowConfirmation(url, title) {
@@ -320,22 +318,16 @@ var pktUI = (function() {
         if (typeof url !== 'undefined' && url.startsWith("about:reader?url=")) {
             url = ReaderMode.getOriginalUrl(url);
         }
 
         var isValidURL = (typeof url !== 'undefined' && (url.startsWith("http") || url.startsWith('https')));
 
         showPanel("chrome://browser/content/pocket/panels/saved.html?pockethost=" + Services.prefs.getCharPref("browser.pocket.site") + "&premiumStatus=" + (pktApi.isPremiumUser() ? '1' : '0'), {
     		onShow: function() {
-                // Open and resize the panel
-                resizePanel({
-                        width: 350,
-                        height: 263
-                });
-
                 // Send error message for invalid url
                 if (!isValidURL) {
                     var error = new Error('Only links can be saved');
                     sendErrorMessage('saveLink', error);
                     return;
                 }
 
                 // Add url
@@ -364,16 +356,18 @@ var pktUI = (function() {
                 if (typeof title !== "undefined") {
                     options.title = title;
                 }
 
                 // Send the link
 				pktApi.addLink(url, options);
 			},
 			onHide: panelDidHide,
+            width: 350,
+            height: 267
     	});
     }
 
     /**
      * Open a generic panel
      */
     function showPanel(url, options) {
 
@@ -392,39 +386,38 @@ var pktUI = (function() {
     	// panel.setAttribute('noautohide', true);
     	// panel.setAttribute('consumeoutsideclicks', false);
     	//
 
     	// For some reason setting onpopupshown and onpopuphidden on the panel directly didn't work, so
     	// do it this hacky way for now
     	currentPanelDidShow = options.onShow;
     	currentPanelDidHide = options.onHide;
+
+        resizePanel({
+            width: options.width,
+            height: options.height
+        });
     }
 
     /**
      * Resize the panel
      * options = {
      * 	width: ,
      *	height: ,
      * 	animate [default false]
      * }
      */
     function resizePanel(options) {
         var iframe = getPanelFrame();
         iframe.width = options.width;
         iframe.height = options.height;
-        return;
 
     	// TODO : Animate the change if given options.animate = true
     	getPanel().sizeTo(options.width, options.height);
-    	setTimeout(function(){
-    		// we set the iframe size directly because it does not automatically stretch vertically
-            var height = document.getElementById('pocket-panel-container').clientHeight + 'px';
-	    	getPanelFrame().style.height = height;
-	    },1);
     }
 
     /**
      * Called when the signup and saved panel was hidden
      */
     function panelDidHide() {
     }
 
--- a/browser/components/sessionstore/ContentRestore.jsm
+++ b/browser/components/sessionstore/ContentRestore.jsm
@@ -31,67 +31,58 @@ XPCOMUtils.defineLazyModuleGetter(this, 
  * side is handled by SessionStore.jsm. The functions in this module are called
  * by content-sessionStore.js based on messages received from SessionStore.jsm
  * (or, in one case, based on a "load" event). Each tab has its own
  * ContentRestore instance, constructed by content-sessionStore.js.
  *
  * In a typical restore, content-sessionStore.js will call the following based
  * on messages and events it receives:
  *
- *   restoreHistory(epoch, tabData, callbacks)
+ *   restoreHistory(tabData, loadArguments, callbacks)
  *     Restores the tab's history and session cookies.
- *   restoreTabContent(finishCallback)
+ *   restoreTabContent(loadArguments, finishCallback)
  *     Starts loading the data for the current page to restore.
  *   restoreDocument()
  *     Restore form and scroll data.
  *
  * When the page has been loaded from the network, we call finishCallback. It
  * should send a message to SessionStore.jsm, which may cause other tabs to be
  * restored.
  *
  * When the page has finished loading, a "load" event will trigger in
  * content-sessionStore.js, which will call restoreDocument. At that point,
  * form data is restored and the restore is complete.
  *
  * At any time, SessionStore.jsm can cancel the ongoing restore by sending a
  * reset message, which causes resetRestore to be called. At that point it's
  * legal to begin another restore.
- *
- * The epoch that is passed into restoreHistory is merely a token. All messages
- * sent back to SessionStore.jsm include the epoch. This way, SessionStore.jsm
- * can discard messages that relate to restores that it has canceled (by
- * starting a new restore, say).
  */
 function ContentRestore(chromeGlobal) {
   let internal = new ContentRestoreInternal(chromeGlobal);
   let external = {};
 
   let EXPORTED_METHODS = ["restoreHistory",
                           "restoreTabContent",
                           "restoreDocument",
-                          "resetRestore",
-                          "getRestoreEpoch",
+                          "resetRestore"
                          ];
 
   for (let method of EXPORTED_METHODS) {
     external[method] = internal[method].bind(internal);
   }
 
   return Object.freeze(external);
 }
 
 function ContentRestoreInternal(chromeGlobal) {
   this.chromeGlobal = chromeGlobal;
 
   // The following fields are only valid during certain phases of the restore
   // process.
 
-  // The epoch that was passed into restoreHistory. Removed in restoreDocument.
-  this._epoch = 0;
-
   // The tabData for the restore. Set in restoreHistory and removed in
   // restoreTabContent.
   this._tabData = null;
 
   // Contains {entry, pageStyle, scrollPositions, formdata}, where entry is a
   // single entry from the tabData.entries array. Set in
   // restoreTabContent and removed in restoreDocument.
   this._restoringDocument = null;
@@ -118,19 +109,18 @@ ContentRestoreInternal.prototype = {
   },
 
   /**
    * Starts the process of restoring a tab. The tabData to be restored is passed
    * in here and used throughout the restoration. The epoch (which must be
    * non-zero) is passed through to all the callbacks. If a load in the tab
    * is started while it is pending, the appropriate callbacks are called.
    */
-  restoreHistory(epoch, tabData, loadArguments, callbacks) {
+  restoreHistory(tabData, loadArguments, callbacks) {
     this._tabData = tabData;
-    this._epoch = epoch;
 
     // In case about:blank isn't done yet.
     let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
     webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL);
 
     // Make sure currentURI is set so that switch-to-tab works before the tab is
     // restored. We'll reset this to about:blank when we try to restore the tab
     // to ensure that docshell doeesn't get confused. Don't bother doing this if
@@ -285,18 +275,16 @@ ContentRestoreInternal.prototype = {
   },
 
   /**
    * Finish restoring the tab by filling in form data and setting the scroll
    * position. The restore is complete when this function exits. It should be
    * called when the "load" event fires for the restoring tab.
    */
   restoreDocument: function () {
-    this._epoch = 0;
-
     if (!this._restoringDocument) {
       return;
     }
     let {entry, pageStyle, formdata, scrollPositions} = this._restoringDocument;
     this._restoringDocument = null;
 
     let window = this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIDOMWindow);
@@ -322,25 +310,17 @@ ContentRestoreInternal.prototype = {
       this._historyListener.uninstall();
     }
     this._historyListener = null;
 
     if (this._progressListener) {
       this._progressListener.uninstall();
     }
     this._progressListener = null;
-  },
-
-  /**
-   * If a restore is ongoing, this function returns the value of |epoch| that
-   * was passed to restoreHistory. If no restore is ongoing, it returns 0.
-   */
-  getRestoreEpoch: function () {
-    return this._epoch;
-  },
+  }
 };
 
 /*
  * This listener detects when a page being restored is reloaded. It triggers a
  * callback and cancels the reload. The callback will send a message to
  * SessionStore.jsm so that it can restore the content immediately.
  */
 function HistoryListener(docShell, callback) {
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -83,16 +83,26 @@ const NOTAB_MESSAGES = new Set([
 
   // For a description see above.
   "SessionStore:crashedTabRevived",
 
   // For a description see above.
   "SessionStore:update",
 ]);
 
+// The list of messages we accept without an "epoch" parameter.
+// See getCurrentEpoch() and friends to find out what an "epoch" is.
+const NOEPOCH_MESSAGES = new Set([
+  // For a description see above.
+  "SessionStore:setupSyncHandler",
+
+  // For a description see above.
+  "SessionStore:crashedTabRevived",
+]);
+
 // The list of messages we want to receive even during the short period after a
 // frame has been removed from the DOM and before its frame script has finished
 // unloading.
 const CLOSED_MESSAGES = new Set([
   // For a description see above.
   "SessionStore:crashedTabRevived",
 
   // For a description see above.
@@ -108,16 +118,17 @@ const TAB_EVENTS = [
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
+Cu.import("resource://gre/modules/debug.js", this);
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
   "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
 XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
   "@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
   "@mozilla.org/base/telemetry;1", "nsITelemetry");
 XPCOMUtils.defineLazyModuleGetter(this, "console",
@@ -324,20 +335,17 @@ let SessionStoreInternal = {
   ]),
 
   _globalState: new GlobalState(),
 
   // During the initial restore and setBrowserState calls tracks the number of
   // windows yet to be restored
   _restoreCount: -1,
 
-  // This number gets incremented each time we start to restore a tab.
-  _nextRestoreEpoch: 1,
-
-  // For each <browser> element being restored, records the current epoch.
+  // For each <browser> element, records the current epoch.
   _browserEpochs: new WeakMap(),
 
   // Any browsers that fires the oop-browser-crashed event gets stored in
   // here - that way we know which browsers to ignore messages from (until
   // they get restored).
   _crashedBrowsers: new WeakSet(),
 
   // A map (xul:browser -> nsIFrameLoader) that maps a browser to the last
@@ -610,16 +618,29 @@ let SessionStoreInternal = {
 
     // Ensure we receive only specific messages from <xul:browser>s that
     // have no tab assigned, e.g. the ones that preload about:newtab pages.
     if (!tab && !NOTAB_MESSAGES.has(aMessage.name)) {
       throw new Error(`received unexpected message '${aMessage.name}' ` +
                       `from a browser that has no tab`);
     }
 
+    let data = aMessage.data || {};
+    let hasEpoch = data.hasOwnProperty("epoch");
+
+    // Most messages sent by frame scripts require to pass an epoch.
+    if (!hasEpoch && !NOEPOCH_MESSAGES.has(aMessage.name)) {
+      throw new Error(`received message '${aMessage.name}' without an epoch`);
+    }
+
+    // Ignore messages from previous epochs.
+    if (hasEpoch && !this.isCurrentEpoch(browser, data.epoch)) {
+      return;
+    }
+
     switch (aMessage.name) {
       case "SessionStore:setupSyncHandler":
         TabState.setSyncHandler(browser, aMessage.objects.handler);
         break;
       case "SessionStore:update":
         // Ignore messages from <browser> elements that have crashed
         // and not yet been revived.
         if (this._crashedBrowsers.has(browser.permanentKey)) {
@@ -675,87 +696,79 @@ let SessionStoreInternal = {
               // after the tab was closed changed enough state so that we no
               // longer consider its data interesting enough to keep around.
               this.removeClosedTabData(closedTabs, index);
             }
           }
         }
         break;
       case "SessionStore:restoreHistoryComplete":
-        if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
-          // Notify the tabbrowser that the tab chrome has been restored.
-          let tabData = browser.__SS_data;
-
-          // wall-paper fix for bug 439675: make sure that the URL to be loaded
-          // is always visible in the address bar
-          let activePageData = tabData.entries[tabData.index - 1] || null;
-          let uri = activePageData ? activePageData.url || null : null;
-          browser.userTypedValue = uri;
-
-          // If the page has a title, set it.
-          if (activePageData) {
-            if (activePageData.title) {
-              tab.label = activePageData.title;
-              tab.crop = "end";
-            } else if (activePageData.url != "about:blank") {
-              tab.label = activePageData.url;
-              tab.crop = "center";
-            }
+        // Notify the tabbrowser that the tab chrome has been restored.
+        let tabData = browser.__SS_data;
+
+        // wall-paper fix for bug 439675: make sure that the URL to be loaded
+        // is always visible in the address bar
+        let activePageData = tabData.entries[tabData.index - 1] || null;
+        let uri = activePageData ? activePageData.url || null : null;
+        browser.userTypedValue = uri;
+
+        // If the page has a title, set it.
+        if (activePageData) {
+          if (activePageData.title) {
+            tab.label = activePageData.title;
+            tab.crop = "end";
+          } else if (activePageData.url != "about:blank") {
+            tab.label = activePageData.url;
+            tab.crop = "center";
           }
-
-          // Restore the tab icon.
-          if ("image" in tabData) {
-            win.gBrowser.setIcon(tab, tabData.image);
-          }
-
-          let event = win.document.createEvent("Events");
-          event.initEvent("SSTabRestoring", true, false);
-          tab.dispatchEvent(event);
         }
+
+        // Restore the tab icon.
+        if ("image" in tabData) {
+          win.gBrowser.setIcon(tab, tabData.image);
+        }
+
+        let event = win.document.createEvent("Events");
+        event.initEvent("SSTabRestoring", true, false);
+        tab.dispatchEvent(event);
         break;
       case "SessionStore:restoreTabContentStarted":
-        if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
-          if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
-            // If a load not initiated by sessionstore was started in a
-            // previously pending tab. Mark the tab as no longer pending.
-            this.markTabAsRestoring(tab);
-          } else {
-            // If the user was typing into the URL bar when we crashed, but hadn't hit
-            // enter yet, then we just need to write that value to the URL bar without
-            // loading anything. This must happen after the load, since it will clear
-            // userTypedValue.
-            let tabData = browser.__SS_data;
-            if (tabData.userTypedValue && !tabData.userTypedClear) {
-              browser.userTypedValue = tabData.userTypedValue;
-              win.URLBarSetURI();
-            }
+        if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+          // If a load not initiated by sessionstore was started in a
+          // previously pending tab. Mark the tab as no longer pending.
+          this.markTabAsRestoring(tab);
+        } else {
+          // If the user was typing into the URL bar when we crashed, but hadn't hit
+          // enter yet, then we just need to write that value to the URL bar without
+          // loading anything. This must happen after the load, since it will clear
+          // userTypedValue.
+          let tabData = browser.__SS_data;
+          if (tabData.userTypedValue && !tabData.userTypedClear) {
+            browser.userTypedValue = tabData.userTypedValue;
+            win.URLBarSetURI();
           }
         }
         break;
       case "SessionStore:restoreTabContentComplete":
-        if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
-          // This callback is used exclusively by tests that want to
-          // monitor the progress of network loads.
-          if (gDebuggingEnabled) {
-            Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null);
-          }
-
-          delete browser.__SS_data;
-
-          SessionStoreInternal._resetLocalTabRestoringState(tab);
-          SessionStoreInternal.restoreNextTab();
-
-          this._sendTabRestoredNotification(tab);
+        // This callback is used exclusively by tests that want to
+        // monitor the progress of network loads.
+        if (gDebuggingEnabled) {
+          Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null);
         }
+
+        delete browser.__SS_data;
+
+        SessionStoreInternal._resetLocalTabRestoringState(tab);
+        SessionStoreInternal.restoreNextTab();
+
+        this._sendTabRestoredNotification(tab);
         break;
       case "SessionStore:reloadPendingTab":
-        if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
-          if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
-            this.restoreTabContent(tab);
-          }
+        if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+          this.restoreTabContent(tab);
         }
         break;
       case "SessionStore:crashedTabRevived":
         this._crashedBrowsers.delete(browser.permanentKey);
         break;
       default:
         throw new Error(`received unknown message '${aMessage.name}'`);
         break;
@@ -1608,18 +1621,20 @@ let SessionStoreInternal = {
     // that we don't try restoring if the user switches to it before
     // reviving the crashed browser. This is throwing away the information
     // that the tab was in a pending state when the browser crashed, which
     // is an explicit choice. For now, when restoring all crashed tabs, based
     // on a user preference we'll either restore all of them at once, or only
     // restore the selected tab and lazily restore the rest. We'll make no
     // efforts at this time to be smart and restore all of the tabs that had
     // been in a restored state at the time of the crash.
-    let tab = aWindow.gBrowser.getTabForBrowser(aBrowser);
-    this._resetLocalTabRestoringState(tab);
+    if (aBrowser.__SS_restoreState) {
+      let tab = aWindow.gBrowser.getTabForBrowser(aBrowser);
+      this._resetLocalTabRestoringState(tab);
+    }
   },
 
   // Clean up data that has been closed a long time ago.
   // Do not reschedule a save. This will wait for the next regular
   // save.
   onIdleDaily: function() {
     // Remove old closed windows
     this._cleanupOldData([this._closedWindows]);
@@ -2723,16 +2738,19 @@ let SessionStoreInternal = {
     // Restore all tabs.
     for (let t = 0; t < aTabs.length; t++) {
       this.restoreTab(aTabs[t], aTabData[t]);
     }
   },
 
   // Restores the given tab state for a given tab.
   restoreTab(tab, tabData, options = {}) {
+    NS_ASSERT(!tab.linkedBrowser.__SS_restoreState,
+              "must reset tab before calling restoreTab()");
+
     let restoreImmediately = options.restoreImmediately;
     let loadArguments = options.loadArguments;
     let browser = tab.linkedBrowser;
     let window = tab.ownerDocument.defaultView;
     let tabbrowser = window.gBrowser;
 
     // Increase the busy state counter before modifying the tab.
     this._setWindowStateBusy(window);
@@ -2783,21 +2801,16 @@ let SessionStoreInternal = {
       tab.__SS_extdata = Cu.cloneInto(tabData.extData, {});
     } else {
       delete tab.__SS_extdata;
     }
 
     // Tab is now open.
     delete tabData.closedAt;
 
-    // Flush all data from the content script synchronously. This is done so
-    // that all async messages that are still on their way to chrome will
-    // be ignored and don't override any tab data set when restoring.
-    TabState.flush(browser);
-
     // Ensure the index is in bounds.
     let activeIndex = (tabData.index || tabData.entries.length) - 1;
     activeIndex = Math.min(activeIndex, tabData.entries.length - 1);
     activeIndex = Math.max(activeIndex, 0);
 
     // Save the index in case we updated it above.
     tabData.index = activeIndex + 1;
 
@@ -2805,21 +2818,20 @@ let SessionStoreInternal = {
     // attribute so that it runs in a content process.
     let activePageData = tabData.entries[activeIndex] || null;
     let uri = activePageData ? activePageData.url || null : null;
     if (loadArguments) {
       uri = loadArguments.uri;
     }
     tabbrowser.updateBrowserRemotenessByURL(browser, uri);
 
-    // Start a new epoch and include the epoch in the restoreHistory
-    // message. If a message is received that relates to a previous epoch, we
-    // discard it.
-    let epoch = this._nextRestoreEpoch++;
-    this._browserEpochs.set(browser.permanentKey, epoch);
+    // Start a new epoch to discard all frame script messages relating to a
+    // previous epoch. All async messages that are still on their way to chrome
+    // will be ignored and don't override any tab data set when restoring.
+    let epoch = this.startNextEpoch(browser);
 
     // keep the data around to prevent dataloss in case
     // a tab gets closed before it's been properly restored
     browser.__SS_data = tabData;
     browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
     browser.setAttribute("pending", "true");
     tab.setAttribute("pending", "true");
 
@@ -3585,57 +3597,80 @@ let SessionStoreInternal = {
   /**
    * Reset the restoring state for a particular tab. This will be called when
    * removing a tab or when a tab needs to be reset (it's being overwritten).
    *
    * @param aTab
    *        The tab that will be "reset"
    */
   _resetLocalTabRestoringState: function (aTab) {
+    NS_ASSERT(aTab.linkedBrowser.__SS_restoreState,
+              "given tab is not restoring");
+
     let window = aTab.ownerDocument.defaultView;
     let browser = aTab.linkedBrowser;
 
     // Keep the tab's previous state for later in this method
     let previousState = browser.__SS_restoreState;
 
     // The browser is no longer in any sort of restoring state.
     delete browser.__SS_restoreState;
-    this._browserEpochs.delete(browser.permanentKey);
 
     aTab.removeAttribute("pending");
     browser.removeAttribute("pending");
 
     if (previousState == TAB_STATE_RESTORING) {
       if (this._tabsRestoringCount)
         this._tabsRestoringCount--;
     } else if (previousState == TAB_STATE_NEEDS_RESTORE) {
       // Make sure that the tab is removed from the list of tabs to restore.
       // Again, this is normally done in restoreTabContent, but that isn't being called
       // for this tab.
       TabRestoreQueue.remove(aTab);
     }
   },
 
   _resetTabRestoringState: function (tab) {
+    NS_ASSERT(tab.linkedBrowser.__SS_restoreState,
+              "given tab is not restoring");
+
     let browser = tab.linkedBrowser;
-    if (browser.__SS_restoreState) {
-      browser.messageManager.sendAsyncMessage("SessionStore:resetRestore", {});
-    }
+    browser.messageManager.sendAsyncMessage("SessionStore:resetRestore", {});
     this._resetLocalTabRestoringState(tab);
   },
 
   /**
+   * Each fresh tab starts out with epoch=0. This function can be used to
+   * start a next epoch by incrementing the current value. It will enables us
+   * to ignore stale messages sent from previous epochs. The function returns
+   * the new epoch ID for the given |browser|.
+   */
+  startNextEpoch(browser) {
+    let next = this.getCurrentEpoch(browser) + 1;
+    this._browserEpochs.set(browser.permanentKey, next);
+    return next;
+  },
+
+  /**
+   * Returns the current epoch for the given <browser>. If we haven't assigned
+   * a new epoch this will default to zero for new tabs.
+   */
+  getCurrentEpoch(browser) {
+    return this._browserEpochs.get(browser.permanentKey) || 0;
+  },
+
+  /**
    * Each time a <browser> element is restored, we increment its "epoch". To
    * check if a message from content-sessionStore.js is out of date, we can
    * compare the epoch received with the message to the <browser> element's
    * epoch. This function does that, and returns true if |epoch| is up-to-date
    * with respect to |browser|.
    */
   isCurrentEpoch: function (browser, epoch) {
-    return (this._browserEpochs.get(browser.permanentKey) || 0) == epoch;
+    return this.getCurrentEpoch(browser) == epoch;
   },
 
 };
 
 /**
  * Priority queue that keeps track of a list of tabs to restore and returns
  * the tab we should restore next, based on priority rules. We decide between
  * pinned, visible and hidden tabs in that and FIFO order. Hidden tabs are only
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -35,16 +35,19 @@ XPCOMUtils.defineLazyServiceGetter(this,
 
 Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this);
 let gFrameTree = new FrameTree(this);
 
 Cu.import("resource:///modules/sessionstore/ContentRestore.jsm", this);
 XPCOMUtils.defineLazyGetter(this, 'gContentRestore',
                             () => { return new ContentRestore(this) });
 
+// The current epoch.
+let gCurrentEpoch = 0;
+
 /**
  * Returns a lazy function that will evaluate the given
  * function |fn| only once and cache its return value.
  */
 function createLazy(fn) {
   let cached = false;
   let cachedValue = null;
 
@@ -83,24 +86,18 @@ let EventListener = {
   },
 
   handleEvent: function (event) {
     // Ignore load events from subframes.
     if (event.target != content.document) {
       return;
     }
 
-    // If we're in the process of restoring, this load may signal
-    // the end of the restoration.
-    let epoch = gContentRestore.getRestoreEpoch();
-    if (!epoch) {
-      return;
-    }
-
-    // Restore the form data and scroll position.
+    // Restore the form data and scroll position. If we're not currently
+    // restoring a tab state then this call will simply be a noop.
     gContentRestore.restoreDocument();
   }
 };
 
 /**
  * Listens for and handles messages sent by the session store service.
  */
 let MessageListener = {
@@ -117,16 +114,24 @@ let MessageListener = {
 
   receiveMessage: function ({name, data}) {
     // The docShell might be gone. Don't process messages,
     // that will just lead to errors anyway.
     if (!docShell) {
       return;
     }
 
+    // A fresh tab always starts with epoch=0. The parent has the ability to
+    // override that to signal a new era in this tab's life. This enables it
+    // to ignore async messages that were already sent but not yet received
+    // and would otherwise confuse the internal tab state.
+    if (data.epoch && data.epoch != gCurrentEpoch) {
+      gCurrentEpoch = data.epoch;
+    }
+
     switch (name) {
       case "SessionStore:restoreHistory":
         this.restoreHistory(data);
         break;
       case "SessionStore:restoreTabContent":
         this.restoreTabContent(data);
         break;
       case "SessionStore:resetRestore":
@@ -134,17 +139,17 @@ let MessageListener = {
         break;
       default:
         debug("received unknown message '" + name + "'");
         break;
     }
   },
 
   restoreHistory({epoch, tabData, loadArguments}) {
-    gContentRestore.restoreHistory(epoch, tabData, loadArguments, {
+    gContentRestore.restoreHistory(tabData, loadArguments, {
       onReload() {
         // Inform SessionStore.jsm about the reload. It will send
         // restoreTabContent in response.
         sendAsyncMessage("SessionStore:reloadPendingTab", {epoch});
       },
 
       // Note: The two callbacks passed here will only be used when a load
       // starts that was not initiated by sessionstore itself. This can happen
@@ -167,17 +172,17 @@ let MessageListener = {
     // SSTabRestoring seem to get confused if chrome and content are out of
     // sync about the state of the restore (particularly regarding
     // docShell.currentURI). Using a synchronous message is the easiest way
     // to temporarily synchronize them.
     sendSyncMessage("SessionStore:restoreHistoryComplete", {epoch});
   },
 
   restoreTabContent({loadArguments}) {
-    let epoch = gContentRestore.getRestoreEpoch();
+    let epoch = gCurrentEpoch;
 
     // We need to pass the value of didStartLoad back to SessionStore.jsm.
     let didStartLoad = gContentRestore.restoreTabContent(loadArguments, () => {
       // Tell SessionStore.jsm that it may want to restore some more tabs,
       // since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
       sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch});
     });
 
@@ -693,17 +698,18 @@ let MessageQueue = {
     durationMs = Date.now() - durationMs;
     let telemetry = {
       FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS: durationMs
     }
 
     // Send all data to the parent process.
     sendMessage("SessionStore:update", {
       id: this._id, data, telemetry,
-      isFinal: options.isFinal || false
+      isFinal: options.isFinal || false,
+      epoch: gCurrentEpoch
     });
 
     // Increase our unique message ID.
     this._id++;
   },
 
   /**
    * This function is used to make the message queue flush all queue data that
--- a/browser/devtools/framework/target.js
+++ b/browser/devtools/framework/target.js
@@ -160,16 +160,17 @@ Object.defineProperty(Target.prototype, 
  * be web pages served over http(s), but they don't have to be.
  */
 function TabTarget(tab) {
   EventEmitter.decorate(this);
   this.destroy = this.destroy.bind(this);
   this._handleThreadState = this._handleThreadState.bind(this);
   this.on("thread-resumed", this._handleThreadState);
   this.on("thread-paused", this._handleThreadState);
+  this.activeTab = this.activeConsole = null;
   // Only real tabs need initialization here. Placeholder objects for remote
   // targets will be initialized after a makeRemote method call.
   if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) {
     this._tab = tab;
     this._setupListeners();
   } else {
     this._form = tab.form;
     this._client = tab.client;
@@ -415,16 +416,29 @@ TabTarget.prototype = {
     let attachTab = () => {
       this._client.attachTab(this._form.actor, (aResponse, aTabClient) => {
         if (!aTabClient) {
           this._remote.reject("Unable to attach to the tab");
           return;
         }
         this.activeTab = aTabClient;
         this.threadActor = aResponse.threadActor;
+        attachConsole();
+      });
+    };
+
+    let attachConsole = () => {
+      this._client.attachConsole(this._form.consoleActor,
+                                 [ "NetworkActivity" ],
+                                 (aResponse, aWebConsoleClient) => {
+        if (!aWebConsoleClient) {
+          this._remote.reject("Unable to attach to the console");
+          return;
+        }
+        this.activeConsole = aWebConsoleClient;
         this._remote.resolve(null);
       });
     };
 
     if (this.isLocalTab) {
       this._client.connect((aType, aTraits) => {
         this._client.getTab({ tab: this.tab })
             .then(aResponse => {
@@ -434,17 +448,17 @@ TabTarget.prototype = {
       });
     } else if (this.isTabActor) {
       // In the remote debugging case, the protocol connection will have been
       // already initialized in the connection screen code.
       attachTab();
     } else {
       // AddonActor and chrome debugging on RootActor doesn't inherits from TabActor and
       // doesn't need to be attached.
-      this._remote.resolve(null);
+      attachConsole();
     }
 
     return this._remote.promise;
   },
 
   /**
    * Listen to the different events.
    */
@@ -588,43 +602,42 @@ TabTarget.prototype = {
       // If, on the other hand, this target was remoted, the promise will be
       // resolved after the remote connection is closed.
       this._teardownRemoteListeners();
 
       if (this.isLocalTab) {
         // We started with a local tab and created the client ourselves, so we
         // should close it.
         this._client.close(cleanupAndResolve);
-      } else {
+      } else if (this.activeTab) {
         // The client was handed to us, so we are not responsible for closing
         // it. We just need to detach from the tab, if already attached.
-        if (this.activeTab) {
-          // |detach| may fail if the connection is already dead, so proceed
-          // cleanup directly after this.
-          this.activeTab.detach();
-          cleanupAndResolve();
-        } else {
-          cleanupAndResolve();
-        }
+        // |detach| may fail if the connection is already dead, so proceed with
+        // cleanup directly after this.
+        this.activeTab.detach();
+        cleanupAndResolve();
+      } else {
+        cleanupAndResolve();
       }
     }
 
     return this._destroyer.promise;
   },
 
   /**
    * Clean up references to what this target points to.
    */
   _cleanup: function TabTarget__cleanup() {
     if (this._tab) {
       targets.delete(this._tab);
     } else {
       promiseTargets.delete(this._form);
     }
     this.activeTab = null;
+    this.activeConsole = null;
     this._client = null;
     this._tab = null;
     this._form = null;
     this._remote = null;
   },
 
   toString: function() {
     return 'TabTarget:' + (this._tab ? this._tab : (this._form && this._form.actor));
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -105,16 +105,19 @@ const ToolboxButtons = exports.ToolboxBu
  * @param {object} hostOptions
  *        Options for host specifically
  */
 function Toolbox(target, selectedTool, hostType, hostOptions) {
   this._target = target;
   this._toolPanels = new Map();
   this._telemetry = new Telemetry();
 
+  this._initInspector = null;
+  this._inspector = null;
+
   this._toolRegistered = this._toolRegistered.bind(this);
   this._toolUnregistered = this._toolUnregistered.bind(this);
   this._refreshHostTitle = this._refreshHostTitle.bind(this);
   this.selectFrame = this.selectFrame.bind(this);
   this._updateFrames = this._updateFrames.bind(this);
   this._splitConsoleOnKeypress = this._splitConsoleOnKeypress.bind(this);
   this.destroy = this.destroy.bind(this);
   this.highlighterUtils = getHighlighterUtils(this);
@@ -362,17 +365,17 @@ Toolbox.prototype = {
       this.webconsolePanel = this.doc.querySelector("#toolbox-panel-webconsole");
       this.webconsolePanel.height = Services.prefs.getIntPref(SPLITCONSOLE_HEIGHT_PREF);
       this.webconsolePanel.addEventListener("resize", this._saveSplitConsoleHeight);
 
       let buttonsPromise = this._buildButtons();
 
       this._pingTelemetry();
 
-      let panel = yield this.selectTool(this._defaultToolId);
+      yield this.selectTool(this._defaultToolId);
 
       // Wait until the original tool is selected so that the split
       // console input will receive focus.
       let splitConsolePromise = promise.resolve();
       if (Services.prefs.getBoolPref(SPLITCONSOLE_ENABLED_PREF)) {
         splitConsolePromise = this.openSplitConsole();
       }
 
--- a/browser/devtools/netmonitor/netmonitor-controller.js
+++ b/browser/devtools/netmonitor/netmonitor-controller.js
@@ -191,38 +191,43 @@ let NetMonitorController = {
     this.disconnect();
 
     // Shutdown is synchronous, for now.
     return this._shutdown = promise.resolve();
   },
 
   /**
    * Initiates remote or chrome network monitoring based on the current target,
-   * wiring event handlers as necessary.
+   * wiring event handlers as necessary. Since the TabTarget will have already
+   * started listening to network requests by now, this is largely
+   * netmonitor-specific initialization.
    *
    * @return object
    *         A promise that is resolved when the monitor finishes connecting.
    */
   connect: Task.async(function*() {
     if (this._connection) {
       return this._connection;
     }
 
     let deferred = promise.defer();
     this._connection = deferred.promise;
 
-    let target = this._target;
-    let { client, form } = target;
+    this.client = this._target.client;
     // Some actors like AddonActor or RootActor for chrome debugging
-    // do not support attach/detach and can be used directly
-    if (!target.isTabActor) {
-      this._startChromeMonitoring(client, form.consoleActor, deferred.resolve);
-    } else {
-      this._startMonitoringTab(client, form, deferred.resolve);
+    // aren't actual tabs.
+    if (this._target.isTabActor) {
+      this.tabClient = this._target.activeTab;
     }
+    this.webConsoleClient = this._target.activeConsole;
+    this.webConsoleClient.setPreferences(NET_PREFS, () => {
+      this.TargetEventsHandler.connect();
+      this.NetworkEventsHandler.connect();
+      deferred.resolve();
+    });
 
     yield deferred.promise;
     window.emit(EVENTS.CONNECTED);
   }),
 
   /**
    * Disconnects the debugger client and removes event handlers as necessary.
    */
@@ -239,92 +244,16 @@ let NetMonitorController = {
    * Checks whether the netmonitor connection is active.
    * @return boolean
    */
   isConnected: function() {
     return !!this.client;
   },
 
   /**
-   * Sets up a monitoring session.
-   *
-   * @param DebuggerClient aClient
-   *        The debugger client.
-   * @param object aTabGrip
-   *        The remote protocol grip of the tab.
-   * @param function aCallback
-   *        A function to invoke once the client attached to the console client.
-   */
-  _startMonitoringTab: function(aClient, aTabGrip, aCallback) {
-    if (!aClient) {
-      Cu.reportError("No client found!");
-      return;
-    }
-    this.client = aClient;
-
-    aClient.attachTab(aTabGrip.actor, (aResponse, aTabClient) => {
-      if (!aTabClient) {
-        Cu.reportError("No tab client found!");
-        return;
-      }
-      this.tabClient = aTabClient;
-
-      aClient.attachConsole(aTabGrip.consoleActor, LISTENERS, (aResponse, aWebConsoleClient) => {
-        if (!aWebConsoleClient) {
-          Cu.reportError("Couldn't attach to console: " + aResponse.error);
-          return;
-        }
-        this.webConsoleClient = aWebConsoleClient;
-        this.webConsoleClient.setPreferences(NET_PREFS, () => {
-          this.TargetEventsHandler.connect();
-          this.NetworkEventsHandler.connect();
-
-          if (aCallback) {
-            aCallback();
-          }
-        });
-      });
-    });
-  },
-
-  /**
-   * Sets up a chrome monitoring session.
-   *
-   * @param DebuggerClient aClient
-   *        The debugger client.
-   * @param object aConsoleActor
-   *        The remote protocol grip of the chrome debugger.
-   * @param function aCallback
-   *        A function to invoke once the client attached to the console client.
-   */
-  _startChromeMonitoring: function(aClient, aConsoleActor, aCallback) {
-    if (!aClient) {
-      Cu.reportError("No client found!");
-      return;
-    }
-    this.client = aClient;
-
-    aClient.attachConsole(aConsoleActor, LISTENERS, (aResponse, aWebConsoleClient) => {
-      if (!aWebConsoleClient) {
-        Cu.reportError("Couldn't attach to console: " + aResponse.error);
-        return;
-      }
-      this.webConsoleClient = aWebConsoleClient;
-      this.webConsoleClient.setPreferences(NET_PREFS, () => {
-        this.TargetEventsHandler.connect();
-        this.NetworkEventsHandler.connect();
-
-        if (aCallback) {
-          aCallback();
-        }
-      });
-    });
-  },
-
-  /**
    * Gets the activity currently performed by the frontend.
    * @return number
    */
   getCurrentActivity: function() {
     return this._currentActivity || ACTIVITY_TYPE.NONE;
   },
 
   /**
@@ -435,17 +364,16 @@ let NetMonitorController = {
  */
 function TargetEventsHandler() {
   this._onTabNavigated = this._onTabNavigated.bind(this);
   this._onTabDetached = this._onTabDetached.bind(this);
 }
 
 TargetEventsHandler.prototype = {
   get target() NetMonitorController._target,
-  get webConsoleClient() NetMonitorController.webConsoleClient,
 
   /**
    * Listen for events emitted by the current tab target.
    */
   connect: function() {
     dumpn("TargetEventsHandler is connecting...");
     this.target.on("close", this._onTabDetached);
     this.target.on("navigate", this._onTabNavigated);
@@ -523,119 +451,133 @@ NetworkEventsHandler.prototype = {
   get client() NetMonitorController._target.client,
   get webConsoleClient() NetMonitorController.webConsoleClient,
 
   /**
    * Connect to the current target client.
    */
   connect: function() {
     dumpn("NetworkEventsHandler is connecting...");
-    this.client.addListener("networkEvent", this._onNetworkEvent);
-    this.client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
+    this.webConsoleClient.on("networkEvent", this._onNetworkEvent);
+    this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate);
+    this._displayCachedEvents();
+  },
+
+  /**
+   * Display any network events already in the cache.
+   */
+  _displayCachedEvents: function() {
+    for (let cachedEvent of this.webConsoleClient.getNetworkEvents()) {
+      // First add the request to the timeline.
+      this._onNetworkEvent("networkEvent", cachedEvent);
+      // Then replay any updates already received.
+      for (let update of cachedEvent.updates) {
+        this._onNetworkEventUpdate("networkEventUpdate", {
+          packet: {
+            updateType: update
+          },
+          networkInfo: cachedEvent
+        });
+      }
+    }
   },
 
   /**
    * Disconnect from the client.
    */
   disconnect: function() {
     if (!this.client) {
       return;
     }
     dumpn("NetworkEventsHandler is disconnecting...");
-    this.client.removeListener("networkEvent", this._onNetworkEvent);
-    this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
+    this.webConsoleClient.off("networkEvent", this._onNetworkEvent);
+    this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate);
   },
 
   /**
    * The "networkEvent" message type handler.
    *
-   * @param string aType
+   * @param string type
    *        Message type.
-   * @param object aPacket
-   *        The message received from the server.
+   * @param object networkInfo
+   *        The network request information.
    */
-  _onNetworkEvent: function(aType, aPacket) {
-    if (aPacket.from != this.webConsoleClient.actor) {
-      // Skip events from different console actors.
-      return;
-    }
+  _onNetworkEvent: function(type, networkInfo) {
+    let { actor, startedDateTime, request: { method, url }, isXHR, fromCache } = networkInfo;
 
-    let { actor, startedDateTime, method, url, isXHR, fromCache } = aPacket.eventActor;
     NetMonitorView.RequestsMenu.addRequest(
       actor, startedDateTime, method, url, isXHR, fromCache
     );
     window.emit(EVENTS.NETWORK_EVENT, actor);
   },
 
   /**
    * The "networkEventUpdate" message type handler.
    *
-   * @param string aType
+   * @param string type
    *        Message type.
-   * @param object aPacket
+   * @param object packet
    *        The message received from the server.
+   * @param object networkInfo
+   *        The network request information.
    */
-  _onNetworkEventUpdate: function(aType, aPacket) {
-    let actor = aPacket.from;
-    if (!NetMonitorView.RequestsMenu.getItemByValue(actor)) {
-      // Skip events from unknown actors.
-      return;
-    }
+  _onNetworkEventUpdate: function(type, { packet, networkInfo }) {
+    let actor = networkInfo.actor;
 
-    switch (aPacket.updateType) {
+    switch (packet.updateType) {
       case "requestHeaders":
         this.webConsoleClient.getRequestHeaders(actor, this._onRequestHeaders);
         window.emit(EVENTS.UPDATING_REQUEST_HEADERS, actor);
         break;
       case "requestCookies":
         this.webConsoleClient.getRequestCookies(actor, this._onRequestCookies);
         window.emit(EVENTS.UPDATING_REQUEST_COOKIES, actor);
         break;
       case "requestPostData":
         this.webConsoleClient.getRequestPostData(actor, this._onRequestPostData);
         window.emit(EVENTS.UPDATING_REQUEST_POST_DATA, actor);
         break;
       case "securityInfo":
-        NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
-          securityState: aPacket.state,
+        NetMonitorView.RequestsMenu.updateRequest(actor, {
+          securityState: networkInfo.securityInfo,
         });
         this.webConsoleClient.getSecurityInfo(actor, this._onSecurityInfo);
         window.emit(EVENTS.UPDATING_SECURITY_INFO, actor);
         break;
       case "responseHeaders":
         this.webConsoleClient.getResponseHeaders(actor, this._onResponseHeaders);
         window.emit(EVENTS.UPDATING_RESPONSE_HEADERS, actor);
         break;
       case "responseCookies":
         this.webConsoleClient.getResponseCookies(actor, this._onResponseCookies);
         window.emit(EVENTS.UPDATING_RESPONSE_COOKIES, actor);
         break;
       case "responseStart":
-        NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
-          httpVersion: aPacket.response.httpVersion,
-          remoteAddress: aPacket.response.remoteAddress,
-          remotePort: aPacket.response.remotePort,
-          status: aPacket.response.status,
-          statusText: aPacket.response.statusText,
-          headersSize: aPacket.response.headersSize
+        NetMonitorView.RequestsMenu.updateRequest(actor, {
+          httpVersion: networkInfo.response.httpVersion,
+          remoteAddress: networkInfo.response.remoteAddress,
+          remotePort: networkInfo.response.remotePort,
+          status: networkInfo.response.status,
+          statusText: networkInfo.response.statusText,
+          headersSize: networkInfo.response.headersSize
         });
         window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
         break;
       case "responseContent":
-        NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
-          contentSize: aPacket.contentSize,
-          transferredSize: aPacket.transferredSize,
-          mimeType: aPacket.mimeType
+        NetMonitorView.RequestsMenu.updateRequest(actor, {
+          contentSize: networkInfo.response.bodySize,
+          transferredSize: networkInfo.response.transferredSize,
+          mimeType: networkInfo.response.content.mimeType
         });
         this.webConsoleClient.getResponseContent(actor, this._onResponseContent);
         window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
         break;
       case "eventTimings":
-        NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
-          totalTime: aPacket.totalTime
+        NetMonitorView.RequestsMenu.updateRequest(actor, {
+          totalTime: networkInfo.totalTime
         });
         this.webConsoleClient.getEventTimings(actor, this._onEventTimings);
         window.emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
         break;
     }
   },
 
   /**
--- a/browser/devtools/netmonitor/test/browser_net_reload-button.js
+++ b/browser/devtools/netmonitor/test/browser_net_reload-button.js
@@ -10,19 +10,16 @@ function test() {
   initNetMonitor(SINGLE_GET_URL).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
     monitor = aMonitor;
     let { document, NetMonitorView } = aMonitor.panelWin;
     let { RequestsMenu } = NetMonitorView;
     reqMenu = RequestsMenu;
 
-    is(reqMenu.itemCount, 0,
-      "The request menu should empty before reloading");
-
     let button = document.querySelector("#requests-menu-reload-notice-button");
     button.click();
   })
   .then(() => {
     return waitForNetworkEvents(monitor, 2);
   })
   .then(() => {
     is(reqMenu.itemCount, 2,
--- a/browser/devtools/netmonitor/test/head.js
+++ b/browser/devtools/netmonitor/test/head.js
@@ -142,16 +142,21 @@ function initNetMonitor(aUrl, aWindow, a
     let target = TargetFactory.forTab(tab);
 
     yield target.makeRemote();
     info("Target remoted.");
 
     if(!aEnableCache) {
       yield toggleCache(target, true);
       info("Cache disabled when the current and all future toolboxes are open.");
+      // Remove any requests generated by the reload while toggling the cache to
+      // avoid interfering with the test.
+      isnot([...target.activeConsole.getNetworkEvents()].length, 0,
+         "Request to reconfigure the tab was recorded.");
+      target.activeConsole.clearNetworkRequests();
     }
 
     let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
     info("Netork monitor pane shown successfully.");
 
     let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
     let monitor = toolbox.getCurrentPanel();
     return [tab, debuggee, monitor];
--- a/browser/devtools/shared/test/browser_graphs-07a.js
+++ b/browser/devtools/shared/test/browser_graphs-07a.js
@@ -196,21 +196,23 @@ function normalDragStop(graph, x, y = 1)
   graph._onMouseMove({ testX: x, testY: y });
   graph._onMouseUp({ testX: x, testY: y });
 }
 
 function buggyDragStop(graph, x, y = 1) {
   x /= window.devicePixelRatio;
   y /= window.devicePixelRatio;
 
-  // Only fire a mousemove instead of a mouseup.
-  // This happens when the mouseup happens outside of the toolbox,
-  // see Bug 1066504.
   graph._onMouseMove({ testX: x, testY: y });
-  graph._onMouseMove({ testX: x, testY: y, buttons: 0 });
+
+  // Only fire a mousemove with no buttons instead of a mouseup.
+  // This happens when the mouseup happens outside of the window.
+  // Send different coordinates to make sure the selection is preserved,
+  // see Bugs 1066504 and 1144779.
+  graph._onMouseMove({ testX: x+1, testY: y+1, buttons: 0 });
 }
 
 function scroll(graph, wheel, x, y = 1) {
   x /= window.devicePixelRatio;
   y /= window.devicePixelRatio;
   graph._onMouseMove({ testX: x, testY: y });
   graph._onMouseWheel({ testX: x, testY: y, detail: wheel });
 }
--- a/browser/devtools/shared/widgets/Graphs.jsm
+++ b/browser/devtools/shared/widgets/Graphs.jsm
@@ -981,22 +981,22 @@ AbstractCanvasGraph.prototype = {
     // Need to stop propagation here, since this function can be bound
     // to both this._window and this._topWindow.  It's only attached to
     // this._topWindow during a drag event.  Null check here since tests
     // don't pass this method into the event object.
     if (e.stopPropagation && this._isMouseActive) {
       e.stopPropagation();
     }
 
-    // If a mouseup happened outside the toolbox and the current operation
-    // is causing the selection changed, then end it.
+    // If a mouseup happened outside the window and the current operation
+    // is causing the selection to change, then end it.
     if (e.buttons == 0 && (this.hasSelectionInProgress() ||
                            resizer.margin != null ||
                            dragger.origin != null)) {
-      return this._onMouseUp(e);
+      return this._onMouseUp();
     }
 
     let {mouseX,mouseY} = this._getRelativeEventCoordinates(e);
     this._cursor.x = mouseX;
     this._cursor.y = mouseY;
 
     if (resizer.margin != null) {
       this._selection[resizer.margin] = mouseX;
@@ -1088,20 +1088,18 @@ AbstractCanvasGraph.prototype = {
 
     this._shouldRedraw = true;
     this.emit("mousedown");
   },
 
   /**
    * Listener for the "mouseup" event on the graph's container.
    */
-  _onMouseUp: function(e) {
+  _onMouseUp: function() {
     this._isMouseActive = false;
-    let {mouseX} = this._getRelativeEventCoordinates(e);
-
     switch (this._canvas.getAttribute("input")) {
       case "hovering-background":
       case "hovering-region":
         if (!this.selectionEnabled) {
           break;
         }
         if (this.getSelectionWidth() < 1) {
           let region = this.getHoveredRegion();
@@ -1110,17 +1108,17 @@ AbstractCanvasGraph.prototype = {
             this._selection.end = region.end;
             this.emit("selecting");
           } else {
             this._selection.start = null;
             this._selection.end = null;
             this.emit("deselecting");
           }
         } else {
-          this._selection.end = mouseX;
+          this._selection.end = this._cursor.x;
           this.emit("selecting");
         }
         break;
 
       case "hovering-selection-start-boundary":
       case "hovering-selection-end-boundary":
         this._selectionResizer.margin = null;
         break;
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -380,8 +380,10 @@ skip-if = e10s # Bug 1042253 - webconsol
 [browser_webconsole_console_trace_duplicates.js]
 [browser_webconsole_cd_iframe.js]
 [browser_webconsole_autocomplete_crossdomain_iframe.js]
 [browser_webconsole_console_custom_styles.js]
 [browser_webconsole_console_api_stackframe.js]
 [browser_webconsole_column_numbers.js]
 [browser_console_open_or_focus.js]
 [browser_webconsole_bug_922212_console_dirxml.js]
+[browser_webconsole_shows_reqs_in_netmonitor.js]
+[browser_netmonitor_shows_reqs_in_webconsole.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js
@@ -0,0 +1,71 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TEST_URI = "data:text/html;charset=utf8,Test that the netmonitor " +
+                 "displays requests that have been recorded in the " +
+                 "web console, even if the netmonitor hadn't opened yet.";
+
+const TEST_FILE = "test-network-request.html";
+const TEST_PATH = "http://example.com/browser/browser/devtools/webconsole/test/" +
+                 TEST_FILE;
+
+const NET_PREF = "devtools.webconsole.filter.networkinfo";
+Services.prefs.setBoolPref(NET_PREF, true);
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref(NET_PREF);
+});
+
+add_task(function* () {
+  let { tab, browser } = yield loadTab(TEST_URI);
+
+  // Test that the request appears in the console.
+  let hud = yield openConsole();
+  info("Web console is open");
+
+  yield loadDocument(browser);
+  info("Document loaded.");
+
+  yield waitForMessages({
+    webconsole: hud,
+    messages: [
+      {
+        name: "network message",
+        text: TEST_FILE,
+        category: CATEGORY_NETWORK,
+        severity: SEVERITY_LOG
+      }
+    ]
+  });
+
+  // Test that the request appears in the network panel.
+  let target = TargetFactory.forTab(tab);
+  let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
+  info("Network panel is open.");
+
+  testNetmonitor(toolbox);
+});
+
+
+function loadDocument(browser) {
+  let deferred = promise.defer();
+
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    deferred.resolve();
+  }, true);
+  content.location = TEST_PATH;
+
+  return deferred.promise;
+}
+
+function testNetmonitor(toolbox) {
+  let monitor = toolbox.getCurrentPanel();
+  let { RequestsMenu } = monitor.panelWin.NetMonitorView;
+  is(RequestsMenu.itemCount, 1, "Network request appears in the network panel");
+
+  let item = RequestsMenu.getItemAtIndex(0);
+  is(item.attachment.method, "GET", "The attached method is correct.");
+  is(item.attachment.url, TEST_PATH, "The attached url is correct.");
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_netlogging.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_netlogging.js
@@ -68,17 +68,18 @@ function testPageLoad()
     ok(lastRequest, "Page load was logged");
 
     is(lastRequest.request.url, TEST_NETWORK_REQUEST_URI,
       "Logged network entry is page load");
     is(lastRequest.request.method, "GET", "Method is correct");
     ok(!lastRequest.request.postData.text, "No request body was stored");
     ok(lastRequest.discardRequestBody, "Request body was discarded");
     ok(!lastRequest.response.content.text, "No response body was stored");
-    ok(lastRequest.discardResponseBody, "Response body was discarded");
+    ok(lastRequest.discardResponseBody || lastRequest.fromCache,
+       "Response body was discarded or response came from the cache");
 
     lastRequest = null;
     requestCallback = null;
     executeSoon(testPageLoadBody);
   };
 
   content.location = TEST_NETWORK_REQUEST_URI;
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_shows_reqs_in_netmonitor.js
@@ -0,0 +1,71 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TEST_URI = "data:text/html;charset=utf8,Test that the web console " +
+                 "displays requests that have been recorded in the " +
+                 "netmonitor, even if the console hadn't opened yet.";
+
+const TEST_FILE = "test-network-request.html";
+const TEST_PATH = "http://example.com/browser/browser/devtools/webconsole/test/" +
+                 TEST_FILE;
+
+const NET_PREF = "devtools.webconsole.filter.networkinfo";
+Services.prefs.setBoolPref(NET_PREF, true);
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref(NET_PREF);
+});
+
+add_task(function* () {
+  let { tab, browser } = yield loadTab(TEST_URI);
+
+  let target = TargetFactory.forTab(tab);
+  let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
+  info("Network panel is open.");
+
+  yield loadDocument(browser);
+  info("Document loaded.");
+
+  // Test that the request appears in the network panel.
+  testNetmonitor(toolbox);
+
+  // Test that the request appears in the console.
+  let hud = yield openConsole();
+  info("Web console is open");
+
+  yield waitForMessages({
+    webconsole: hud,
+    messages: [
+      {
+        name: "network message",
+        text: TEST_FILE,
+        category: CATEGORY_NETWORK,
+        severity: SEVERITY_LOG
+      }
+    ]
+  });
+});
+
+
+function loadDocument(browser) {
+  let deferred = promise.defer();
+
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    deferred.resolve();
+  }, true);
+  content.location = TEST_PATH;
+
+  return deferred.promise;
+}
+
+function testNetmonitor(toolbox) {
+  let monitor = toolbox.getCurrentPanel();
+  let { RequestsMenu } = monitor.panelWin.NetMonitorView;
+  is(RequestsMenu.itemCount, 1, "Network request appears in the network panel");
+
+  let item = RequestsMenu.getItemAtIndex(0);
+  is(item.attachment.method, "GET", "The attached method is correct.");
+  is(item.attachment.url, TEST_PATH, "The attached url is correct.");
+}
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -10,17 +10,16 @@ let {console} = Cu.import("resource://gr
 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let {require, TargetFactory} = devtools;
 let {Utils: WebConsoleUtils} = require("devtools/toolkit/webconsole/utils");
 let {Messages} = require("devtools/webconsole/console-output");
 const asyncStorage = require("devtools/toolkit/shared/async-storage");
 
-// promise._reportErrors = true; // please never leave me.
 //Services.prefs.setBoolPref("devtools.debugger.log", true);
 
 let gPendingOutputTest = 0;
 
 // The various categories of messages.
 const CATEGORY_NETWORK = 0;
 const CATEGORY_CSS = 1;
 const CATEGORY_JS = 2;
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -200,17 +200,16 @@ function WebConsoleFrame(aWebConsoleOwne
   this.owner = aWebConsoleOwner;
   this.hudId = this.owner.hudId;
   this.window = this.owner.iframeWindow;
 
   this._repeatNodes = {};
   this._outputQueue = [];
   this._itemDestroyQueue = [];
   this._pruneCategoriesQueue = {};
-  this._networkRequests = {};
   this.filterPrefs = {};
 
   this.output = new ConsoleOutput(this);
 
   this._toggleFilter = this._toggleFilter.bind(this);
   this._onPanelSelected = this._onPanelSelected.bind(this);
   this._flushMessageQueue = this._flushMessageQueue.bind(this);
   this._onToolboxPrefChanged = this._onToolboxPrefChanged.bind(this);
@@ -249,24 +248,16 @@ WebConsoleFrame.prototype = {
   /**
    * Holds the initialization promise object.
    * @private
    * @type object
    */
   _initDefer: null,
 
   /**
-   * Holds the network requests currently displayed by the Web Console. Each key
-   * represents the connection ID and the value is network request information.
-   * @private
-   * @type object
-   */
-  _networkRequests: null,
-
-  /**
    * Last time when we displayed any message in the output.
    *
    * @private
    * @type number
    *       Timestamp in milliseconds since the Unix epoch.
    */
   _lastOutputFlush: 0,
 
@@ -1217,16 +1208,19 @@ WebConsoleFrame.prototype = {
         }
         case "LogMessage":
           this.handleLogMessage(aMessage);
           break;
         case "ConsoleAPI":
           this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage,
                              [aMessage]);
           break;
+        case "NetworkEvent":
+          this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [aMessage]);
+          break;
       }
     }, this);
   },
 
   /**
    * Logs a message to the Web Console that originates from the Web Console
    * server.
    *
@@ -1523,29 +1517,24 @@ WebConsoleFrame.prototype = {
       node._objectActors = new Set([aPacket.message.actor]);
     }
     return node;
   },
 
   /**
    * Log network event.
    *
-   * @param object aActor
-   *        The network event actor to log.
+   * @param object networkInfo
+   *        The network request information to log.
    * @return nsIDOMElement|null
    *         The message element to display in the Web Console output.
    */
-  logNetEvent: function WCF_logNetEvent(aActor)
+  logNetEvent: function(networkInfo)
   {
-    let actorId = aActor.actor;
-    let networkInfo = this._networkRequests[actorId];
-    if (!networkInfo) {
-      return null;
-    }
-
+    let actorId = networkInfo.actor;
     let request = networkInfo.request;
     let clipboardText = request.method + " " + request.url;
     let severity = SEVERITY_LOG;
     if (networkInfo.isXHR) {
       clipboardText = request.method + " XHR " + request.url;
       severity = SEVERITY_INFO;
     }
     let mixedRequest =
@@ -1555,17 +1544,18 @@ WebConsoleFrame.prototype = {
     }
 
     let methodNode = this.document.createElementNS(XHTML_NS, "span");
     methodNode.className = "method";
     methodNode.textContent = request.method + " ";
 
     let messageNode = this.createMessageNode(CATEGORY_NETWORK, severity,
                                              methodNode, null, null,
-                                             clipboardText);
+                                             clipboardText, null,
+                                             networkInfo.timeStamp);
     if (networkInfo.private) {
       messageNode.setAttribute("private", true);
     }
     messageNode._connectionId = actorId;
     messageNode.url = request.url;
 
     let body = methodNode.parentNode;
     body.setAttribute("aria-haspopup", true);
@@ -1793,94 +1783,39 @@ WebConsoleFrame.prototype = {
     let node = this.createMessageNode(CATEGORY_JS, SEVERITY_WARNING,
                                       l10n.getStr("ConsoleAPIDisabled"));
     this.outputMessage(CATEGORY_JS, node);
   },
 
   /**
    * Handle the network events coming from the remote Web Console.
    *
-   * @param object aActor
-   *        The NetworkEventActor grip.
+   * @param object networkInfo
+   *        The network request information.
    */
-  handleNetworkEvent: function WCF_handleNetworkEvent(aActor)
+  handleNetworkEvent: function(networkInfo)
   {
-    let networkInfo = {
-      node: null,
-      actor: aActor.actor,
-      discardRequestBody: true,
-      discardResponseBody: true,
-      startedDateTime: aActor.startedDateTime,
-      request: {
-        url: aActor.url,
-        method: aActor.method,
-      },
-      isXHR: aActor.isXHR,
-      response: {},
-      timings: {},
-      updates: [], // track the list of network event updates
-      private: aActor.private,
-    };
-
-    this._networkRequests[aActor.actor] = networkInfo;
-    this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [aActor]);
+    this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [networkInfo]);
   },
 
   /**
    * Handle network event updates coming from the server.
    *
-   * @param string aActorId
-   *        The network event actor ID.
-   * @param string aType
-   *        Update type.
-   * @param object aPacket
+   * @param object networkInfo
+   *        The network request information.
+   * @param object packet
    *        Update details.
    */
-  handleNetworkEventUpdate:
-  function WCF_handleNetworkEventUpdate(aActorId, aType, aPacket)
+  handleNetworkEventUpdate: function(networkInfo, packet)
   {
-    let networkInfo = this._networkRequests[aActorId];
-    if (!networkInfo) {
-      return;
-    }
-
-    networkInfo.updates.push(aType);
-
-    switch (aType) {
-      case "requestHeaders":
-        networkInfo.request.headersSize = aPacket.headersSize;
-        break;
-      case "requestPostData":
-        networkInfo.discardRequestBody = aPacket.discardRequestBody;
-        networkInfo.request.bodySize = aPacket.dataSize;
-        break;
-      case "responseStart":
-        networkInfo.response.httpVersion = aPacket.response.httpVersion;
-        networkInfo.response.status = aPacket.response.status;
-        networkInfo.response.statusText = aPacket.response.statusText;
-        networkInfo.response.headersSize = aPacket.response.headersSize;
-        networkInfo.discardResponseBody = aPacket.response.discardResponseBody;
-        break;
-      case "responseContent":
-        networkInfo.response.content = {
-          mimeType: aPacket.mimeType,
-        };
-        networkInfo.response.bodySize = aPacket.contentSize;
-        networkInfo.discardResponseBody = aPacket.discardResponseBody;
-        break;
-      case "eventTimings":
-        networkInfo.totalTime = aPacket.totalTime;
-        break;
-    }
-
-    if (networkInfo.node && this._updateNetMessage(aActorId)) {
+    if (networkInfo.node && this._updateNetMessage(packet.from)) {
       this.emit("new-messages", new Set([{
         update: true,
         node: networkInfo.node,
-        response: aPacket,
+        response: packet,
       }]));
     }
 
     // For unit tests we pass the HTTP activity object to the test callback,
     // once requests complete.
     if (this.owner.lastFinishedRequestCallback &&
         networkInfo.updates.indexOf("responseContent") > -1 &&
         networkInfo.updates.indexOf("eventTimings") > -1) {
@@ -1895,17 +1830,17 @@ WebConsoleFrame.prototype = {
    * @private
    * @param string aActorId
    *        The network event actor ID for which you want to update the message.
    * @return boolean
    *         |true| if the message node was updated, or |false| otherwise.
    */
   _updateNetMessage: function WCF__updateNetMessage(aActorId)
   {
-    let networkInfo = this._networkRequests[aActorId];
+    let networkInfo = this.webConsoleClient.getNetworkRequest(aActorId);
     if (!networkInfo || !networkInfo.node) {
       return;
     }
 
     let messageNode = networkInfo.node;
     let updates = networkInfo.updates;
     let hasEventTimings = updates.indexOf("eventTimings") > -1;
     let hasResponseStart = updates.indexOf("responseStart") > -1;
@@ -2441,18 +2376,18 @@ WebConsoleFrame.prototype = {
     if (category == CATEGORY_NETWORK) {
       let connectionId = null;
       if (methodOrNode == this.logNetEvent) {
         connectionId = args[0].actor;
       }
       else if (typeof methodOrNode != "function") {
         connectionId = methodOrNode._connectionId;
       }
-      if (connectionId && connectionId in this._networkRequests) {
-        delete this._networkRequests[connectionId];
+      if (connectionId && this.webConsoleClient.hasNetworkRequest(connectionId)) {
+        this.webConsoleClient.removeNetworkRequest(connectionId);
         this._releaseObject(connectionId);
       }
     }
     else if (category == CATEGORY_WEBDEV &&
              methodOrNode == this.logConsoleAPIMessage) {
       args[0].arguments.forEach((aValue) => {
         if (WebConsoleUtils.isActorGrip(aValue)) {
           this._releaseObject(aValue.actor);
@@ -2519,17 +2454,17 @@ WebConsoleFrame.prototype = {
         aNode.category == CATEGORY_SECURITY) {
       let repeatNode = aNode.getElementsByClassName("message-repeats")[0];
       if (repeatNode && repeatNode._uid) {
         delete this._repeatNodes[repeatNode._uid];
       }
     }
     else if (aNode._connectionId &&
              aNode.category == CATEGORY_NETWORK) {
-      delete this._networkRequests[aNode._connectionId];
+      this.webConsoleClient.removeNetworkRequest(aNode._connectionId);
       this._releaseObject(aNode._connectionId);
     }
     else if (aNode.classList.contains("inlined-variables-view")) {
       let view = aNode._variablesView;
       if (view) {
         view.controller.releaseActors();
       }
       aNode._variablesView = null;
@@ -3013,17 +2948,17 @@ WebConsoleFrame.prototype = {
     gDevTools.off("pref-changed", this._onToolboxPrefChanged);
 
     this._repeatNodes = {};
     this._outputQueue.forEach(this._destroyItem, this);
     this._outputQueue = [];
     this._itemDestroyQueue.forEach(this._destroyItem, this);
     this._itemDestroyQueue = [];
     this._pruneCategoriesQueue = {};
-    this._networkRequests = {};
+    this.webConsoleClient.clearNetworkRequests();
 
     if (this._outputTimerInitialized) {
       this._outputTimerInitialized = false;
       this._outputTimer.cancel();
     }
     this._outputTimer = null;
     if (this.jsterm) {
       this.jsterm.destroy();
@@ -3967,17 +3902,17 @@ JSTerm.prototype = {
     let node;
     while ((node = outputNode.firstChild)) {
       hud.removeOutputMessage(node);
     }
 
     hud.groupDepth = 0;
     hud._outputQueue.forEach(hud._destroyItem, hud);
     hud._outputQueue = [];
-    hud._networkRequests = {};
+    this.webConsoleClient.clearNetworkRequests();
     hud._repeatNodes = {};
 
     if (aClearStorage) {
       this.webConsoleClient.clearMessagesCache();
     }
 
     this._sidebarDestroy();
 
@@ -5109,18 +5044,16 @@ WebConsoleConnectionProxy.prototype = {
       this._connectTimer = null;
     });
 
     let client = this.client = this.target.client;
 
     client.addListener("logMessage", this._onLogMessage);
     client.addListener("pageError", this._onPageError);
     client.addListener("consoleAPICall", this._onConsoleAPICall);
-    client.addListener("networkEvent", this._onNetworkEvent);
-    client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
     client.addListener("fileActivity", this._onFileActivity);
     client.addListener("reflowActivity", this._onReflowActivity);
     client.addListener("lastPrivateContextExited", this._onLastPrivateContextExited);
     this.target.on("will-navigate", this._onTabNavigated);
     this.target.on("navigate", this._onTabNavigated);
 
     this._consoleActor = this.target.form.consoleActor;
     if (this.target.isTabActor) {
@@ -5175,16 +5108,18 @@ WebConsoleConnectionProxy.prototype = {
                      aResponse.message);
       this._connectDefer.reject(aResponse);
       return;
     }
 
     this.webConsoleClient = aWebConsoleClient;
 
     this._hasNativeConsoleAPI = aResponse.nativeConsoleAPI;
+    this.webConsoleClient.on("networkEvent", this._onNetworkEvent);
+    this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate);
 
     let msgs = ["PageError", "ConsoleAPI"];
     this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages);
 
     this.owner._updateReflowActivityListener();
   },
 
   /**
@@ -5204,17 +5139,20 @@ WebConsoleConnectionProxy.prototype = {
     }
 
     if (!this._connectTimer) {
       // This happens if the promise is rejected (eg. a timeout), but the
       // connection attempt is successful, nonetheless.
       Cu.reportError("Web Console getCachedMessages error: invalid state.");
     }
 
-    this.owner.displayCachedMessages(aResponse.messages);
+    let messages = aResponse.messages.concat(...this.webConsoleClient.getNetworkEvents());
+    messages.sort((a, b) => a.timeStamp - b.timeStamp);
+
+    this.owner.displayCachedMessages(messages);
 
     if (!this._hasNativeConsoleAPI) {
       this.owner.logWarningAboutReplacedAPI();
     }
 
     this.connected = true;
     this._connectDefer.resolve(this);
   },
@@ -5270,43 +5208,44 @@ WebConsoleConnectionProxy.prototype = {
     }
   },
 
   /**
    * The "networkEvent" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
-   * @param string aType
+   * @param string type
    *        Message type.
-   * @param object aPacket
-   *        The message received from the server.
+   * @param object networkInfo
+   *        The network request information.
    */
-  _onNetworkEvent: function WCCP__onNetworkEvent(aType, aPacket)
+  _onNetworkEvent: function(type, networkInfo)
   {
-    if (this.owner && aPacket.from == this._consoleActor) {
-      this.owner.handleNetworkEvent(aPacket.eventActor);
+    if (this.owner) {
+      this.owner.handleNetworkEvent(networkInfo);
     }
   },
 
   /**
    * The "networkEventUpdate" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
-   * @param string aType
+   * @param string type
    *        Message type.
-   * @param object aPacket
+   * @param object packet
    *        The message received from the server.
+   * @param object networkInfo
+   *        The network request information.
    */
-  _onNetworkEventUpdate: function WCCP__onNetworkEvenUpdatet(aType, aPacket)
+  _onNetworkEventUpdate: function(type, { packet, networkInfo })
   {
     if (this.owner) {
-      this.owner.handleNetworkEventUpdate(aPacket.from, aPacket.updateType,
-                                          aPacket);
+      this.owner.handleNetworkEventUpdate(networkInfo, packet);
     }
   },
 
   /**
    * The "fileActivity" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
@@ -5396,21 +5335,21 @@ WebConsoleConnectionProxy.prototype = {
     if (!this.client) {
       this._disconnecter.resolve(null);
       return this._disconnecter.promise;
     }
 
     this.client.removeListener("logMessage", this._onLogMessage);
     this.client.removeListener("pageError", this._onPageError);
     this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
-    this.client.removeListener("networkEvent", this._onNetworkEvent);
-    this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
     this.client.removeListener("fileActivity", this._onFileActivity);
     this.client.removeListener("reflowActivity", this._onReflowActivity);
     this.client.removeListener("lastPrivateContextExited", this._onLastPrivateContextExited);
+    this.webConsoleClient.off("networkEvent", this._onNetworkEvent);
+    this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate);
     this.target.off("will-navigate", this._onTabNavigated);
     this.target.off("navigate", this._onTabNavigated);
 
     this.client = null;
     this.webConsoleClient = null;
     this.target = null;
     this.connected = false;
     this.owner = null;
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -249,20 +249,17 @@ Section "-InstallStartCleanup"
 
   ; setup the application model id registration value
   ${InitHashAppModelId} "$INSTDIR" "Software\Mozilla\${AppName}\TaskBarIDs"
 
   ; Remove the updates directory for Vista and above
   ${CleanUpdateDirectories} "Mozilla\Firefox" "Mozilla\updates"
 
   ${RemoveDeprecatedFiles}
-
-  StrCpy $R2 "false"
-  StrCpy $R3 "false"
-  ${RemovePrecompleteEntries} "$R2" "$R3"
+  ${RemovePrecompleteEntries} "false"
 
   ${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js"
     Delete "$INSTDIR\defaults\pref\channel-prefs.js"
   ${EndIf}
   ${If} ${FileExists} "$INSTDIR\defaults\pref"
     RmDir "$INSTDIR\defaults\pref"
   ${EndIf}
   ${If} ${FileExists} "$INSTDIR\defaults"
--- a/browser/installer/windows/nsis/stub.nsi
+++ b/browser/installer/windows/nsis/stub.nsi
@@ -66,16 +66,18 @@ Var DownloadReset
 Var ExistingTopDir
 Var SpaceAvailableBytes
 Var InitialInstallDir
 Var HandleDownload
 Var CanSetAsDefault
 Var InstallCounterStep
 Var InstallStepSize
 Var InstallTotalSteps
+Var ProgressCompleted
+Var ProgressTotal
 Var TmpVal
 
 Var ExitCode
 Var FirefoxLaunchCode
 
 ; The first three tick counts are for the start of a phase and equate equate to
 ; the display of individual installer pages.
 Var StartIntroPhaseTickCount
@@ -253,16 +255,17 @@ Var ControlRightPX
 !insertmacro GetLongPath
 !insertmacro GetPathFromString
 !insertmacro GetParent
 !insertmacro GetSingleInstallPath
 !insertmacro GetTextWidthHeight
 !insertmacro IsUserAdmin
 !insertmacro RemovePrecompleteEntries
 !insertmacro SetBrandNameVars
+!insertmacro ITBL3Create
 !insertmacro UnloadUAC
 
 VIAddVersionKey "FileDescription" "${BrandShortName} Stub Installer"
 VIAddVersionKey "OriginalFilename" "setup-stub.exe"
 
 Name "$BrandFullName"
 OutFile "setup-stub.exe"
 icon "setup.ico"
@@ -1288,16 +1291,19 @@ Function createInstall
   Pop $StartDownloadPhaseTickCount
 
   ${If} ${FileExists} "$INSTDIR\uninstall\uninstall.log"
     StrCpy $InstallTotalSteps ${InstallPaveOverTotalSteps}
   ${Else}
     StrCpy $InstallTotalSteps ${InstallCleanTotalSteps}
   ${EndIf}
 
+  ${ITBL3Create}
+  ${ITBL3SetProgressState} "${TBPF_INDETERMINATE}"
+
   ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS}
 
   LockWindow off
   nsDialogs::Show
 
   ${NSD_FreeImage} $0
   ${NSD_FreeImage} $HwndBitmapBlurb1
   ${NSD_FreeImage} $HwndBitmapBlurb2
@@ -1310,16 +1316,31 @@ Function StartDownload
                 /CONNECTTIMEOUT 120 /RECEIVETIMEOUT 120 /END
   StrCpy $4 ""
   ${NSD_CreateTimer} OnDownload ${DownloadIntervalMS}
   ${If} ${FileExists} "$INSTDIR\${TO_BE_DELETED}"
     RmDir /r "$INSTDIR\${TO_BE_DELETED}"
   ${EndIf}
 FunctionEnd
 
+Function SetProgressBars
+  SendMessage $Progressbar ${PBM_SETPOS} $ProgressCompleted 0
+  ${ITBL3SetProgressValue} "$ProgressCompleted" "$ProgressTotal"
+FunctionEnd
+
+Function RemoveFileProgressCallback
+  IntOp $InstallCounterStep $InstallCounterStep + 2
+  System::Int64Op $ProgressCompleted + $InstallStepSize
+  Pop $ProgressCompleted
+  Call SetProgressBars
+  System::Int64Op $ProgressCompleted + $InstallStepSize
+  Pop $ProgressCompleted
+  Call SetProgressBars
+FunctionEnd
+
 Function OnDownload
   InetBgDL::GetStats
   # $0 = HTTP status code, 0=Completed
   # $1 = Completed files
   # $2 = Remaining files
   # $3 = Number of downloaded bytes for the current file
   # $4 = Size of current file (Empty string if the size is unknown)
   # /RESET must be used if status $0 > 299 (e.g. failure)
@@ -1328,16 +1349,17 @@ Function OnDownload
   ${If} $0 > 299
     ${NSD_KillTimer} OnDownload
     IntOp $DownloadRetryCount $DownloadRetryCount + 1
     ${If} "$DownloadReset" != "true"
       StrCpy $DownloadedBytes "0"
       ${NSD_AddStyle} $Progressbar ${PBS_MARQUEE}
       SendMessage $Progressbar ${PBM_SETMARQUEE} 1 \
                   $ProgressbarMarqueeIntervalMS ; start=1|stop=0 interval(ms)=+N
+      ${ITBL3SetProgressState} "${TBPF_INDETERMINATE}"
     ${EndIf}
     InetBgDL::Get /RESET /END
     StrCpy $DownloadSizeBytes ""
     StrCpy $DownloadReset "true"
 
     ${If} $DownloadRetryCount >= ${DownloadMaxRetries}
       StrCpy $ExitCode "${ERR_DOWNLOAD_TOO_MANY_RETRIES}"
       ; Use a timer so the UI has a chance to update
@@ -1385,18 +1407,19 @@ Function OnDownload
     StrCpy $DownloadSizeBytes "$4"
     System::Int64Op $4 / 2
     Pop $HalfOfDownload
     System::Int64Op $HalfOfDownload / $InstallTotalSteps
     Pop $InstallStepSize
     SendMessage $Progressbar ${PBM_SETMARQUEE} 0 0 ; start=1|stop=0 interval(ms)=+N
     ${RemoveStyle} $Progressbar ${PBS_MARQUEE}
     System::Int64Op $HalfOfDownload + $DownloadSizeBytes
-    Pop $R9
-    SendMessage $Progressbar ${PBM_SETRANGE32} 0 $R9
+    Pop $ProgressTotal
+    StrCpy $ProgressCompleted 0
+    SendMessage $Progressbar ${PBM_SETRANGE32} $ProgressCompleted $ProgressTotal
   ${EndIf}
 
   ; Don't update the status until after the download starts
   ${If} $2 != 0
   ${AndIf} "$4" == ""
     Return
   ${EndIf}
 
@@ -1443,22 +1466,23 @@ Function OnDownload
           ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS}
         ${EndIf}
         Return
       ${EndIf}
 
       LockWindow on
       ; Update the progress bars first in the UI change so they take affect
       ; before other UI changes.
-      SendMessage $Progressbar ${PBM_SETPOS} $DownloadSizeBytes 0
+      StrCpy $ProgressCompleted "$DownloadSizeBytes"
+      Call SetProgressBars
       System::Int64Op $InstallStepSize * ${InstallProgressFirstStep}
       Pop $R9
-      SendMessage $Progressbar ${PBM_SETSTEP} $R9 0
-      SendMessage $Progressbar ${PBM_STEPIT} 0 0
-      SendMessage $Progressbar ${PBM_SETSTEP} $InstallStepSize 0
+      System::Int64Op $ProgressCompleted + $R9
+      Pop $ProgressCompleted
+      Call SetProgressBars
       ShowWindow $LabelDownloading ${SW_HIDE}
       ShowWindow $LabelInstalling ${SW_SHOW}
       ShowWindow $LabelBlurb2 ${SW_HIDE}
       ShowWindow $BitmapBlurb2 ${SW_HIDE}
       ShowWindow $LabelBlurb3 ${SW_SHOW}
       ShowWindow $BitmapBlurb3 ${SW_SHOW}
       ; Disable the Cancel button during the install
       GetDlgItem $5 $HWNDPARENT 2
@@ -1535,17 +1559,18 @@ Function OnDownload
       ; from creating a taskbar shortcut (Bug 791613).
       ${GetShortcutsLogPath} $0
       Delete "$0"
       ; Workaround to prevent pinning to the taskbar.
       ${If} $CheckboxShortcutOnBar == 0
         WriteIniStr "$0" "TASKBAR" "Migrated" "true"
       ${EndIf}
 
-      ${RemovePrecompleteEntries} $Progressbar $InstallCounterStep
+      GetFunctionAddress $0 RemoveFileProgressCallback
+      ${RemovePrecompleteEntries} $0
 
       ; Delete the install.log and let the full installer create it. When the
       ; installer closes it we can detect that it has completed.
       Delete "$INSTDIR\install.log"
 
       ; Delete firefox.exe.moz-upgrade and firefox.exe.moz-delete if it exists
       ; since it being present will require an OS restart for the full
       ; installer.
@@ -1564,17 +1589,18 @@ Function OnDownload
         LockWindow on
         ShowWindow $LabelBlurb1 ${SW_HIDE}
         ShowWindow $BitmapBlurb1 ${SW_HIDE}
         ShowWindow $LabelBlurb2 ${SW_SHOW}
         ShowWindow $BitmapBlurb2 ${SW_SHOW}
         LockWindow off
       ${EndIf}
       StrCpy $DownloadedBytes "$3"
-      SendMessage $Progressbar ${PBM_SETPOS} $3 0
+      StrCpy $ProgressCompleted "$DownloadedBytes"
+      Call SetProgressBars
     ${EndIf}
   ${EndIf}
 FunctionEnd
 
 Function OnPing
   InetBgDL::GetStats
   # $0 = HTTP status code, 0=Completed
   # $1 = Completed files
@@ -1603,17 +1629,19 @@ Function CheckInstall
     ; Close the handle that prevents modification of the full installer
     System::Call 'kernel32::CloseHandle(i $HandleDownload)'
     StrCpy $ExitCode "${ERR_INSTALL_TIMEOUT}"
     ; Use a timer so the UI has a chance to update
     ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
     Return
   ${EndIf}
 
-  SendMessage $Progressbar ${PBM_STEPIT} 0 0
+  System::Int64Op $ProgressCompleted + $InstallStepSize
+  Pop $ProgressCompleted
+  Call SetProgressBars
 
   ${If} ${FileExists} "$INSTDIR\install.log"
     Delete "$INSTDIR\install.tmp"
     CopyFiles /SILENT "$INSTDIR\install.log" "$INSTDIR\install.tmp"
 
     ; The unfocus and refocus that happens approximately here is caused by the
     ; installer calling SHChangeNotify to refresh the shortcut icons.
 
@@ -1627,39 +1655,40 @@ Function CheckInstall
       System::Call 'kernel32::CloseHandle(i $HandleDownload)'
       Rename "$INSTDIR\install.tmp" "$INSTDIR\install.log"
       Delete "$PLUGINSDIR\download.exe"
       Delete "$PLUGINSDIR\${CONFIG_INI}"
       System::Call "kernel32::GetTickCount()l .s"
       Pop $EndInstallPhaseTickCount
       System::Int64Op $InstallStepSize * ${InstallProgressFinishStep}
       Pop $InstallStepSize
-      SendMessage $Progressbar ${PBM_SETSTEP} $InstallStepSize 0
       ${NSD_CreateTimer} FinishInstall ${InstallIntervalMS}
     ${EndUnless}
   ${EndIf}
 FunctionEnd
 
 Function FinishInstall
   ; The full installer has completed but the progress bar still needs to finish
   ; so increase the size of the step.
   IntOp $InstallCounterStep $InstallCounterStep + ${InstallProgressFinishStep}
   ${If} $InstallTotalSteps < $InstallCounterStep
     StrCpy $InstallCounterStep "$InstallTotalSteps"
   ${EndIf}
 
   ${If} $InstallTotalSteps != $InstallCounterStep
-    SendMessage $Progressbar ${PBM_STEPIT} 0 0
+    System::Int64Op $ProgressCompleted + $InstallStepSize
+    Pop $ProgressCompleted
+    Call SetProgressBars
     Return
   ${EndIf}
 
   ${NSD_KillTimer} FinishInstall
 
-  SendMessage $Progressbar ${PBM_GETRANGE} 0 0 $R9
-  SendMessage $Progressbar ${PBM_SETPOS} $R9 0
+  StrCpy $ProgressCompleted "$ProgressTotal"
+  Call SetProgressBars
 
   ${If} "$CheckboxSetAsDefault" == "1"
     ${GetParameters} $0
     ClearErrors
     ${GetOptions} "$0" "/UAC:" $0
     ${If} ${Errors} ; Not elevated
       Call ExecSetAsDefaultAppUser
     ${Else} ; Elevated - execute the function in the unelevated process
@@ -1930,16 +1959,20 @@ Function LaunchAppFromElevatedProcess
   ; Set the current working directory to the installation directory
   ${GetParent} "$0" $1
   SetOutPath "$1"
   Exec "$\"$0$\""
 FunctionEnd
 
 Function DisplayDownloadError
   ${NSD_KillTimer} DisplayDownloadError
+  ; To better display the error state on the taskbar set the progress completed
+  ; value to the total value.
+  ${ITBL3SetProgressValue} "100" "100"
+  ${ITBL3SetProgressState} "${TBPF_ERROR}"
   MessageBox MB_OKCANCEL|MB_ICONSTOP "$(ERROR_DOWNLOAD)" IDCANCEL +2 IDOK +1
   StrCpy $OpenedDownloadPage "1" ; Already initialized to 0
 
   ${If} "$OpenedDownloadPage" == "1"
     ClearErrors
     ${GetParameters} $0
     ${GetOptions} "$0" "/UAC:" $1
     ${If} ${Errors}
--- a/browser/installer/windows/nsis/uninstaller.nsi
+++ b/browser/installer/windows/nsis/uninstaller.nsi
@@ -392,19 +392,17 @@ Section "Uninstall"
   ${un.CleanVirtualStore}
 
   ; Only unregister the dll if the registration points to this installation
   ReadRegStr $R1 HKCR "CLSID\{0D68D6D0-D93D-4D08-A30D-F00DD1F45B24}\InProcServer32" ""
   ${If} "$INSTDIR\AccessibleMarshal.dll" == "$R1"
     ${UnregisterDLL} "$INSTDIR\AccessibleMarshal.dll"
   ${EndIf}
 
-  StrCpy $R2 "false"
-  StrCpy $R3 "false"
-  ${un.RemovePrecompleteEntries} "$R2" "$R3"
+  ${un.RemovePrecompleteEntries} "false"
 
   ${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js"
     Delete /REBOOTOK "$INSTDIR\defaults\pref\channel-prefs.js"
   ${EndIf}
   ${If} ${FileExists} "$INSTDIR\defaults\pref"
     RmDir /REBOOTOK "$INSTDIR\defaults\pref"
   ${EndIf}
   ${If} ${FileExists} "$INSTDIR\defaults"
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -32,27 +32,36 @@ xpinstallDisabledButton.accesskey=n
 
 # LOCALIZATION NOTE (addonDownloadingAndVerifying):
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # Also see https://bugzilla.mozilla.org/show_bug.cgi?id=570012 for mockups
 addonDownloadingAndVerifying=Downloading and verifying add-on…;Downloading and verifying #1 add-ons…
 addonDownloadVerifying=Verifying
 
+addonInstall.unsigned=(Unverified)
 addonInstall.cancelButton.label=Cancel
 addonInstall.cancelButton.accesskey=C
 addonInstall.acceptButton.label=Install
 addonInstall.acceptButton.accesskey=I
 
-# LOCALIZATION NOTE (addonConfirmInstallMessage):
+# LOCALIZATION NOTE (addonConfirmInstallMessage,addonConfirmInstallUnsigned):
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 is brandShortName
 # #2 is the number of add-ons being installed
 addonConfirmInstall.message=This site would like to install an add-on in #1:;This site would like to install #2 add-ons in #1:
+addonConfirmInstallUnsigned.message=Caution: This site would like to install an unverified add-on in #1. Proceed at your own risk.;Caution: This site would like to install #2 unverified add-ons in #1. Proceed at your own risk.
+
+# LOCALIZATION NOTE (addonConfirmInstallSomeUnsigned.message):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is brandShortName
+# #2 is the total number of add-ons being installed (at least 2)
+addonConfirmInstallSomeUnsigned.message=;Caution: This site would like to install #2 add-ons in #1, some of which are unverified. Proceed at your own risk.
 
 addonwatch.slow=%1$S might be making %2$S run slowly
 addonwatch.disable.label=Disable %S
 addonwatch.ignoreSession.label=Ignore for now
 addonwatch.ignoreSession.accesskey=I
 addonwatch.ignorePerm.label=Ignore permanently
 addonwatch.ignorePerm.accesskey=p
 addonwatch.restart.message=To disable %1$S you must restart %2$S
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -335,8 +335,12 @@ context_offer_label=Let's talk about thi
 context_inroom_label=Let's talk about:
 ## LOCALIZATION_NOTE (context_edit_activate_label): {{title}} will be replaced
 ## by the title of the active tab, also known as the title of an HTML document.
 ## The quotes around the title are intentional.
 context_edit_activate_label=Talk about "{{title}}"
 context_edit_name_placeholder=Conversation Name
 context_edit_comments_placeholder=Comments
 context_add_some_label=Add some context
+context_edit_tooltip=Edit Context
+context_hide_tooltip=Hide Context
+context_show_tooltip=Show Context
+context_save_label=Save Context
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1857,16 +1857,17 @@ toolbarbutton.chevron > .toolbarbutton-i
 #full-screen-remember-decision {
   font-size: 120%;
 }
 
 %include ../shared/devtools/responsivedesign.inc.css
 %include ../shared/devtools/commandline.inc.css
 %include ../shared/plugin-doorhanger.inc.css
 %include ../shared/badcontent-doorhanger.inc.css
+%include ../shared/login-doorhanger.inc.css
 
 %include downloads/indicator.css
 
 .gcli-panel {
   padding: 0;
 }
 
 .gclitoolbar-input-node > .textbox-input-box > html|*.textbox-input::-moz-selection {
deleted file mode 100644
--- a/browser/themes/linux/downloads/contentAreaDownloadsView.css
+++ /dev/null
@@ -1,11 +0,0 @@
-/* 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 url("chrome://global/skin/inContentUI.css");
-
-#downloadsListEmptyDescription {
-  margin: 1em;
-  text-align: center;
-  color: GrayText;
-}
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -129,17 +129,17 @@ browser.jar:
   skin/classic/browser/customizableui/subView-arrow-back-inverted.png  (../shared/customizableui/subView-arrow-back-inverted.png)
   skin/classic/browser/customizableui/subView-arrow-back-inverted-rtl.png  (../shared/customizableui/subView-arrow-back-inverted-rtl.png)
   skin/classic/browser/customizableui/whimsy.png  (../shared/customizableui/whimsy.png)
   skin/classic/browser/customizableui/whimsy@2x.png  (../shared/customizableui/whimsy@2x.png)
   skin/classic/browser/customizableui/whimsy-bw.png  (../shared/customizableui/whimsy-bw.png)
   skin/classic/browser/customizableui/whimsy-bw@2x.png  (../shared/customizableui/whimsy-bw@2x.png)
   skin/classic/browser/downloads/allDownloadsViewOverlay.css   (downloads/allDownloadsViewOverlay.css)
   skin/classic/browser/downloads/buttons.png          (downloads/buttons.png)
-  skin/classic/browser/downloads/contentAreaDownloadsView.css  (downloads/contentAreaDownloadsView.css)
+  skin/classic/browser/downloads/contentAreaDownloadsView.css  (../shared/downloads/contentAreaDownloadsView.css)
   skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
   skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
   skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
   skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png)
   skin/classic/browser/downloads/downloads.css        (downloads/downloads.css)
   skin/classic/browser/feeds/feedIcon.png             (feeds/feedIcon.png)
   skin/classic/browser/feeds/feedIcon16.png           (feeds/feedIcon16.png)
   skin/classic/browser/feeds/subscribe.css            (feeds/subscribe.css)
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3890,16 +3890,17 @@ notification[value="loop-sharing-notific
 #full-screen-remember-decision {
   font-size: 120%;
 }
 
 %include ../shared/devtools/responsivedesign.inc.css
 %include ../shared/devtools/commandline.inc.css
 %include ../shared/plugin-doorhanger.inc.css
 %include ../shared/badcontent-doorhanger.inc.css
+%include ../shared/login-doorhanger.inc.css
 
 %include downloads/indicator.css
 
 /* On mac, the popup notification contents are indented by default and so
   the default closebutton margins from notification.css require adjustment */
 
 .click-to-play-plugins-notification-description-box > .popup-notification-closebutton {
   -moz-margin-end: -6px;
deleted file mode 100644
--- a/browser/themes/osx/downloads/contentAreaDownloadsView.css
+++ /dev/null
@@ -1,22 +0,0 @@
-/* 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 url("chrome://global/skin/inContentUI.css");
-
-.downloadButton {
-  box-shadow: none;
-}
-
-.downloadButton:not([disabled="true"]):hover:active,
-.downloadButton:not([disabled]):hover:active {
-  background: transparent;
-  border: none;
-  box-shadow: none;
-}
-
-#downloadsListEmptyDescription {
-  margin: 1em;
-  text-align: center;
-  color: GrayText;
-}
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -210,17 +210,17 @@ browser.jar:
 * skin/classic/browser/customizableui/panelUIOverlay.css    (customizableui/panelUIOverlay.css)
   skin/classic/browser/customizableui/whimsy.png          (../shared/customizableui/whimsy.png)
   skin/classic/browser/customizableui/whimsy@2x.png       (../shared/customizableui/whimsy@2x.png)
   skin/classic/browser/customizableui/whimsy-bw.png       (../shared/customizableui/whimsy-bw.png)
   skin/classic/browser/customizableui/whimsy-bw@2x.png    (../shared/customizableui/whimsy-bw@2x.png)
   skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
   skin/classic/browser/downloads/buttons.png                (downloads/buttons.png)
   skin/classic/browser/downloads/buttons@2x.png             (downloads/buttons@2x.png)
-  skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
+  skin/classic/browser/downloads/contentAreaDownloadsView.css (../shared/downloads/contentAreaDownloadsView.css)
   skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
   skin/classic/browser/downloads/download-glow-menuPanel@2x.png (downloads/download-glow-menuPanel@2x.png)
   skin/classic/browser/downloads/download-notification-finish.png  (downloads/download-notification-finish.png)
   skin/classic/browser/downloads/download-notification-finish@2x.png  (downloads/download-notification-finish@2x.png)
   skin/classic/browser/downloads/download-notification-start.png  (downloads/download-notification-start.png)
   skin/classic/browser/downloads/download-notification-start@2x.png  (downloads/download-notification-start@2x.png)
   skin/classic/browser/downloads/download-summary.png       (downloads/download-summary.png)
   skin/classic/browser/downloads/download-summary@2x.png    (downloads/download-summary@2x.png)
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -251,16 +251,21 @@ panelview:not([mainview]) .toolbarbutton
   text-align: start;
   display: -moz-box;
 }
 
 .cui-widget-panel > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 4px 0;
 }
 
+.cui-widget-panel[viewId="PanelUI-pocketView"] > .panel-arrowcontainer > .panel-arrowcontent {
+  padding-top: 0;
+  padding-bottom: 0;
+}
+
 .cui-widget-panel.cui-widget-panelWithFooter > .panel-arrowcontainer > .panel-arrowcontent {
   padding-bottom: 0;
 }
 
 #PanelUI-contents {
   display: block;
   flex: 1 0 auto;
   margin-left: auto;
rename from browser/themes/windows/downloads/contentAreaDownloadsView.css
rename to browser/themes/shared/downloads/contentAreaDownloadsView.css
--- a/browser/themes/windows/downloads/contentAreaDownloadsView.css
+++ b/browser/themes/shared/downloads/contentAreaDownloadsView.css
@@ -1,22 +1,30 @@
 /* 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 url("chrome://global/skin/inContentUI.css");
+@import url("chrome://global/skin/in-content/common.css");
 
-.downloadButton {
-  box-shadow: none;
+#contentAreaDownloadsView {
+  padding: 18px;
 }
 
+#downloadsRichListBox:not(:-moz-focusring) {
+  border-color: transparent;
+}
+
+.downloadButton:not([disabled="true"]):hover,
 .downloadButton:not([disabled="true"]):hover:active,
 .downloadButton:not([disabled]):hover:active {
   background: transparent;
   border: none;
-  box-shadow: none;
+}
+
+.downloadButton > .button-box {
+  padding-bottom: 0;
 }
 
 #downloadsListEmptyDescription {
   margin: 1em;
   text-align: center;
   color: GrayText;
 }
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/login-doorhanger.inc.css
@@ -0,0 +1,19 @@
+#login-fill-testing {
+  color: #b33;
+  font-weight: bold;
+}
+
+#login-fill-list {
+  border: 1px solid black;
+  max-height: 20em;
+}
+
+.login-hostname {
+  margin: 4px;
+  font-weight: bold;
+}
+
+.login-username {
+  margin: 4px;
+  color: #888;
+}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2451,16 +2451,17 @@ notification[value="loop-sharing-notific
 #full-screen-remember-decision {
   font-size: 120%;
 }
 
 %include ../shared/devtools/responsivedesign.inc.css
 %include ../shared/devtools/commandline.inc.css
 %include ../shared/plugin-doorhanger.inc.css
 %include ../shared/badcontent-doorhanger.inc.css
+%include ../shared/login-doorhanger.inc.css
 
 %include downloads/indicator.css
 
 /* Error counter */
 
 #developer-toolbar-toolbox-button[error-count]:before {
   color: #FDF3DE;
   min-width: 16px;
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -168,17 +168,17 @@ browser.jar:
         skin/classic/browser/customizableui/subView-arrow-back-inverted-rtl.png  (../shared/customizableui/subView-arrow-back-inverted-rtl.png)
         skin/classic/browser/customizableui/whimsy.png  (../shared/customizableui/whimsy.png)
         skin/classic/browser/customizableui/whimsy@2x.png  (../shared/customizableui/whimsy@2x.png)
         skin/classic/browser/customizableui/whimsy-bw.png  (../shared/customizableui/whimsy-bw.png)
         skin/classic/browser/customizableui/whimsy-bw@2x.png  (../shared/customizableui/whimsy-bw@2x.png)
         skin/classic/browser/downloads/allDownloadsViewOverlay.css   (downloads/allDownloadsViewOverlay.css)
         skin/classic/browser/downloads/buttons.png                   (downloads/buttons.png)
         skin/classic/browser/downloads/buttons-XP.png                (downloads/buttons-XP.png)
-        skin/classic/browser/downloads/contentAreaDownloadsView.css  (downloads/contentAreaDownloadsView.css)
+        skin/classic/browser/downloads/contentAreaDownloadsView.css  (../shared/downloads/contentAreaDownloadsView.css)
         skin/classic/browser/downloads/download-glow-menuPanel-XPVista7.png   (downloads/download-glow-menuPanel-XPVista7.png)
         skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
         skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
         skin/classic/browser/downloads/download-summary.png          (downloads/download-summary.png)
         skin/classic/browser/downloads/downloads.css                 (downloads/downloads.css)
         skin/classic/browser/feeds/feedIcon.png                      (feeds/feedIcon.png)
         skin/classic/browser/feeds/feedIcon16.png                    (feeds/feedIcon16.png)
         skin/classic/browser/feeds/feedIcon-XP.png                   (feeds/feedIcon-XP.png)
--- a/configure.in
+++ b/configure.in
@@ -3514,20 +3514,20 @@ case "$GRE_MILESTONE" in
       RELEASE_BUILD=1
       AC_DEFINE(RELEASE_BUILD)
       ;;
 esac
 AC_SUBST(NIGHTLY_BUILD)
 AC_SUBST(RELEASE_BUILD)
 
 dnl ========================================================
-dnl Multiprocess Firefox Nightly Testing UI
+dnl Multiprocess Firefox Testing UI - Nightly and Aurora
 dnl To be removed in Bug 1003313
 dnl ========================================================
-if test -n "$NIGHTLY_BUILD"; then
+if test -z "$RELEASE_BUILD"; then
     E10S_TESTING_ONLY=1
     AC_DEFINE(E10S_TESTING_ONLY)
 fi
 
 AC_SUBST(E10S_TESTING_ONLY)
 
 dnl ========================================================
 dnl system libevent Support
--- a/dom/bluetooth/bluez/BluetoothSocket.cpp
+++ b/dom/bluetooth/bluez/BluetoothSocket.cpp
@@ -59,21 +59,20 @@ public:
    */
   void Connect();
 
   void Send(UnixSocketIOBuffer* aBuffer);
 
   // I/O callback methods
   //
 
-  void OnAccepted(int aFd, const sockaddr_any* aAddr,
-                  socklen_t aAddrLen) override;
   void OnConnected() override;
   void OnError(const char* aFunction, int aErrno) override;
   void OnListening() override;
+  void OnSocketCanAcceptWithoutBlocking() override;
   void OnSocketCanReceiveWithoutBlocking() override;
   void OnSocketCanSendWithoutBlocking() override;
 
   // Methods for |DataSocket|
   //
 
   nsresult QueryReceiveBuffer(UnixSocketIOBuffer** aBuffer);
   void ConsumeBuffer();
@@ -216,36 +215,40 @@ BluetoothSocket::BluetoothSocketIO::Canc
 }
 
 void
 BluetoothSocket::BluetoothSocketIO::Listen()
 {
   MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
   MOZ_ASSERT(mConnector);
 
-  // This will set things we don't particularly care about, but it will hand
-  // back the correct structure size which is what we do care about.
-  if (!mConnector->CreateAddr(true, mAddrSize, mAddr, nullptr)) {
-    NS_WARNING("Cannot create socket address!");
-    FireSocketError();
-    return;
-  }
-
   if (!IsOpen()) {
     int fd = mConnector->Create();
     if (fd < 0) {
       NS_WARNING("Cannot create socket fd!");
       FireSocketError();
       return;
     }
     if (!SetSocketFlags(fd)) {
       NS_WARNING("Cannot set socket flags!");
       FireSocketError();
       return;
     }
+    if (!mConnector->SetUpListenSocket(fd)) {
+      NS_WARNING("Could not set up listen socket!");
+      FireSocketError();
+      return;
+    }
+    // This will set things we don't particularly care about, but it will hand
+    // back the correct structure size which is what we do care about.
+    if (!mConnector->CreateAddr(true, mAddrSize, mAddr, nullptr)) {
+      NS_WARNING("Cannot create socket address!");
+      FireSocketError();
+      return;
+    }
     SetFd(fd);
 
     // calls OnListening on success, or OnError otherwise
     nsresult rv = UnixSocketWatcher::Listen(
       reinterpret_cast<struct sockaddr*>(&mAddr), mAddrSize);
     NS_WARN_IF(NS_FAILED(rv));
   }
 }
@@ -263,123 +266,111 @@ BluetoothSocket::BluetoothSocketIO::Conn
       FireSocketError();
       return;
     }
     if (!SetSocketFlags(fd)) {
       NS_WARNING("Cannot set socket flags!");
       FireSocketError();
       return;
     }
+    if (!mConnector->SetUp(fd)) {
+      NS_WARNING("Could not set up socket!");
+      FireSocketError();
+      return;
+    }
+    if (!mConnector->CreateAddr(false, mAddrSize, mAddr, mAddress.get())) {
+      NS_WARNING("Cannot create socket address!");
+      FireSocketError();
+      return;
+    }
     SetFd(fd);
   }
 
-  if (!mConnector->CreateAddr(false, mAddrSize, mAddr, mAddress.get())) {
-    NS_WARNING("Cannot create socket address!");
-    FireSocketError();
-    return;
-  }
-
   // calls OnConnected() on success, or OnError() otherwise
   nsresult rv = UnixSocketWatcher::Connect(
     reinterpret_cast<struct sockaddr*>(&mAddr), mAddrSize);
   NS_WARN_IF(NS_FAILED(rv));
 }
 
 void
 BluetoothSocket::BluetoothSocketIO::Send(UnixSocketIOBuffer* aBuffer)
 {
   EnqueueData(aBuffer);
   AddWatchers(WRITE_WATCHER, false);
 }
 
 void
-BluetoothSocket::BluetoothSocketIO::OnAccepted(
-  int aFd, const sockaddr_any* aAddr, socklen_t aAddrLen)
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_LISTENING);
-  MOZ_ASSERT(aAddr);
-  MOZ_ASSERT(aAddrLen > 0 && (size_t)aAddrLen <= sizeof(mAddr));
-
-  memcpy (&mAddr, aAddr, aAddrLen);
-  mAddrSize = aAddrLen;
-
-  if (!mConnector->SetUp(aFd)) {
-    NS_WARNING("Could not set up socket!");
-    return;
-  }
-
-  RemoveWatchers(READ_WATCHER|WRITE_WATCHER);
-  Close();
-  if (!SetSocketFlags(aFd)) {
-    return;
-  }
-  SetSocket(aFd, SOCKET_IS_CONNECTED);
-
-  NS_DispatchToMainThread(
-    new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_SUCCESS));
-
-  AddWatchers(READ_WATCHER, true);
-  if (HasPendingData()) {
-    AddWatchers(WRITE_WATCHER, false);
-  }
-}
-
-void
 BluetoothSocket::BluetoothSocketIO::OnConnected()
 {
   MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
   MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED);
 
-  if (!SetSocketFlags(GetFd())) {
-    NS_WARNING("Cannot set socket flags!");
-    FireSocketError();
-    return;
-  }
-
-  if (!mConnector->SetUp(GetFd())) {
-    NS_WARNING("Could not set up socket!");
-    FireSocketError();
-    return;
-  }
-
   NS_DispatchToMainThread(
     new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_SUCCESS));
 
   AddWatchers(READ_WATCHER, true);
   if (HasPendingData()) {
     AddWatchers(WRITE_WATCHER, false);
   }
 }
 
 void
 BluetoothSocket::BluetoothSocketIO::OnListening()
 {
   MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
   MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_LISTENING);
 
-  if (!mConnector->SetUpListenSocket(GetFd())) {
-    NS_WARNING("Could not set up listen socket!");
-    FireSocketError();
-    return;
-  }
-
   AddWatchers(READ_WATCHER, true);
 }
 
 void
 BluetoothSocket::BluetoothSocketIO::OnError(const char* aFunction, int aErrno)
 {
   MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
 
   UnixFdWatcher::OnError(aFunction, aErrno);
   FireSocketError();
 }
 
 void
+BluetoothSocket::BluetoothSocketIO::OnSocketCanAcceptWithoutBlocking()
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_LISTENING);
+
+  RemoveWatchers(READ_WATCHER|WRITE_WATCHER);
+
+  socklen_t mAddrSize = sizeof(mAddr);
+  int fd = TEMP_FAILURE_RETRY(accept(GetFd(),
+    reinterpret_cast<struct sockaddr*>(&mAddr), &mAddrSize));
+  if (fd < 0) {
+    OnError("accept", errno);
+    return;
+  }
+  if (!SetSocketFlags(fd)) {
+    return;
+  }
+  if (!mConnector->SetUp(fd)) {
+    NS_WARNING("Could not set up socket!");
+    return;
+  }
+
+  Close();
+  SetSocket(fd, SOCKET_IS_CONNECTED);
+
+  NS_DispatchToMainThread(
+    new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_SUCCESS));
+
+  AddWatchers(READ_WATCHER, true);
+  if (HasPendingData()) {
+    AddWatchers(WRITE_WATCHER, false);
+  }
+}
+
+void
 BluetoothSocket::BluetoothSocketIO::OnSocketCanReceiveWithoutBlocking()
 {
   MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
   MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED); // see bug 990984
 
   ssize_t res = ReceiveData(GetFd());
   if (res < 0) {
     /* I/O error */
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2913,17 +2913,26 @@ ContentChild::RecvInvokeDragSession(nsTA
              do_CreateInstance(NS_VARIANT_CONTRACTID);
           NS_ENSURE_TRUE(variant, false);
           if (item.data().type() == IPCDataTransferData::TnsString) {
             const nsString& data = item.data().get_nsString();
             variant->SetAsAString(data);
           } else if (item.data().type() == IPCDataTransferData::TPBlobChild) {
             BlobChild* blob = static_cast<BlobChild*>(item.data().get_PBlobChild());
             nsRefPtr<FileImpl> fileImpl = blob->GetBlobImpl();
-            variant->SetAsISupports(fileImpl);
+            nsString path;
+            ErrorResult result;
+            fileImpl->GetMozFullPathInternal(path, result);
+            if (result.Failed()) {
+              variant->SetAsISupports(fileImpl);
+            } else {
+              nsCOMPtr<nsIFile> file;
+              NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true, getter_AddRefs(file));
+              variant->SetAsISupports(file);
+            }
           } else {
             continue;
           }
           dataTransfer->SetDataWithPrincipal(NS_ConvertUTF8toUTF16(item.flavor()),
                                              variant, i,
                                              nsContentUtils::GetSystemPrincipal());
         }
       }
--- a/ipc/unixfd/UnixSocketWatcher.cpp
+++ b/ipc/unixfd/UnixSocketWatcher.cpp
@@ -24,24 +24,24 @@ void UnixSocketWatcher::Close()
 
 nsresult
 UnixSocketWatcher::Connect(const struct sockaddr* aAddr, socklen_t aAddrLen)
 {
   MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
   MOZ_ASSERT(IsOpen());
   MOZ_ASSERT(aAddr || !aAddrLen);
 
-  if (connect(GetFd(), aAddr, aAddrLen) < 0) {
+  if (TEMP_FAILURE_RETRY(connect(GetFd(), aAddr, aAddrLen) < 0)) {
     if (errno == EINPROGRESS) {
       mConnectionStatus = SOCKET_IS_CONNECTING;
       // Set up a write watch to receive the connect signal
       AddWatchers(WRITE_WATCHER, false);
-    } else {
-      OnError("connect", errno);
+      return NS_OK;
     }
+    OnError("connect", errno);
     return NS_ERROR_FAILURE;
   }
 
   mConnectionStatus = SOCKET_IS_CONNECTED;
   OnConnected();
 
   return NS_OK;
 }
@@ -96,25 +96,17 @@ void
 UnixSocketWatcher::OnFileCanReadWithoutBlocking(int aFd)
 {
   MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
   MOZ_ASSERT(aFd == GetFd());
 
   if (mConnectionStatus == SOCKET_IS_CONNECTED) {
     OnSocketCanReceiveWithoutBlocking();
   } else if (mConnectionStatus == SOCKET_IS_LISTENING) {
-    sockaddr_any addr;
-    socklen_t addrLen = sizeof(addr);
-    int fd = TEMP_FAILURE_RETRY(accept(GetFd(),
-      reinterpret_cast<struct sockaddr*>(&addr), &addrLen));
-    if (fd < 0) {
-      OnError("accept", errno);
-    } else {
-      OnAccepted(fd, &addr, addrLen);
-    }
+    OnSocketCanAcceptWithoutBlocking();
   } else {
     NS_NOTREACHED("invalid connection state for reading");
   }
 }
 
 void
 UnixSocketWatcher::OnFileCanWriteWithoutBlocking(int aFd)
 {
--- a/ipc/unixfd/UnixSocketWatcher.h
+++ b/ipc/unixfd/UnixSocketWatcher.h
@@ -55,26 +55,25 @@ public:
   }
 
   // Connect to a peer
   nsresult Connect(const struct sockaddr* aAddr, socklen_t aAddrLen);
 
   // Listen on socket for incoming connection requests
   nsresult Listen(const struct sockaddr* aAddr, socklen_t aAddrLen);
 
-  // Callback method for accepted connections
-  virtual void OnAccepted(int aFd, const sockaddr_any* aAddr,
-                          socklen_t aAddrLen) {};
-
   // Callback method for successful connection requests
   virtual void OnConnected() {};
 
   // Callback method for successful listen requests
   virtual void OnListening() {};
 
+  // Callback method for accepting from a listening socket
+  virtual void OnSocketCanAcceptWithoutBlocking() {};
+
   // Callback method for receiving from socket
   virtual void OnSocketCanReceiveWithoutBlocking() {};
 
   // Callback method for sending on socket
   virtual void OnSocketCanSendWithoutBlocking() {};
 
 protected:
   UnixSocketWatcher(MessageLoop* aIOLoop);
--- a/ipc/unixsocket/ListenSocket.cpp
+++ b/ipc/unixsocket/ListenSocket.cpp
@@ -40,21 +40,20 @@ public:
   /**
    * Run bind/listen to prepare for further runs of accept()
    */
   void Listen(ConnectionOrientedSocketIO* aCOSocketIO);
 
   // I/O callback methods
   //
 
-  void OnAccepted(int aFd, const sockaddr_any* aAddr,
-                  socklen_t aAddrLen) override;
   void OnConnected() override;
   void OnError(const char* aFunction, int aErrno) override;
   void OnListening() override;
+  void OnSocketCanAcceptWithoutBlocking() override;
 
   // Methods for |SocketIOBase|
   //
 
   SocketBase* GetSocketBase() override;
 
   bool IsShutdownOnMainThread() const override;
   bool IsShutdownOnIOThread() const override;
@@ -150,70 +149,54 @@ ListenSocketIO::Listen(ConnectionOriente
       FireSocketError();
       return;
     }
     if (!SetSocketFlags(fd)) {
       NS_WARNING("Cannot set socket flags!");
       FireSocketError();
       return;
     }
+    if (!mConnector->SetUpListenSocket(GetFd())) {
+      NS_WARNING("Could not set up listen socket!");
+      FireSocketError();
+      return;
+    }
+    // This will set things we don't particularly care about, but
+    // it will hand back the correct structure size which is what
+    // we do care about.
+    if (!mConnector->CreateAddr(true, mAddrSize, mAddr, nullptr)) {
+      NS_WARNING("Cannot create socket address!");
+      FireSocketError();
+      return;
+    }
     SetFd(fd);
   }
 
   mCOSocketIO = aCOSocketIO;
 
-  // This will set things we don't particularly care about, but
-  // it will hand back the correct structure size which is what
-  // we do care about.
-  if (!mConnector->CreateAddr(true, mAddrSize, mAddr, nullptr)) {
-    NS_WARNING("Cannot create socket address!");
-    FireSocketError();
-    return;
-  }
-
   // calls OnListening on success, or OnError otherwise
   nsresult rv = UnixSocketWatcher::Listen(
     reinterpret_cast<struct sockaddr*>(&mAddr), mAddrSize);
   NS_WARN_IF(NS_FAILED(rv));
 }
 
 void
-ListenSocketIO::OnAccepted(int aFd,
-                           const sockaddr_any* aAddr,
-                           socklen_t aAddrLen)
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_LISTENING);
-  MOZ_ASSERT(mCOSocketIO);
-
-  RemoveWatchers(READ_WATCHER|WRITE_WATCHER);
-
-  mCOSocketIO->Accept(aFd, aAddr, aAddrLen);
-}
-
-void
 ListenSocketIO::OnConnected()
 {
   MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
 
   NS_NOTREACHED("Invalid call to |ListenSocketIO::OnConnected|");
 }
 
 void
 ListenSocketIO::OnListening()
 {
   MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
   MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_LISTENING);
 
-  if (!mConnector->SetUpListenSocket(GetFd())) {
-    NS_WARNING("Could not set up listen socket!");
-    FireSocketError();
-    return;
-  }
-
   AddWatchers(READ_WATCHER, true);
 
   /* We signal a successful 'connection' to a local address for listening. */
   NS_DispatchToMainThread(
     new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_SUCCESS));
 }
 
 void
@@ -268,16 +251,39 @@ ListenSocketIO::SetSocketFlags(int aFd)
   flags |= O_NONBLOCK;
   if (-1 == TEMP_FAILURE_RETRY(fcntl(aFd, F_SETFL, flags))) {
     return false;
   }
 
   return true;
 }
 
+void
+ListenSocketIO::OnSocketCanAcceptWithoutBlocking()
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_LISTENING);
+  MOZ_ASSERT(mCOSocketIO);
+
+  struct sockaddr_storage addr;
+  socklen_t addrLen = sizeof(addr);
+  int fd = TEMP_FAILURE_RETRY(accept(GetFd(),
+    reinterpret_cast<struct sockaddr*>(&addr), &addrLen));
+  if (fd < 0) {
+    OnError("accept", errno);
+    return;
+  }
+
+  RemoveWatchers(READ_WATCHER|WRITE_WATCHER);
+
+  mCOSocketIO->Accept(fd,
+                      reinterpret_cast<union sockaddr_any*>(&addr),
+                      addrLen);
+}
+
 // |SocketIOBase|
 
 SocketBase*
 ListenSocketIO::GetSocketBase()
 {
   return mListenSocket.get();
 }
 
--- a/ipc/unixsocket/StreamSocket.cpp
+++ b/ipc/unixsocket/StreamSocket.cpp
@@ -59,18 +59,16 @@ public:
    */
   void Connect();
 
   void Send(UnixSocketIOBuffer* aBuffer);
 
   // I/O callback methods
   //
 
-  void OnAccepted(int aFd, const sockaddr_any* aAddr,
-                  socklen_t aAddrLen) override;
   void OnConnected() override;
   void OnError(const char* aFunction, int aErrno) override;
   void OnListening() override;
   void OnSocketCanReceiveWithoutBlocking() override;
   void OnSocketCanSendWithoutBlocking() override;
 
   // Methods for |ConnectionOrientedSocketIO|
   //
@@ -248,90 +246,48 @@ StreamSocketIO::Connect()
       FireSocketError();
       return;
     }
     if (!SetSocketFlags(fd)) {
       NS_WARNING("Cannot set socket flags!");
       FireSocketError();
       return;
     }
+    if (!mConnector->SetUp(GetFd())) {
+      NS_WARNING("Could not set up socket!");
+      FireSocketError();
+      return;
+    }
+    if (!mConnector->CreateAddr(false, mAddrSize, mAddr, mAddress.get())) {
+      NS_WARNING("Cannot create socket address!");
+      FireSocketError();
+      return;
+    }
     SetFd(fd);
   }
 
-  if (!mConnector->CreateAddr(false, mAddrSize, mAddr, mAddress.get())) {
-    NS_WARNING("Cannot create socket address!");
-    FireSocketError();
-    return;
-  }
-
   // calls OnConnected() on success, or OnError() otherwise
   nsresult rv = UnixSocketWatcher::Connect(
     reinterpret_cast<struct sockaddr*>(&mAddr), mAddrSize);
   NS_WARN_IF(NS_FAILED(rv));
 }
 
 void
 StreamSocketIO::Send(UnixSocketIOBuffer* aData)
 {
   EnqueueData(aData);
   AddWatchers(WRITE_WATCHER, false);
 }
 
 void
-StreamSocketIO::OnAccepted(int aFd,
-                           const sockaddr_any* aAddr,
-                           socklen_t aAddrLen)
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_LISTENING);
-  MOZ_ASSERT(aAddr);
-  MOZ_ASSERT(aAddrLen <= static_cast<socklen_t>(sizeof(mAddr)));
-
-  memcpy (&mAddr, aAddr, aAddrLen);
-  mAddrSize = aAddrLen;
-
-  if (!mConnector->SetUp(aFd)) {
-    NS_WARNING("Could not set up socket!");
-    return;
-  }
-
-  RemoveWatchers(READ_WATCHER|WRITE_WATCHER);
-  Close();
-  if (!SetSocketFlags(aFd)) {
-    return;
-  }
-  SetSocket(aFd, SOCKET_IS_CONNECTED);
-
-  NS_DispatchToMainThread(
-    new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_SUCCESS));
-
-  AddWatchers(READ_WATCHER, true);
-  if (HasPendingData()) {
-    AddWatchers(WRITE_WATCHER, false);
-  }
-}
-
-void
 StreamSocketIO::OnConnected()
 {
   MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
   MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED);
 
-  if (!SetSocketFlags(GetFd())) {
-    NS_WARNING("Cannot set socket flags!");
-    FireSocketError();
-    return;
-  }
-
-  if (!mConnector->SetUp(GetFd())) {
-    NS_WARNING("Could not set up socket!");
-    FireSocketError();
-    return;
-  }
-
   NS_DispatchToMainThread(
     new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_SUCCESS));
 
   AddWatchers(READ_WATCHER, true);
   if (HasPendingData()) {
     AddWatchers(WRITE_WATCHER, false);
   }
 }
@@ -439,40 +395,39 @@ nsresult
 StreamSocketIO::Accept(int aFd,
                        const union sockaddr_any* aAddr, socklen_t aAddrLen)
 {
   MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
   MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTING);
 
   // File-descriptor setup
 
+  if (!SetSocketFlags(aFd)) {
+    return NS_ERROR_FAILURE;
+  }
   if (!mConnector->SetUp(aFd)) {
     NS_WARNING("Could not set up socket!");
     return NS_ERROR_FAILURE;
   }
 
-  if (!SetSocketFlags(aFd)) {
-    return NS_ERROR_FAILURE;
-  }
   SetSocket(aFd, SOCKET_IS_CONNECTED);
 
-  AddWatchers(READ_WATCHER, true);
-  if (HasPendingData()) {
-    AddWatchers(WRITE_WATCHER, false);
-  }
-
   // Address setup
-
   memcpy(&mAddr, aAddr, aAddrLen);
   mAddrSize = aAddrLen;
 
   // Signal success
   NS_DispatchToMainThread(
     new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_SUCCESS));
 
+  AddWatchers(READ_WATCHER, true);
+  if (HasPendingData()) {
+    AddWatchers(WRITE_WATCHER, false);
+  }
+
   return NS_OK;
 }
 
 // |DataSocketIO|
 
 nsresult
 StreamSocketIO::QueryReceiveBuffer(UnixSocketIOBuffer** aBuffer)
 {
rename from mobile/android/base/animation/BounceAnimator.java
rename to mobile/android/base/animation/BounceAnimatorBuilder.java
--- a/mobile/android/base/animation/BounceAnimator.java
+++ b/mobile/android/base/animation/BounceAnimatorBuilder.java
@@ -13,43 +13,42 @@ import com.nineoldandroids.animation.Val
 
 /**
  * This is an Animator that chains AccelerateInterpolators. It can be used to create a customized
  * Bounce animation.
  *
  * After constructing an instance, animations can be queued up sequentially with the
  * {@link #queue(Attributes) queue} method.
  */
-public class BounceAnimator extends ValueAnimator {
+public class BounceAnimatorBuilder extends ValueAnimator {
 
     public static final class Attributes {
         public final float value;
         public final int durationMs;
 
         public Attributes(float value, int duration) {
             this.value = value;
             this.durationMs = duration;
         }
     }
 
     private final View mView;
     private final String mPropertyName;
     private final List<Animator> animatorChain = new LinkedList<Animator>();
 
-    public BounceAnimator(View view, String property) {
+    public BounceAnimatorBuilder(View view, String property) {
         mView = view;
         mPropertyName = property;
     }
 
     public void queue(Attributes attrs) {
         final ValueAnimator animator = ObjectAnimator.ofFloat(mView, mPropertyName, attrs.value);
         animator.setDuration(attrs.durationMs);
         animator.setInterpolator(new AccelerateInterpolator());
         animatorChain.add(animator);
     }
 
-    @Override
-    public void start() {
+    public AnimatorSet build(){
         AnimatorSet animatorSet = new AnimatorSet();
         animatorSet.playSequentially(animatorChain);
-        animatorSet.start();
+        return animatorSet;
     }
 }
--- a/mobile/android/base/home/HomePagerTabStrip.java
+++ b/mobile/android/base/home/HomePagerTabStrip.java
@@ -1,18 +1,18 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.animation.BounceAnimator;
-import org.mozilla.gecko.animation.BounceAnimator.Attributes;
+import org.mozilla.gecko.animation.BounceAnimatorBuilder;
+import org.mozilla.gecko.animation.BounceAnimatorBuilder.Attributes;
 import org.mozilla.gecko.animation.TransitionsTracker;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.support.v4.view.PagerTabStrip;
@@ -28,22 +28,21 @@ import com.nineoldandroids.view.ViewHelp
 /**
  * HomePagerTabStrip is a custom implementation of PagerTabStrip
  * that exposes XML attributes for the public methods.
  */
 
 class HomePagerTabStrip extends PagerTabStrip {
 
     private static final String LOGTAG = "PagerTabStrip";
-    private static final int ANIMATION_DELAY_MS = 250;
+    private static final int ANIMATION_DELAY_MS = 50;
     private static final int ALPHA_MS = 10;
     private static final int BOUNCE1_MS = 350;
     private static final int BOUNCE2_MS = 200;
     private static final int BOUNCE3_MS = 100;
-    private static final int BOUNCE4_MS = 100;
     private static final int INIT_OFFSET = 100;
 
     private final Paint shadowPaint;
     private final int shadowSize;
 
     public HomePagerTabStrip(Context context) {
         this(context, null);
     }
@@ -98,40 +97,43 @@ class HomePagerTabStrip extends PagerTab
         ViewHelper.setAlpha(nextTextView, 0);
 
         // Alpha animations.
         final ValueAnimator alpha1 = ObjectAnimator.ofFloat(prevTextView, "alpha", 1);
         final ValueAnimator alpha2 = ObjectAnimator.ofFloat(nextTextView, "alpha", 1);
 
         final AnimatorSet alphaAnimatorSet = new AnimatorSet();
         alphaAnimatorSet.playTogether(alpha1, alpha2);
+        alphaAnimatorSet.setDuration(ALPHA_MS);
         alphaAnimatorSet.setStartDelay(ANIMATION_DELAY_MS);
-        alphaAnimatorSet.setDuration(ALPHA_MS);
 
         // Bounce animation.
         final float bounceDistance = getWidth()/100f; // Hack: TextFields still have 0 width here.
 
-        final BounceAnimator prevBounceAnimator = new BounceAnimator(prevTextView, "translationX");
-        prevBounceAnimator.queue(new Attributes(bounceDistance, BOUNCE1_MS));
-        prevBounceAnimator.queue(new Attributes(-bounceDistance/4, BOUNCE2_MS));
-        prevBounceAnimator.queue(new Attributes(0, BOUNCE4_MS));
-        prevBounceAnimator.setStartDelay(ANIMATION_DELAY_MS);
+        final BounceAnimatorBuilder prevBounceAnimatorBuilder = new BounceAnimatorBuilder(prevTextView, "translationX");
+        prevBounceAnimatorBuilder.queue(new Attributes(bounceDistance, BOUNCE1_MS));
+        prevBounceAnimatorBuilder.queue(new Attributes(-bounceDistance/4, BOUNCE2_MS));
+        prevBounceAnimatorBuilder.queue(new Attributes(0, BOUNCE3_MS));
 
-        final BounceAnimator nextBounceAnimator = new BounceAnimator(nextTextView, "translationX");
-        nextBounceAnimator.queue(new Attributes(-bounceDistance, BOUNCE1_MS));
-        nextBounceAnimator.queue(new Attributes(bounceDistance/4, BOUNCE2_MS));
-        nextBounceAnimator.queue(new Attributes(0, BOUNCE4_MS));
-        nextBounceAnimator.setStartDelay(ANIMATION_DELAY_MS);
+        final BounceAnimatorBuilder nextBounceAnimatorBuilder = new BounceAnimatorBuilder(nextTextView, "translationX");
+        nextBounceAnimatorBuilder.queue(new Attributes(-bounceDistance, BOUNCE1_MS));
+        nextBounceAnimatorBuilder.queue(new Attributes(bounceDistance/4, BOUNCE2_MS));
+        nextBounceAnimatorBuilder.queue(new Attributes(0, BOUNCE3_MS));
 
-        TransitionsTracker.track(nextBounceAnimator);
+        final AnimatorSet bounceAnimatorSet = new AnimatorSet();
+        bounceAnimatorSet.playTogether(prevBounceAnimatorBuilder.build(), nextBounceAnimatorBuilder.build());
+
+        TransitionsTracker.track(nextBounceAnimatorBuilder);
+
+        final AnimatorSet titlesAnimatorSet = new AnimatorSet();
+        titlesAnimatorSet.playTogether(alphaAnimatorSet, bounceAnimatorSet);
+        titlesAnimatorSet.setStartDelay(ANIMATION_DELAY_MS);
 
         // Start animations.
-        alphaAnimatorSet.start();
-        prevBounceAnimator.start();
-        nextBounceAnimator.start();
+        titlesAnimatorSet.start();
     }
 
     private class PreDrawListener implements ViewTreeObserver.OnPreDrawListener {
         @Override
         public boolean onPreDraw() {
             if (!TransitionsTracker.areTransitionsRunning()) {
                 // Don't show the title bounce animation if other animations are running.
                 animateTitles();
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -139,17 +139,17 @@ gbjar.sources += [
     'AboutPages.java',
     'ActionModeCompat.java',
     'ActionModeCompatView.java',
     'ActivityHandlerHelper.java',
     'AlertNotification.java',
     'AndroidGamepadManager.java',
     'animation/AnimationUtils.java',
     'animation/AnimatorProxy.java',
-    'animation/BounceAnimator.java',
+    'animation/BounceAnimatorBuilder.java',
     'animation/HeightChangeAnimation.java',
     'animation/PropertyAnimator.java',
     'animation/Rotate3DAnimation.java',
     'animation/TransitionsTracker.java',
     'animation/ViewHelper.java',
     'ANRReporter.java',
     'AppNotificationClient.java',
     'Assert.java',
--- a/mobile/android/base/prompts/PromptInput.java
+++ b/mobile/android/base/prompts/PromptInput.java
@@ -211,17 +211,17 @@ public class PromptInput {
                     try {
                         calendar.setTime(new SimpleDateFormat("HH:mm").parse(mValue));
                     } catch (Exception e) { }
                 }
                 input.setCurrentHour(calendar.get(GregorianCalendar.HOUR_OF_DAY));
                 input.setCurrentMinute(calendar.get(GregorianCalendar.MINUTE));
                 mView = (View)input;
             } else if (mType.equals("datetime-local") || mType.equals("datetime")) {
-                DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd HH:mm", mValue,
+                DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd HH:mm", mValue.replace("T"," ").replace("Z", ""),
                                                           DateTimePicker.PickersState.DATETIME);
                 input.toggleCalendar(true);
                 mView = (View)input;
             } else if (mType.equals("month")) {
                 DateTimePicker input = new DateTimePicker(context, "yyyy-MM", mValue,
                                                           DateTimePicker.PickersState.MONTH);
                 mView = (View)input;
             }
@@ -250,21 +250,21 @@ public class PromptInput {
                 DateTimePicker dp = (DateTimePicker)mView;
                 GregorianCalendar calendar = new GregorianCalendar();
                 calendar.setTimeInMillis(dp.getTimeInMillis());
                 if (mType.equals("date")) {
                     return formatDateString("yyyy-MM-dd",calendar);
                 } else if (mType.equals("week")) {
                     return formatDateString("yyyy-'W'ww",calendar);
                 } else if (mType.equals("datetime-local")) {
-                    return formatDateString("yyyy-MM-dd HH:mm",calendar);
+                    return formatDateString("yyyy-MM-dd'T'HH:mm",calendar);
                 } else if (mType.equals("datetime")) {
                     calendar.set(GregorianCalendar.ZONE_OFFSET,0);
                     calendar.setTimeInMillis(dp.getTimeInMillis());
-                    return formatDateString("yyyy-MM-dd HH:mm",calendar);
+                    return formatDateString("yyyy-MM-dd'T'HH:mm'Z'",calendar);
                 } else if (mType.equals("month")) {
                     return formatDateString("yyyy-MM",calendar);
                 }
             }
             return super.getValue();
         }
     }
 
--- a/mobile/android/components/Snippets.js
+++ b/mobile/android/components/Snippets.js
@@ -168,17 +168,17 @@ var gMessageIds = [];
  * Updates set of snippets in the home banner message rotation.
  *
  * @param messages JSON array of message data JSON objects.
  *   Each message object should have the following properties:
  *     - id (?): Unique identifier for this snippets message
  *     - text (string): Text to show as banner message
  *     - url (string): URL to open when banner is clicked
  *     - icon (data URI): Icon to appear in banner
- *     - target_geo (string): Country code for where this message should be shown (e.g. "US")
+ *     - countries (list of strings): Country codes for where this message should be shown (e.g. ["US", "GR"])
  */
 function updateBanner(messages) {
   // Remove the current messages, if there are any.
   gMessageIds.forEach(function(id) {
     Home.banner.remove(id);
   })
   gMessageIds = [];
 
@@ -189,19 +189,20 @@ function updateBanner(messages) {
       return removedSnippetIds.indexOf(message.id) === -1;
     });
   } catch (e) {
     // If the pref doesn't exist, there aren't any snippets to filter out.
   }
 
   messages.forEach(function(message) {
     // Don't add this message to the banner if it's not supposed to be shown in this country.
-    if ("target_geo" in message && message.target_geo != gCountryCode) {
+    if ("countries" in message && message.countries.indexOf(gCountryCode) === -1) {
       return;
     }
+
     let id = Home.banner.add({
       text: message.text,
       icon: message.icon,
       weight: message.weight,
       onclick: function() {
         let parentId = gChromeWin.BrowserApp.selectedTab.id;
         gChromeWin.BrowserApp.addTab(message.url, { parentId: parentId });
         UITelemetry.addEvent("action.1", "banner", null, message.id);
--- a/modules/libmar/sign/mar_sign.c
+++ b/modules/libmar/sign/mar_sign.c
@@ -528,16 +528,19 @@ extract_signature(const char *src, uint3
   signatureCount = ntohl(signatureCount);
   if (sigIndex >= signatureCount) {
     fprintf(stderr, "ERROR: Signature index was out of range\n");
     goto failure;
   }
 
   /* Skip to the correct signature */
   for (i = 0; i <= sigIndex; i++) {
+    /* Avoid leaking while skipping signatures */
+    free(extractedSignature);
+
     /* skip past the signature algorithm ID */
     if (fseeko(fpSrc, sizeof(uint32_t), SEEK_CUR)) {
       fprintf(stderr, "ERROR: Could not seek past sig algorithm ID.\n");
       goto failure;
     }
 
     /* Get the signature length */
     if (fread(&signatureLen, sizeof(signatureLen), 1, fpSrc) != 1) {
--- a/modules/libmar/verify/mar_verify.c
+++ b/modules/libmar/verify/mar_verify.c
@@ -46,19 +46,20 @@ mar_read_entire_file(const char * filePa
         if (fread(fileData, *size, 1, f) == 1) {
           *data = fileData;
           result = 0;
         } else {
           free(fileData);
         }
       }
     }
-    fclose(f);
   }
 
+  fclose(f);
+
   return result;
 }
 
 int mar_extract_and_verify_signatures_fp(FILE *fp,
                                          CryptoX_ProviderHandle provider,
                                          CryptoX_PublicKey *keys,
                                          uint32_t keyCount);
 int mar_verify_signatures_for_fp(FILE *fp,
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -15,16 +15,17 @@
 #include "nsComponentManagerUtils.h"
 #include "nsCOMPtr.h"
 #include "nsDataSignatureVerifier.h"
 #include "nsHashKeys.h"
 #include "nsIFile.h"
 #include "nsIFileStreams.h"
 #include "nsIInputStream.h"
 #include "nsIStringEnumerator.h"
+#include "nsIDirectoryEnumerator.h"
 #include "nsIZipReader.h"
 #include "nsNetUtil.h"
 #include "nsNSSCertificate.h"
 #include "nsProxyRelease.h"
 #include "nssb64.h"
 #include "NSSCertDBTrustDomain.h"
 #include "nsString.h"
 #include "nsTHashtable.h"
@@ -93,17 +94,17 @@ ReadStream(const nsCOMPtr<nsIInputStream
     return NS_ERROR_FILE_CORRUPTED;
   }
 
   buf.data[buf.len - 1] = 0; // null-terminate
 
   return NS_OK;
 }
 
-// Finds exactly one (signature metadata) entry that matches the given
+// Finds exactly one (signature metadata) JAR entry that matches the given
 // search pattern, and then load it. Fails if there are no matches or if
 // there is more than one match. If bugDigest is not null then on success
 // bufDigest will contain the SHA-1 digeset of the entry.
 nsresult
 FindAndLoadOneEntry(nsIZipReader * zip,
                     const nsACString & searchPattern,
                     /*out*/ nsACString & filename,
                     /*out*/ SECItem & buf,
@@ -148,37 +149,32 @@ FindAndLoadOneEntry(nsIZipReader * zip,
 
   return NS_OK;
 }
 
 // Verify the digest of an entry. We avoid loading the entire entry into memory
 // at once, which would require memory in proportion to the size of the largest
 // entry. Instead, we require only a small, fixed amount of memory.
 //
+// @param stream  an input stream from a JAR entry or file depending on whether
+//                it is from a signed archive or unpacked into a directory
 // @param digestFromManifest The digest that we're supposed to check the file's
 //                           contents against, from the manifest
 // @param buf A scratch buffer that we use for doing the I/O, which must have
 //            already been allocated. The size of this buffer is the unit
 //            size of our I/O.
 nsresult
-VerifyEntryContentDigest(nsIZipReader * zip, const nsACString & aFilename,
-                         const SECItem & digestFromManifest, SECItem & buf)
+VerifyStreamContentDigest(nsIInputStream* stream,
+                          const SECItem& digestFromManifest, SECItem& buf)
 {
   MOZ_ASSERT(buf.len > 0);
   if (digestFromManifest.len != SHA1_LENGTH)
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
 
   nsresult rv;
-
-  nsCOMPtr<nsIInputStream> stream;
-  rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
-  if (NS_FAILED(rv)) {
-    return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
-  }
-
   uint64_t len64;
   rv = stream->Available(&len64);
   NS_ENSURE_SUCCESS(rv, rv);
   if (len64 > UINT32_MAX) {
     return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
   }
 
   ScopedPK11Context digestContext(PK11_CreateDigestContext(SEC_OID_SHA1));
@@ -221,16 +217,91 @@ VerifyEntryContentDigest(nsIZipReader * 
 
   if (SECITEM_CompareItem(&digestFromManifest, &digest.get()) != SECEqual) {
     return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY;
   }
 
   return NS_OK;
 }
 
+nsresult
+VerifyEntryContentDigest(nsIZipReader* zip, const nsACString& aFilename,
+                         const SECItem& digestFromManifest, SECItem& buf)
+{
+  nsCOMPtr<nsIInputStream> stream;
+  nsresult rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
+  }
+
+  return VerifyStreamContentDigest(stream, digestFromManifest, buf);
+}
+
+// @oaram aDir       directory containing the unpacked signed archive
+// @param aFilename  path of the target file relative to aDir
+// @param digestFromManifest The digest that we're supposed to check the file's
+//                           contents against, from the manifest
+// @param buf A scratch buffer that we use for doing the I/O
+nsresult
+VerifyFileContentDigest(nsIFile* aDir, const nsAString& aFilename,
+                        const SECItem& digestFromManifest, SECItem& buf)
+{
+  // Find the file corresponding to the manifest path
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = aDir->Clone(getter_AddRefs(file));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // We don't know how to handle JARs with signed directory entries.
+  // It's technically possible in the manifest but makes no sense on disk.
+  // Inside an archive we just ignore them, but here we have to treat it
+  // as an error because the signed bytes never got unpacked.
+  int32_t pos = 0;
+  int32_t slash;
+  int32_t namelen = aFilename.Length();
+  if (namelen == 0 || aFilename[namelen - 1] == '/') {
+    return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
+  }
+
+  // Append path segments one by one
+  do {
+    slash = aFilename.FindChar('/', pos);
+    int32_t segend = (slash == kNotFound) ? namelen : slash;
+    rv = file->Append(Substring(aFilename, pos, (segend - pos)));
+    if (NS_FAILED(rv)) {
+      return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
+    }
+    pos = slash + 1;
+  }  while (pos < namelen && slash != kNotFound);
+
+  bool exists;
+  rv = file->Exists(&exists);
+  if (NS_FAILED(rv) || !exists) {
+    return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
+  }
+
+  bool isDir;
+  rv = file->IsDirectory(&isDir);
+  if (NS_FAILED(rv) || isDir) {
+    // We only support signed files, not directory entries
+    return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
+  }
+
+  // Open an input stream for that file and verify it.
+  nsCOMPtr<nsIInputStream> stream;
+  rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1,
+                                  nsIFileInputStream::CLOSE_ON_EOF);
+  if (NS_FAILED(rv) || !stream) {
+    return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
+  }
+
+  return VerifyStreamContentDigest(stream, digestFromManifest, buf);
+}
+
 // On input, nextLineStart is the start of the current line. On output,
 // nextLineStart is the start of the next line.
 nsresult
 ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line,
          bool allowContinuations = true)
 {
   line.Truncate();
   size_t previousLength = 0;
@@ -281,16 +352,17 @@ ReadLine(/*in/out*/ const char* & nextLi
     ++nextLineStart; // skip space and keep appending
   }
 }
 
 // The header strings are defined in the JAR specification.
 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
 #define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$"
+#define JAR_META_DIR "META-INF"
 #define JAR_MF_HEADER "Manifest-Version: 1.0"
 #define JAR_SF_HEADER "Signature-Version: 1.0"
 
 nsresult
 ParseAttribute(const nsAutoCString & curLine,
                /*out*/ nsAutoCString & attrName,
                /*out*/ nsAutoCString & attrValue)
 {
@@ -944,8 +1016,502 @@ nsNSSCertificateDB::VerifySignedManifest
   NS_ENSURE_ARG_POINTER(aSignatureStream);
   NS_ENSURE_ARG_POINTER(aCallback);
 
   RefPtr<VerifySignedmanifestTask> task(
     new VerifySignedmanifestTask(aTrustedRoot, aManifestStream,
                                  aSignatureStream, aCallback));
   return task->Dispatch("SignedManifest");
 }
+
+
+//
+// Signature verification for archives unpacked into a file structure
+//
+
+// Finds the "*.rsa" signature file in the META-INF directory and returns
+// the name. It is an error if there are none or more than one .rsa file
+nsresult
+FindSignatureFilename(nsIFile* aMetaDir,
+                      /*out*/ nsAString& aFilename)
+{
+  nsCOMPtr<nsISimpleEnumerator> entries;
+  nsresult rv = aMetaDir->GetDirectoryEntries(getter_AddRefs(entries));
+  nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(entries);
+  if (NS_FAILED(rv) || !files) {
+    return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
+  }
+
+  bool found = false;
+  nsCOMPtr<nsIFile> file;
+  rv = files->GetNextFile(getter_AddRefs(file));
+
+  while (NS_SUCCEEDED(rv) && file) {
+    nsAutoString leafname;
+    rv = file->GetLeafName(leafname);
+    if (NS_SUCCEEDED(rv)) {
+      if (StringEndsWith(leafname, NS_LITERAL_STRING(".rsa"))) {
+        if (!found) {
+          found = true;
+          aFilename = leafname;
+        } else {
+          // second signature file is an error
+          rv = NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+          break;
+        }
+      }
+      rv = files->GetNextFile(getter_AddRefs(file));
+    }
+  }
+
+  if (!found) {
+    rv = NS_ERROR_SIGNED_JAR_NOT_SIGNED;
+  }
+
+  files->Close();
+  return rv;
+}
+
+// Loads the signature metadata file that matches the given filename in
+// the passed-in Meta-inf directory. If bufDigest is not null then on
+// success bufDigest will contain the SHA-1 digest of the entry.
+nsresult
+LoadOneMetafile(nsIFile* aMetaDir,
+                const nsAString& aFilename,
+                /*out*/ SECItem& aBuf,
+                /*optional, out*/ Digest* aBufDigest)
+{
+  nsCOMPtr<nsIFile> metafile;
+  nsresult rv = aMetaDir->Clone(getter_AddRefs(metafile));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = metafile->Append(aFilename);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool exists;
+  rv = metafile->Exists(&exists);
+  if (NS_FAILED(rv) || !exists) {
+    // we can call a missing .rsa file "unsigned" but FindSignatureFilename()
+    // already found one: missing other metadata files means a broken signature.
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+
+  nsCOMPtr<nsIInputStream> stream;
+  rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), metafile);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = ReadStream(stream, aBuf);
+  stream->Close();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (aBufDigest) {
+    rv = aBufDigest->DigestBuf(SEC_OID_SHA1, aBuf.data, aBuf.len - 1);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+// Parses MANIFEST.MF and verifies the contents of the unpacked files
+// listed in the manifest.
+// The filenames of all entries will be returned in aMfItems. aBuf must
+// be a pre-allocated scratch buffer that is used for doing I/O.
+nsresult
+ParseMFUnpacked(const char* aFilebuf, nsIFile* aDir,
+                /*out*/ nsTHashtable<nsStringHashKey>& aMfItems,
+                ScopedAutoSECItem& aBuf)
+{
+  nsresult rv;
+
+  const char* nextLineStart = aFilebuf;
+
+  rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Skip the rest of the header section, which ends with a blank line.
+  {
+    nsAutoCString line;
+    do {
+      rv = ReadLine(nextLineStart, line);
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
+    } while (line.Length() > 0);
+
+    // Manifest containing no file entries is OK, though useless.
+    if (*nextLineStart == '\0') {
+      return NS_OK;
+    }
+  }
+
+  nsAutoString curItemName;
+  ScopedAutoSECItem digest;
+
+  for (;;) {
+    nsAutoCString curLine;
+    rv = ReadLine(nextLineStart, curLine);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    if (curLine.Length() == 0) {
+      // end of section (blank line or end-of-file)
+
+      if (curItemName.Length() == 0) {
+        // '...Each section must start with an attribute with the name as
+        // "Name",...', so every section must have a Name attribute.
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+      }
+
+      if (digest.len == 0) {
+        // We require every entry to have a digest, since we require every
+        // entry to be signed and we don't allow duplicate entries.
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+      }
+
+      if (aMfItems.Contains(curItemName)) {
+        // Duplicate entry
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+      }
+
+      // Verify that the file's content digest matches the digest from this
+      // MF section.
+      rv = VerifyFileContentDigest(aDir, curItemName, digest, aBuf);
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
+
+      aMfItems.PutEntry(curItemName);
+
+      if (*nextLineStart == '\0') {
+        // end-of-file
+        break;
+      }
+
+      // reset so we know we haven't encountered either of these for the next
+      // item yet.
+      curItemName.Truncate();
+      digest.reset();
+
+      continue; // skip the rest of the loop below
+    }
+
+    nsAutoCString attrName;
+    nsAutoCString attrValue;
+    rv = ParseAttribute(curLine, attrName, attrValue);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    // Lines to look for:
+
+    // (1) Digest:
+    if (attrName.LowerCaseEqualsLiteral("sha1-digest")) {
+      if (digest.len > 0) {
+        // multiple SHA1 digests in section
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+      }
+
+      rv = MapSECStatus(ATOB_ConvertAsciiToItem(&digest, attrValue.get()));
+      if (NS_FAILED(rv)) {
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+      }
+
+      continue;
+    }
+
+    // (2) Name: associates this manifest section with a file in the jar.
+    if (attrName.LowerCaseEqualsLiteral("name")) {
+      if (MOZ_UNLIKELY(curItemName.Length() > 0)) {
+        // multiple names in section
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+      }
+
+      if (MOZ_UNLIKELY(attrValue.Length() == 0)) {
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+      }
+
+      curItemName = NS_ConvertUTF8toUTF16(attrValue);
+
+      continue;
+    }
+
+    // (3) Magic: the only other must-understand attribute
+    if (attrName.LowerCaseEqualsLiteral("magic")) {
+      // We don't understand any magic, so we can't verify an entry that
+      // requires magic. Since we require every entry to have a valid
+      // signature, we have no choice but to reject the entry.
+      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+    }
+
+    // unrecognized attributes must be ignored
+  }
+
+  return NS_OK;
+}
+
+// recursively check a directory tree for files not in the list of
+// verified files we found in the manifest. For each file we find
+// Check it against the files found in the manifest. If the file wasn't
+// in the manifest then it's unsigned and we can stop looking. Otherwise
+// remove it from the collection so we can check leftovers later.
+//
+// @param aDir   Directory to check
+// @param aPath  Relative path to that directory (to check against aItems)
+// @param aItems All the files found
+// @param *Filename  signature files that won't be in the manifest
+nsresult
+CheckDirForUnsignedFiles(nsIFile* aDir,
+                         const nsString& aPath,
+                         /* in/out */ nsTHashtable<nsStringHashKey>& aItems,
+                         const nsAString& sigFilename,
+                         const nsAString& sfFilename,
+                         const nsAString& mfFilename)
+{
+  nsCOMPtr<nsISimpleEnumerator> entries;
+  nsresult rv = aDir->GetDirectoryEntries(getter_AddRefs(entries));
+  nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(entries);
+  if (NS_FAILED(rv) || !files) {
+    return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
+  }
+
+  bool inMeta = StringBeginsWith(aPath, NS_LITERAL_STRING(JAR_META_DIR));
+
+  while (NS_SUCCEEDED(rv)) {
+    nsCOMPtr<nsIFile> file;
+    rv = files->GetNextFile(getter_AddRefs(file));
+    if (NS_FAILED(rv) || !file) {
+      break;
+    }
+
+    nsAutoString leafname;
+    rv = file->GetLeafName(leafname);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    nsAutoString curName(aPath + leafname);
+
+    bool isDir;
+    rv = file->IsDirectory(&isDir);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    // if it's a directory we need to recurse
+    if (isDir) {
+      curName.Append(NS_LITERAL_STRING("/"));
+      rv = CheckDirForUnsignedFiles(file, curName, aItems,
+                                    sigFilename, sfFilename, mfFilename);
+    } else {
+      // The files that comprise the signature mechanism are not covered by the
+      // signature.
+      //
+      // XXX: This is OK for a single signature, but doesn't work for
+      // multiple signatures because the metadata for the other signatures
+      // is not signed either.
+      if (inMeta && ( leafname == sigFilename ||
+                      leafname == sfFilename ||
+                      leafname == mfFilename )) {
+        continue;
+      }
+
+      // make sure the current file was found in the manifest
+      nsStringHashKey* item = aItems.GetEntry(curName);
+      if (!item) {
+        return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY;
+      }
+
+      // Remove the item so we can check for leftover items later
+      aItems.RemoveEntry(curName);
+    }
+  }
+  files->Close();
+  return rv;
+}
+
+/*
+ * Verify the signature of a directory structure as if it were a
+ * signed JAR file (used for unpacked JARs)
+ */
+nsresult
+VerifySignedDirectory(AppTrustedRoot aTrustedRoot,
+                      nsIFile* aDirectory,
+                      /*out, optional */ nsIX509Cert** aSignerCert)
+{
+  NS_ENSURE_ARG_POINTER(aDirectory);
+
+  if (aSignerCert) {
+    *aSignerCert = nullptr;
+  }
+
+  // Make sure there's a META-INF directory
+
+  nsCOMPtr<nsIFile> metaDir;
+  nsresult rv = aDirectory->Clone(getter_AddRefs(metaDir));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = metaDir->Append(NS_LITERAL_STRING(JAR_META_DIR));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  bool exists;
+  rv = metaDir->Exists(&exists);
+  if (NS_FAILED(rv) || !exists) {
+    return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
+  }
+  bool isDirectory;
+  rv = metaDir->IsDirectory(&isDirectory);
+  if (NS_FAILED(rv) || !isDirectory) {
+    return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
+  }
+
+  // Find and load the Signature (RSA) file
+
+  nsAutoString sigFilename;
+  rv = FindSignatureFilename(metaDir, sigFilename);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  ScopedAutoSECItem sigBuffer;
+  rv = LoadOneMetafile(metaDir, sigFilename, sigBuffer, nullptr);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
+  }
+
+  // Load the signature (SF) file and verify the signature.
+  // The .sf and .rsa files must have the same name apart from the extension.
+
+  nsAutoString sfFilename(Substring(sigFilename, 0, sigFilename.Length() - 3)
+                          + NS_LITERAL_STRING("sf"));
+
+  ScopedAutoSECItem sfBuffer;
+  Digest sfCalculatedDigest;
+  rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer, &sfCalculatedDigest);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+
+  sigBuffer.type = siBuffer;
+  ScopedCERTCertList builtChain;
+  rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
+                       builtChain);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+
+  // Get the expected manifest hash from the signed .sf file
+
+  ScopedAutoSECItem mfDigest;
+  rv = ParseSF(char_ptr_cast(sfBuffer.data), mfDigest);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+
+  // Load manifest (MF) file and verify signature
+
+  nsAutoString mfFilename(NS_LITERAL_STRING("manifest.mf"));
+  ScopedAutoSECItem manifestBuffer;
+  Digest mfCalculatedDigest;
+  rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, &mfCalculatedDigest);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+
+  if (SECITEM_CompareItem(&mfDigest, &mfCalculatedDigest.get()) != SECEqual) {
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+
+  // Parse manifest and verify signed hash of all listed files
+
+  // Allocate the I/O buffer only once per JAR, instead of once per entry, in
+  // order to minimize malloc/free calls and in order to avoid fragmenting
+  // memory.
+  ScopedAutoSECItem buf(128 * 1024);
+
+  nsTHashtable<nsStringHashKey> items;
+  rv = ParseMFUnpacked(char_ptr_cast(manifestBuffer.data),
+                       aDirectory, items, buf);
+  if (NS_FAILED(rv)){
+    return rv;
+  }
+
+  // We've checked that everything listed in the manifest exists and is signed
+  // correctly. Now check on disk for extra (unsigned) files.
+  // Deletes found entries from items as it goes.
+  rv = CheckDirForUnsignedFiles(aDirectory, EmptyString(), items,
+                                sigFilename, sfFilename, mfFilename);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // We verified that every entry that we require to be signed is signed. But,
+  // were there any missing entries--that is, entries that are mentioned in the
+  // manifest but missing from the directory tree? (There shouldn't be given
+  // ParseMFUnpacked() checking them all, but it's a cheap sanity check.)
+  if (items.Count() != 0) {
+    return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
+  }
+
+  // Return the signer's certificate to the reader if they want it.
+  // XXX: We should return an nsIX509CertList with the whole validated chain.
+  if (aSignerCert) {
+    MOZ_ASSERT(CERT_LIST_HEAD(builtChain));
+    nsCOMPtr<nsIX509Cert> signerCert =
+      nsNSSCertificate::Create(CERT_LIST_HEAD(builtChain)->cert);
+    NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY);
+    signerCert.forget(aSignerCert);
+  }
+
+  return NS_OK;
+}
+
+class VerifySignedDirectoryTask final : public CryptoTask
+{
+public:
+  VerifySignedDirectoryTask(AppTrustedRoot aTrustedRoot, nsIFile* aUnpackedJar,
+                            nsIVerifySignedDirectoryCallback* aCallback)
+    : mTrustedRoot(aTrustedRoot)
+    , mDirectory(aUnpackedJar)
+    , mCallback(new nsMainThreadPtrHolder<nsIVerifySignedDirectoryCallback>(aCallback))
+  {
+  }
+
+private:
+  virtual nsresult CalculateResult() override
+  {
+    return VerifySignedDirectory(mTrustedRoot,
+                                 mDirectory,
+                                 getter_AddRefs(mSignerCert));
+  }
+
+  // This class doesn't directly hold NSS resources so there's nothing that
+  // needs to be released
+  virtual void ReleaseNSSResources() override { }
+
+  virtual void CallCallback(nsresult rv) override
+  {
+    (void) mCallback->VerifySignedDirectoryFinished(rv, mSignerCert);
+  }
+
+  const AppTrustedRoot mTrustedRoot;
+  const nsCOMPtr<nsIFile> mDirectory;
+  nsMainThreadPtrHandle<nsIVerifySignedDirectoryCallback> mCallback;
+  nsCOMPtr<nsIX509Cert> mSignerCert; // out
+};
+
+NS_IMETHODIMP
+nsNSSCertificateDB::VerifySignedDirectoryAsync(
+  AppTrustedRoot aTrustedRoot, nsIFile* aUnpackedJar,
+  nsIVerifySignedDirectoryCallback* aCallback)
+{
+  NS_ENSURE_ARG_POINTER(aUnpackedJar);
+  NS_ENSURE_ARG_POINTER(aCallback);
+  RefPtr<VerifySignedDirectoryTask> task(new VerifySignedDirectoryTask(aTrustedRoot,
+                                                                       aUnpackedJar,
+                                                                       aCallback));
+  return task->Dispatch("UnpackedJar");
+}
--- a/security/manager/ssl/public/nsIX509CertDB.idl
+++ b/security/manager/ssl/public/nsIX509CertDB.idl
@@ -23,28 +23,35 @@ typedef uint32_t AppTrustedRoot;
 [scriptable, function, uuid(fc2b60e5-9a07-47c2-a2cd-b83b68a660ac)]
 interface nsIOpenSignedAppFileCallback : nsISupports
 {
   void openSignedAppFileFinished(in nsresult rv,
                                  in nsIZipReader aZipReader,
                                  in nsIX509Cert aSignerCert);
 };
 
+[scriptable, function, uuid(d5f97827-622a-488f-be08-d850432ac8ec)]
+interface nsIVerifySignedDirectoryCallback : nsISupports
+{
+  void verifySignedDirectoryFinished(in nsresult rv,
+                                     in nsIX509Cert aSignerCert);
+};
+
 [scriptable, function, uuid(3d6a9c87-5c5f-46fc-9410-96da6092f0f2)]
 interface nsIVerifySignedManifestCallback : nsISupports
 {
   void verifySignedManifestFinished(in nsresult rv,
                                     in nsIX509Cert aSignerCert);
 };
 
 /**
  * This represents a service to access and manipulate
  * X.509 certificates stored in a database.
  */
-[scriptable, uuid(560bc9ac-3e71-472e-9b08-2270d0c71878)]
+[scriptable, uuid(6d27211b-7119-4777-9c62-f29310eeb262)]
 interface nsIX509CertDB : nsISupports {
 
   /**
    *  Constants that define which usages a certificate
    *  is trusted for.
    */
   const unsigned long UNTRUSTED       =      0;
   const unsigned long TRUSTED_SSL     = 1 << 0;
@@ -313,16 +320,32 @@ interface nsIX509CertDB : nsISupports {
   const AppTrustedRoot TrustedHostedAppTestRoot = 8;
   const AppTrustedRoot AddonsPublicRoot = 9;
   const AppTrustedRoot AddonsStageRoot = 10;
   void openSignedAppFileAsync(in AppTrustedRoot trustedRoot,
                               in nsIFile aJarFile,
                               in nsIOpenSignedAppFileCallback callback);
 
   /**
+   *  Verifies the signature on a directory representing an unpacked signed
+   *  JAR file. To be considered valid, there must be exactly one signature
+   *  on the directory structure and that signature must have signed every
+   *  entry. Further, the signature must come from a certificate that
+   *  is trusted for code signing.
+   *
+   *  On success NS_OK and the trusted certificate that signed the
+   *  unpacked JAR are returned.
+   *
+   *  On failure, an error code is returned.
+   */
+  void verifySignedDirectoryAsync(in AppTrustedRoot trustedRoot,
+                                  in nsIFile aUnpackedDir,
+                                  in nsIVerifySignedDirectoryCallback callback);
+
+  /**
    * Given streams containing a signature and a manifest file, verifies
    * that the signature is valid for the manifest. The signature must
    * come from a certificate that is trusted for code signing and that
    * was issued by the given trusted root.
    *
    *  On success, NS_OK and the trusted certificate that signed the
    *  Manifest are returned.
    *
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c212c70eebb9e42088dd22e556964489667698f0
GIT binary patch
literal 6074
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
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_dir.js
@@ -0,0 +1,173 @@
+"use strict";
+
+Components.utils.import("resource://gre/modules/ZipUtils.jsm");
+
+do_get_profile(); // must be called before getting nsIX509CertDB
+const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
+
+var gSignedXPI = do_get_file("test_signed_apps/sslcontrol.xpi", false);
+var gTarget = FileUtils.getDir("TmpD", ["test_signed_dir"]);
+gTarget.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+
+// tamper data structure, each element is optional
+// { copy:    [[path,newname], [path2,newname2], ...],
+//   delete:  [path, path2, ...],
+//   corrupt: [path, path2, ...]
+// }
+
+function prepare(tamper) {
+  ZipUtils.extractFiles(gSignedXPI, gTarget);
+
+  // copy files
+  if (tamper.copy) {
+    tamper.copy.forEach(i => {
+      let f = gTarget.clone();
+      i[0].split('/').forEach(seg => {f.append(seg);});
+      f.copyTo(null, i[1]);
+    });
+  }
+
+  // delete files
+  if (tamper.delete) {
+    tamper.delete.forEach(i => {
+      let f = gTarget.clone();
+      i.split('/').forEach(seg => {f.append(seg);});
+      f.remove(true);
+    });
+  }
+
+  // corrupt files
+  if (tamper.corrupt) {
+    tamper.corrupt.forEach(i => {
+      let f = gTarget.clone();
+      i.split('/').forEach(seg => {f.append(seg);});
+      let s = FileUtils.openFileOutputStream(f, FileUtils.MODE_WRONLY);
+      const str = "Kilroy was here";
+      s.write(str, str.length);
+      s.close();
+    });
+  }
+
+  return gTarget;
+}
+
+
+function check_result(name, expectedRv, dir) {
+  return function verifySignedDirCallback(rv, aSignerCert) {
+    equal(rv, expectedRv, name + " rv:");
+    equal(aSignerCert != null, Components.isSuccessCode(expectedRv),
+          "expecting certificate:");
+    // cleanup and kick off next test
+    dir.remove(true);
+    run_next_test();
+  };
+}
+
+function verifyDirAsync(name, expectedRv, tamper) {
+  let targetDir = prepare(tamper);
+  certdb.verifySignedDirectoryAsync(
+    Ci.nsIX509CertDB.AddonsPublicRoot, targetDir,
+    check_result(name, expectedRv, targetDir));
+}
+
+
+//
+// the tests
+//
+
+add_test(function() {
+  verifyDirAsync("'valid'", Cr.NS_OK, {} /* no tampering */ );
+});
+
+add_test(function() {
+  verifyDirAsync("'no meta dir'", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED,
+                 {delete: ["META-INF"]});
+});
+
+add_test(function() {
+  verifyDirAsync("'empty meta dir'", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED,
+                 {delete: ["META-INF/mozilla.rsa",
+                           "META-INF/mozilla.sf",
+                           "META-INF/manifest.mf"]});
+});
+
+add_test(function() {
+  verifyDirAsync("'two rsa files'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
+                 {copy: [["META-INF/mozilla.rsa","extra.rsa"]]});
+});
+
+add_test(function() {
+  verifyDirAsync("'corrupt rsa file'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
+                 {corrupt: ["META-INF/mozilla.rsa"]});
+});
+
+add_test(function() {
+  verifyDirAsync("'missing sf file'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
+                 {delete: ["META-INF/mozilla.sf"]});
+});
+
+add_test(function() {
+  verifyDirAsync("'corrupt sf file'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
+                 {corrupt: ["META-INF/mozilla.sf"]});
+});
+
+add_test(function() {
+  verifyDirAsync("'extra .sf file (invalid)'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY,
+                 {copy: [["META-INF/mozilla.rsa","extra.sf"]]});
+});
+
+add_test(function() {
+  verifyDirAsync("'extra .sf file (valid)'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY,
+                 {copy: [["META-INF/mozilla.sf","extra.sf"]]});
+});
+
+add_test(function() {
+  verifyDirAsync("'missing manifest'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
+                 {delete: ["META-INF/manifest.mf"]});
+});
+
+add_test(function() {
+  verifyDirAsync("'corrupt manifest'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
+                 {corrupt: ["META-INF/manifest.mf"]});
+});
+
+add_test(function() {
+  verifyDirAsync("'missing file'", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING,
+                 {delete: ["bootstrap.js"]});
+});
+
+add_test(function() {
+  verifyDirAsync("'corrupt file'", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY,
+                 {corrupt: ["bootstrap.js"]});
+});
+
+add_test(function() {
+  verifyDirAsync("'extra file'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY,
+                 {copy: [["bootstrap.js","extra"]]});
+});
+
+add_test(function() {
+  verifyDirAsync("'missing file in dir'", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING,
+                 {delete: ["content/options.xul"]});
+});
+
+add_test(function() {
+  verifyDirAsync("'corrupt file in dir'", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY,
+                 {corrupt: ["content/options.xul"]});
+});
+
+add_test(function() {
+  verifyDirAsync("'extra file in dir'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY,
+                 {copy: [["content/options.xul","extra"]]});
+});
+
+do_register_cleanup(function() {
+  if (gTarget.exists()) {
+    gTarget.remove(true);
+  }
+});
+
+function run_test() {
+  run_next_test();
+}
\ No newline at end of file
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -65,16 +65,17 @@ run-sequentially = hardcoded ports
 [test_cert_overrides.js]
 run-sequentially = hardcoded ports
 [test_intermediate_basic_usage_constraints.js]
 [test_name_constraints.js]
 [test_cert_trust.js]
 [test_cert_version.js]
 [test_signed_apps.js]
 [test_signed_apps-marketplace.js]
+[test_signed_dir.js]
 
 [test_cert_eku-CA_EP.js]
 [test_cert_eku-CA_EP_NS_OS_SA_TS.js]
 [test_cert_eku-CA.js]
 [test_cert_eku-CA_NS.js]
 [test_cert_eku-CA_OS.js]
 [test_cert_eku-CA_SA.js]
 [test_cert_eku-CA_TS.js]
--- a/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js
+++ b/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js
@@ -14,17 +14,36 @@ function promiseNotification(topic) {
     let observe = () => {
       Services.obs.removeObserver(observe, topic);
       resolve();
     }
     Services.obs.addObserver(observe, topic, false);
   });
 }
 
-// Just enough mocks so we can avoid hawk etc.
+// Just enough mocks so we can avoid hawk and storage.
+let MockStorage = function() {
+  this.data = null;
+};
+MockStorage.prototype = Object.freeze({
+  set: function (contents) {
+    this.data = contents;
+    return Promise.resolve(null);
+  },
+  get: function () {
+    return Promise.resolve(this.data);
+  },
+  getOAuthTokens() {
+    return Promise.resolve(null);
+  },
+  setOAuthTokens(contents) {
+    return Promise.resolve();
+  },
+});
+
 function MockFxAccountsClient() {
   this._email = "nobody@example.com";
   this._verified = false;
 
   this.accountStatus = function(uid) {
     let deferred = Promise.defer();
     deferred.resolve(!!uid && (!this._deletedOnServer));
     return deferred.promise;
@@ -38,16 +57,17 @@ function MockFxAccountsClient() {
 MockFxAccountsClient.prototype = {
   __proto__: FxAccountsClient.prototype
 }
 
 function MockFxAccounts(mockGrantClient) {
   return new FxAccounts({
     fxAccountsClient: new MockFxAccountsClient(),
     getAssertion: () => Promise.resolve("assertion"),
+    signedInUserStorage: new MockStorage(),
     _destroyOAuthToken: function(tokenData) {
       // somewhat sad duplication of _destroyOAuthToken, but hard to avoid.
       return mockGrantClient.destroyToken(tokenData.token).then( () => {
         Services.obs.notifyObservers(null, "testhelper-fxa-revoke-complete", null);
       });
     },
   });
 }
--- a/testing/mochitest/browser-harness.xul
+++ b/testing/mochitest/browser-harness.xul
@@ -243,19 +243,50 @@
                     gConfig.testRoot == "webapprtChrome" ? "webapprt:webapp" :
                     null;
       if (!winType) {
         throw new Error("Unrecognized gConfig.testRoot: " + gConfig.testRoot);
       }
       var testWin = windowMediator.getMostRecentWindow(winType);
 
       setStatus("Running...");
-      testWin.focus();
-      var Tester = new testWin.Tester(links, gDumper, testsFinished);
-      Tester.start();
+
+      // It's possible that the test harness window is not yet focused when this
+      // function runs (in which case testWin is already focused, and focusing it
+      // will be a no-op, and then the test harness window will steal focus later,
+      // which will mess up tests). So wait for the test harness window to be
+      // focused before trying to focus testWin.
+      waitForFocus(() => {
+        // Focus the test window and start tests.
+        waitForFocus(() => {
+          var Tester = new testWin.Tester(links, gDumper, testsFinished);
+          Tester.start();
+        }, testWin);
+      }, window);
+    }
+
+    function executeSoon(callback) {
+      let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+      tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
+    }
+
+    function waitForFocus(callback, win) {
+      // If "win" is already focused, just call the callback.
+      let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+      if (fm.focusedWindow == win) {
+        executeSoon(callback);
+        return;
+      }
+
+      // Otherwise focus it, and wait for the focus event.
+      win.addEventListener("focus", function listener() {
+        win.removeEventListener("focus", listener, true);
+        executeSoon(callback);
+      }, true);
+      win.focus();
     }
 
     function sum(a, b) {
       return a + b;
     }
 
     function getHTMLLogFromTests(aTests) {
       if (!aTests.length)
--- a/testing/taskcluster/mach_commands.py
+++ b/testing/taskcluster/mach_commands.py
@@ -30,17 +30,17 @@ ROOT = os.path.dirname(os.path.realpath(
 GECKO = os.path.realpath(os.path.join(ROOT, '..', '..'))
 DOCKER_ROOT = os.path.join(ROOT, '..', 'docker')
 MOZHARNESS_CONFIG = os.path.join(GECKO, 'testing', 'mozharness', 'mozharness.json')
 
 # XXX: If/when we have the taskcluster queue use construct url instead
 ARTIFACT_URL = 'https://queue.taskcluster.net/v1/task/{}/artifacts/{}'
 REGISTRY = open(os.path.join(DOCKER_ROOT, 'REGISTRY')).read().strip()
 
-DEFINE_TASK = 'queue:define-task:aws-provisioner/{}'
+DEFINE_TASK = 'queue:define-task:aws-provisioner-v1/{}'
 
 TREEHERDER_ROUTE_PREFIX = 'tc-treeherder-stage'
 TREEHERDER_ROUTES = {
     'staging': 'tc-treeherder-stage',
     'production': 'tc-treeherder'
 }
 
 DEFAULT_TRY = 'try: -b do -p all -u all'
--- a/testing/taskcluster/scripts/phone-builder/build-lightsaber-nightly.sh
+++ b/testing/taskcluster/scripts/phone-builder/build-lightsaber-nightly.sh
@@ -21,17 +21,17 @@ mar_file=b2g-${TARGET%%-*}-gecko-update.
 if [ $VARIANT == "user" ]; then
   PLATFORM=$TARGET
 else
   PLATFORM=$TARGET-$VARIANT
 fi
 
 ./mozharness/scripts/b2g_lightsaber.py \
   --config b2g/taskcluster-lightsaber-nightly.py \
-  --config balrog/staging.py \
+  --config balrog/docker-worker.py \
   "$debug_flag" \
   --disable-mock \
   --variant=$VARIANT \
   --work-dir=$WORKSPACE/B2G \
   --gaia-languages-file locales/languages_all.json \
   --log-level=debug \
   --target=$TARGET \
   --b2g-config-dir=$TARGET \
--- a/testing/taskcluster/scripts/phone-builder/build-phone-nightly.sh
+++ b/testing/taskcluster/scripts/phone-builder/build-phone-nightly.sh
@@ -9,17 +9,17 @@ fi
 if [ ! -d $HOME/.ssh ]; then
     mkdir $HOME/.ssh
 fi
 
 aws s3 cp s3://b2g-nightly-credentials/balrog_credentials .
 
 ./mozharness/scripts/b2g_build.py \
   --config b2g/taskcluster-phone-nightly.py \
-  --config balrog/staging.py \
+  --config balrog/docker-worker.py \
   "$debug_flag" \
   --disable-mock \
   --variant=$VARIANT \
   --work-dir=$WORKSPACE/B2G \
   --gaia-languages-file locales/languages_all.json \
   --log-level=debug \
   --target=$TARGET \
   --b2g-config-dir=$TARGET \
--- a/testing/taskcluster/tasks/builds/b2g_aries_lightsaber_nightly_base.yml
+++ b/testing/taskcluster/tasks/builds/b2g_aries_lightsaber_nightly_base.yml
@@ -1,14 +1,18 @@
 $inherits:
   from: 'tasks/builds/b2g_phone_base.yml'
 task:
-  workerType: flame-kk
+  workerType: balrog
+  scopes:
+    - 'docker-worker:feature:balrogVPNProxy'
 
   payload:
+    features:
+      balrogVPNProxy: true
     env:
       TARGET: 'aries'
       DEBUG: 0
     command:
       - >
         checkout-gecko workspace &&
         cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
         buildbot_step 'Build' ./build-lightsaber-nightly.sh $HOME/workspace
--- a/testing/taskcluster/tasks/builds/b2g_flame_kk_nightly.yml
+++ b/testing/taskcluster/tasks/builds/b2g_flame_kk_nightly.yml
@@ -1,21 +1,24 @@
 $inherits:
   from: 'tasks/builds/b2g_phone_base.yml'
   variables:
     build_name: 'flame-kk-nightly'
     build_type: 'opt'
 task:
-  workerType: flame-kk
+  workerType: balrog
   scopes:
     - 'docker-worker:cache:build-flame-kk-nightly'
+    - 'docker-worker:feature:balrogVPNProxy'
   metadata:
     name: '[TC] B2G Flame KK Nightly'
 
   payload:
+    features:
+      balrogVPNProxy: true
     cache:
       build-flame-kk-nightly: /home/worker/object-folder
     env:
       TARGET: 'flame-kk'
       DEBUG: 0
     command:
       - >
         checkout-gecko workspace &&
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/LoginDoorhangers.jsm
@@ -0,0 +1,244 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = [
+  "LoginDoorhangers",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+this.LoginDoorhangers = {};
+
+/**
+ * Doorhanger for selecting and filling logins.
+ *
+ * @param {Object} properties
+ *        Properties from this object will be applied to the new instance.
+ */
+this.LoginDoorhangers.FillDoorhanger = function (properties) {
+  this.onFilterInput = this.onFilterInput.bind(this);
+  this.onListDblClick = this.onListDblClick.bind(this);
+  this.onListKeyPress = this.onListKeyPress.bind(this);
+
+  this.filterString = properties.filterString;
+
+  if (properties.browser) {
+    this.browser = properties.browser;
+  }
+};
+
+this.LoginDoorhangers.FillDoorhanger.prototype = {
+  /**
+   * Whether the elements for this doorhanger are currently in the document.
+   */
+  bound: false,
+
+  /**
+   * Associates the doorhanger with its browser. When the tab associated to this
+   * browser is selected, the anchor icon for the doorhanger will appear.
+   *
+   * The browser may change during the lifetime of the doorhanger, in case the
+   * web page is moved to a different chrome window by the swapDocShells method.
+   */
+  set browser(browser) {
+    this._browser = browser;
+
+    let doorhanger = this;
+    let PopupNotifications = this.chomeDocument.defaultView.PopupNotifications;
+    let notification = PopupNotifications.show(
+      browser,
+      "password-fill",
+      "",
+      "password-notification-icon",
+      null,
+      null,
+      {
+        dismissed: true,
+        persistWhileVisible: true,
+        eventCallback: function (topic, otherBrowser) {
+          switch (topic) {
+            case "shown":
+              // Since we specified the "dismissed" option, this event will only
+              // be called after the "show" method returns, so the reference to
+              // "this.notification" will be available at this point.
+              doorhanger.bound = true;
+              doorhanger.bind();
+              break;
+
+            case "dismissed":
+            case "removed":
+              if (doorhanger.bound) {
+                doorhanger.unbind();
+              }
+              break;
+
+            case "swapping":
+              this._browser = otherBrowser;
+              return true;
+          }
+          return false;
+        },
+      }
+    );
+
+    this.notification = notification;
+    notification.doorhanger = this;
+  },
+  get browser() {
+    return this._browser;
+  },
+  _browser: null,
+
+  /**
+   * DOM document to which the doorhanger is currently associated.
+   *
+   * This may change during the lifetime of the doorhanger, in case the web page
+   * is moved to a different chrome window by the swapDocShells method.
+   */
+  get chomeDocument() {
+    return this.browser.ownerDocument;
+  },
+
+  /**
+   * Hides this notification, if the notification panel is currently open.
+   */
+  hide() {
+    let PopupNotifications = this.chomeDocument.defaultView.PopupNotifications;
+    if (PopupNotifications.isPanelOpen) {
+      PopupNotifications.panel.hidePopup();
+    }
+  },
+
+  /**
+   * Removes the doorhanger from the browser.
+   */
+  remove() {
+    this.notification.remove();
+  },
+
+  /**
+   * Binds this doorhanger to its UI controls.
+   */
+  bind() {
+    this.element = this.chomeDocument.getElementById("login-fill-doorhanger");
+    this.list = this.chomeDocument.getElementById("login-fill-list");
+    this.filter = this.chomeDocument.getElementById("login-fill-filter");
+
+    this.filter.setAttribute("value", this.filterString);
+
+    this.refreshList();
+
+    this.filter.addEventListener("input", this.onFilterInput);
+    this.list.addEventListener("dblclick", this.onListDblClick);
+    this.list.addEventListener("keypress", this.onListKeyPress);
+
+    // Move the main element to the notification panel for displaying.
+    this.notification.owner.panel.firstElementChild.appendChild(this.element);
+    this.element.hidden = false;
+  },
+
+  /**
+   * Unbinds this doorhanger from its UI controls.
+   */
+  unbind() {
+    this.filter.removeEventListener("input", this.onFilterInput);
+    this.list.removeEventListener("dblclick", this.onListDblClick);
+    this.list.removeEventListener("keypress", this.onListKeyPress);
+
+    this.clearList();
+
+    // Place the element back in the document for the next time we need it.
+    this.element.hidden = true;
+    this.chomeDocument.getElementById("mainPopupSet").appendChild(this.element);
+  },
+
+  /**
+   * User-editable string used to filter the list of all logins.
+   */
+  filterString: "",
+
+  /**
+   * Handles text changes in the filter textbox.
+   */
+  onFilterInput() {
+    this.filterString = this.filter.value;
+    this.refreshList();
+  },
+
+  /**
+   * Rebuilds the list of logins.
+   */
+  refreshList() {
+    this.clearList();
+
+    let formLogins = Services.logins.findLogins({}, "", "", null);
+    let filterToUse = this.filterString.trim().toLowerCase();
+    if (filterToUse) {
+      formLogins = formLogins.filter(login => {
+        return login.hostname.toLowerCase().indexOf(filterToUse) != -1 ||
+               login.username.toLowerCase().indexOf(filterToUse) != -1 ;
+      });
+    }
+
+    for (let { hostname, username } of formLogins) {
+      let item = this.chomeDocument.createElementNS(XUL_NS, "richlistitem");
+      item.classList.add("login-fill-item");
+      item.setAttribute("hostname", hostname);
+      item.setAttribute("username", username);
+      this.list.appendChild(item);
+    }
+  },
+
+  /**
+   * Clears the list of logins.
+   */
+  clearList() {
+    while (this.list.firstChild) {
+      this.list.removeChild(this.list.firstChild);
+    }
+  },
+
+  /**
+   * Handles the action associated to a login item.
+   */
+  onListDblClick(event) {
+    if (event.button != 0 || !this.list.selectedItem) {
+      return;
+    }
+    this.fillLogin();
+  },
+  onListKeyPress(event) {
+    if (event.keyCode != Ci.nsIDOMKeyEvent.DOM_VK_RETURN ||
+        !this.list.selectedItem) {
+      return;
+    }
+    this.fillLogin();
+  },
+  fillLogin() {
+    this.hide();
+  },
+};
+
+/**
+ * Retrieves an existing FillDoorhanger associated with a browser, or null if an
+ * associated doorhanger of that type cannot be found.
+ *
+ * @param An object with the following properties:
+ *        {
+ *          browser:
+ *            The <browser> element to which the doorhanger is associated.
+ *        }
+ */
+this.LoginDoorhangers.FillDoorhanger.find = function ({ browser }) {
+  let PopupNotifications = browser.ownerDocument.defaultView.PopupNotifications;
+  let notification = PopupNotifications.getNotification("password-fill",
+                                                        browser);
+  return notification && notification.doorhanger;
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/content/login.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<!DOCTYPE bindings SYSTEM "chrome://passwordmgr/locale/passwordManager.dtd">
+
+<bindings id="login-bindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:xbl="http://www.mozilla.org/xbl">
+
+  <binding id="login"
+           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+    <content orient="vertical">
+      <xul:label class="login-hostname" crop="end"
+                 xbl:inherits="value=hostname"/>
+      <xul:label class="login-username" crop="end"
+                 xbl:inherits="value=username"/>
+    </content>
+  </binding>
+</bindings>
--- a/toolkit/components/passwordmgr/jar.mn
+++ b/toolkit/components/passwordmgr/jar.mn
@@ -1,11 +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/.
 
 toolkit.jar:
 %   content passwordmgr %content/passwordmgr/
+    content/passwordmgr/login.xml                      (content/login.xml)
 *   content/passwordmgr/passwordManager.xul            (content/passwordManager.xul)
 *   content/passwordmgr/passwordManager.js             (content/passwordManager.js)
 *   content/passwordmgr/passwordManagerExceptions.js   (content/passwordManagerExceptions.js)
     content/passwordmgr/passwordManagerExceptions.xul  (content/passwordManagerExceptions.xul)
     content/passwordmgr/passwordManagerCommon.js       (content/passwordManagerCommon.js)
--- a/toolkit/components/passwordmgr/moz.build
+++ b/toolkit/components/passwordmgr/moz.build
@@ -36,16 +36,17 @@ EXTRA_PP_COMPONENTS += [
 ]
 
 EXTRA_PP_JS_MODULES += [
     'LoginManagerParent.jsm',
 ]
 
 EXTRA_JS_MODULES += [
     'InsecurePasswordUtils.jsm',
+    'LoginDoorhangers.jsm',
     'LoginHelper.jsm',
     'LoginManagerContent.jsm',
     'LoginRecipes.jsm',
 ]
 
 if CONFIG['OS_TARGET'] == 'Android':
     EXTRA_COMPONENTS += [
         'storage-mozStorage.js',
--- a/toolkit/components/places/nsINavHistoryService.idl
+++ b/toolkit/components/places/nsINavHistoryService.idl
@@ -1410,12 +1410,12 @@ interface nsINavHistoryService : nsISupp
    * Clear all TRANSITION_EMBED visits.
    */
   void clearEmbedVisits();
 };
 
 /**
  * @see runInBatchMode of nsINavHistoryService/nsINavBookmarksService
  */
-[scriptable, uuid(5143f2bb-be0a-4faf-9acb-b0ed3f82952c)]
+[scriptable, function, uuid(5a5a9154-95ac-4e3d-90df-558816297407)]
 interface nsINavHistoryBatchCallback : nsISupports {
   void runBatched(in nsISupports aUserData);
 };
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -4181,20 +4181,23 @@ nsNavHistory::URIToResultNode(nsIURI* aU
                               nsNavHistoryQueryOptions* aOptions,
                               nsNavHistoryResultNode** aResult)
 {
   nsAutoCString tagsFragment;
   GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
                      true, tagsFragment);
   // Should match kGetInfoIndex_*
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
-    "SELECT h.id, :page_url, h.title, h.rev_host, h.visit_count, "
-           "h.last_visit_date, f.url, null, null, null, null, "
-           ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid "
+    "SELECT h.id, :page_url, COALESCE(b.title, h.title), "
+           "h.rev_host, h.visit_count, h.last_visit_date, f.url, "
+           "b.id, b.dateAdded, b.lastModified, b.parent, "
+           ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+           "b.guid, b.position, b.type, b.fk "
     "FROM moz_places h "
+    "LEFT JOIN moz_bookmarks b ON b.fk = h.id "
     "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
     "WHERE h.url = :page_url ")
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
 
   nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -2762,17 +2762,17 @@ nsNavHistoryQueryResultNode::NotifyIfTag
 
   // Find matching URI nodes.
   nsRefPtr<nsNavHistoryResultNode> node;
   nsNavHistory* history = nsNavHistory::GetHistoryService();
 
   nsCOMArray<nsNavHistoryResultNode> matches;
   RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
 
-  if (matches.Count() == 0 && mHasSearchTerms && !mRemovingURI) {
+  if (matches.Count() == 0 && mHasSearchTerms) {
     // A new tag has been added, it's possible it matches our query.
     NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
     rv = history->URIToResultNode(aURI, mOptions, getter_AddRefs(node));
     NS_ENSURE_SUCCESS(rv, rv);
     if (history->EvaluateQueryForNode(mQueries, mOptions, node)) {
       rv = InsertSortedChild(node);
       NS_ENSURE_SUCCESS(rv, rv);
     }
@@ -2833,17 +2833,16 @@ NS_IMETHODIMP
 nsNavHistoryQueryResultNode::OnItemRemoved(int64_t aItemId,
                                            int64_t aParentId,
                                            int32_t aIndex,
                                            uint16_t aItemType,
                                            nsIURI* aURI,
                                            const nsACString& aGUID,
                                            const nsACString& aParentGUID)
 {
-  mRemovingURI = aURI;
   if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
       mLiveUpdate != QUERYUPDATE_SIMPLE && mLiveUpdate != QUERYUPDATE_TIME) {
     nsresult rv = Refresh();
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
 }
 
--- a/toolkit/components/places/nsNavHistoryResult.h
+++ b/toolkit/components/places/nsNavHistoryResult.h
@@ -669,17 +669,16 @@ public:
   void ClearChildren(bool unregister);
   nsresult Refresh() override;
 
   virtual uint16_t GetSortType() override;
   virtual void GetSortingAnnotation(nsACString& aSortingAnnotation) override;
   virtual void RecursiveSort(const char* aData,
                              SortComparator aComparator) override;
 
-  nsCOMPtr<nsIURI> mRemovingURI;
   nsresult NotifyIfTagsChanged(nsIURI* aURI);
 
   uint32_t mBatchChanges;
 
   // Tracks transition type filters shared by all mQueries.
   nsTArray<uint32_t> mTransitions;
 
 protected:
--- a/toolkit/components/places/nsTaggingService.js
+++ b/toolkit/components/places/nsTaggingService.js
@@ -102,81 +102,84 @@ TaggingService.prototype = {
    *        Array of tags.  Entries can be tag names or concrete item id.
    * @param trim [optional]
    *        Whether to trim passed-in named tags. Defaults to false.
    * @return Array of tag objects like { id: number, name: string }.
    *
    * @throws Cr.NS_ERROR_INVALID_ARG if any element of the input array is not
    *         a valid tag.
    */
-  _convertInputMixedTagsArray: function TS__convertInputMixedTagsArray(aTags, trim=false)
-  {
-    return aTags.map(function (val)
-    {
-      let tag = { _self: this };
-      if (typeof(val) == "number" && this._tagFolders[val]) {
+  _convertInputMixedTagsArray(aTags, trim=false) {
+    // Handle sparse array with a .filter.
+    return aTags.filter(tag => tag !== undefined)
+                .map(idOrName => {
+      let tag = {};
+      if (typeof(idOrName) == "number" && this._tagFolders[idOrName]) {
         // This is a tag folder id.
-        tag.id = val;
+        tag.id = idOrName;
         // We can't know the name at this point, since a previous tag could
         // want to change it.
-        tag.__defineGetter__("name", function () this._self._tagFolders[this.id]);
+        tag.__defineGetter__("name", () => this._tagFolders[tag.id]);
       }
-      else if (typeof(val) == "string" && val.length > 0 && val.length <= Ci.nsITaggingService.MAX_TAG_LENGTH) {
+      else if (typeof(idOrName) == "string" && idOrName.length > 0 &&
+               idOrName.length <= Ci.nsITaggingService.MAX_TAG_LENGTH) {
         // This is a tag name.
-        tag.name = trim ? val.trim() : val;
+        tag.name = trim ? idOrName.trim() : idOrName;
         // We can't know the id at this point, since a previous tag could
         // have created it.
-        tag.__defineGetter__("id", function () this._self._getItemIdForTag(this.name));
+        tag.__defineGetter__("id", () => this._getItemIdForTag(tag.name));
       }
       else {
         throw Cr.NS_ERROR_INVALID_ARG;
       }
       return tag;
-    }, this);
+    });
   },
 
   // nsITaggingService
   tagURI: function TS_tagURI(aURI, aTags)
   {
     if (!aURI || !aTags || !Array.isArray(aTags)) {
       throw Cr.NS_ERROR_INVALID_ARG;
     }
 
     // This also does some input validation.
     let tags = this._convertInputMixedTagsArray(aTags, true);
 
-    let taggingService = this;
-    PlacesUtils.bookmarks.runInBatchMode({
-      runBatched: function (aUserData)
-      {
-        tags.forEach(function (tag)
-        {
-          if (tag.id == -1) {
-            // Tag does not exist yet, create it.
-            this._createTag(tag.name);
-          }
+    let taggingFunction = () => {
+      for (let tag of tags) {
+        if (tag.id == -1) {
+          // Tag does not exist yet, create it.
+          this._createTag(tag.name);
+        }
+
+        if (this._getItemIdForTaggedURI(aURI, tag.name) == -1) {
+          // The provided URI is not yet tagged, add a tag for it.
+          // Note that bookmarks under tag containers must have null titles.
+          PlacesUtils.bookmarks.insertBookmark(
+            tag.id, aURI, PlacesUtils.bookmarks.DEFAULT_INDEX, null
+          );
+        }
 
-          if (this._getItemIdForTaggedURI(aURI, tag.name) == -1) {
-            // The provided URI is not yet tagged, add a tag for it.
-            // Note that bookmarks under tag containers must have null titles.
-            PlacesUtils.bookmarks.insertBookmark(
-              tag.id, aURI, PlacesUtils.bookmarks.DEFAULT_INDEX, null
-            );
-          }
+        // Try to preserve user's tag name casing.
+        // Rename the tag container so the Places view matches the most-recent
+        // user-typed value.
+        if (PlacesUtils.bookmarks.getItemTitle(tag.id) != tag.name) {
+          // this._tagFolders is updated by the bookmarks observer.
+          PlacesUtils.bookmarks.setItemTitle(tag.id, tag.name);
+        }
+      }
+    };
 
-          // Try to preserve user's tag name casing.
-          // Rename the tag container so the Places view matches the most-recent
-          // user-typed value.
-          if (PlacesUtils.bookmarks.getItemTitle(tag.id) != tag.name) {
-            // this._tagFolders is updated by the bookmarks observer.
-            PlacesUtils.bookmarks.setItemTitle(tag.id, tag.name);
-          }
-        }, taggingService);
-      }
-    }, null);
+    // Use a batch only if creating more than 2 tags.
+    if (tags.length < 3) {
+      taggingFunction();
+    } else {
+      PlacesUtils.bookmarks.runInBatchMode(taggingFunction, null);
+    }
   },
 
   /**
    * Removes the tag container from the tags root if the given tag is empty.
    *
    * @param aTagId
    *        the itemId of the tag element under the tags root
    */
@@ -220,33 +223,35 @@ TaggingService.prototype = {
     let tags = this._convertInputMixedTagsArray(aTags);
 
     let isAnyTagNotTrimmed = tags.some(tag => /^\s|\s$/.test(tag.name));
     if (isAnyTagNotTrimmed) {
       Deprecated.warning("At least one tag passed to untagURI was not trimmed",
                          "https://bugzilla.mozilla.org/show_bug.cgi?id=967196");
     }
 
-    let taggingService = this;
-    PlacesUtils.bookmarks.runInBatchMode({
-      runBatched: function (aUserData)
-      {
-        tags.forEach(function (tag)
-        {
-          if (tag.id != -1) {
-            // A tag could exist.
-            let itemId = this._getItemIdForTaggedURI(aURI, tag.name);
-            if (itemId != -1) {
-              // There is a tagged item.
-              PlacesUtils.bookmarks.removeItem(itemId);
-            }
+    let untaggingFunction = () => {
+      for (let tag of tags) {
+        if (tag.id != -1) {
+          // A tag could exist.
+          let itemId = this._getItemIdForTaggedURI(aURI, tag.name);
+          if (itemId != -1) {
+            // There is a tagged item.
+            PlacesUtils.bookmarks.removeItem(itemId);
           }
-        }, taggingService);
+        }
       }
-    }, null);
+    };
+
+    // Use a batch only if creating more than 2 tags.
+    if (tags.length < 3) {
+      untaggingFunction();
+    } else {
+      PlacesUtils.bookmarks.runInBatchMode(untaggingFunction, null);
+    }
   },
 
   // nsITaggingService
   getURIsForTag: function TS_getURIsForTag(aTagName) {
     if (!aTagName || aTagName.length == 0)
       throw Cr.NS_ERROR_INVALID_ARG;
 
     if (/^\s|\s$/.test(aTagName)) {
--- a/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js
+++ b/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js
@@ -138,18 +138,16 @@ add_test(function onItemChanged_title_bo
 });
 
 add_test(function onItemChanged_tags_bookmark() {
   let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
   let uri = PlacesUtils.bookmarks.getBookmarkURI(id);
   const TITLE = "New title";
   const TAG = "tag"
   gBookmarksObserver.expected = [
-    { name: "onBeginUpdateBatch", // Tag addition uses a batch.
-     args: [] },
     { name: "onItemAdded", // This is the tag folder.
       args: [
         { name: "itemId", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "parentId", check: function (v) v === PlacesUtils.tagsFolderId },
         { name: "index", check: function (v) v === 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_FOLDER },
         { name: "uri", check: function (v) v === null },
         { name: "title", check: function (v) v === TAG },
@@ -176,20 +174,16 @@ add_test(function onItemChanged_tags_boo
         { name: "isAnno", check: function (v) v === false },
         { name: "newValue", check: function (v) v === "" },
         { name: "lastModified", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
         { name: "parentId", check: function (v) v === PlacesUtils.unfiledBookmarksFolderId },
         { name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
         { name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
       ] },
-    { name: "onEndUpdateBatch",
-      args: [] },
-    { name: "onBeginUpdateBatch", // Tag removal uses a batch.
-     args: [] },
     { name: "onItemRemoved", // This is the tag.
       args: [
         { name: "itemId", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "parentId", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "index", check: function (v) v === 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
         { name: "uri", check: function (v) v instanceof Ci.nsIURI && v.equals(uri) },
         { name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
@@ -212,18 +206,16 @@ add_test(function onItemChanged_tags_boo
         { name: "isAnno", check: function (v) v === false },
         { name: "newValue", check: function (v) v === "" },
         { name: "lastModified", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
         { name: "parentId", check: function (v) v === PlacesUtils.unfiledBookmarksFolderId },
         { name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
         { name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
       ] },
-    { name: "onEndUpdateBatch",
-      args: [] },
   ];
   PlacesUtils.tagging.tagURI(uri, [TAG]);
   PlacesUtils.tagging.untagURI(uri, [TAG]);
 });
 
 add_test(function onItemMoved_bookmark() {
   let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
   let uri = PlacesUtils.bookmarks.getBookmarkURI(id);
--- a/toolkit/components/places/tests/queries/test_history_queries_tags_liveUpdate.js
+++ b/toolkit/components/places/tests/queries/test_history_queries_tags_liveUpdate.js
@@ -155,16 +155,20 @@ add_task(function visits_searchterm_quer
 add_task(function pages_searchterm_is_tag_query()
 {
   let [query, options] = newQueryWithOptions();
   query.searchTerms = "test-tag";
   testQueryContents(query, options, function (root) {
     compareArrayToResult([], root);
     gTestData.forEach(function (data) {
       let uri = NetUtil.newURI(data.uri);
+      PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+                                           uri,
+                                           PlacesUtils.bookmarks.DEFAULT_INDEX,
+                                           data.title);
       PlacesUtils.tagging.tagURI(uri, ["test-tag"]);
       compareArrayToResult([data], root);
       PlacesUtils.tagging.untagURI(uri, ["test-tag"]);
       compareArrayToResult([], root);
     });
   });
 });
 
@@ -172,15 +176,19 @@ add_task(function visits_searchterm_is_t
 {
   let [query, options] = newQueryWithOptions();
   query.searchTerms = "test-tag";
   options.resultType = Ci.nsINavHistoryQueryOptions.RESULTS_AS_VISIT;
   testQueryContents(query, options, function (root) {
     compareArrayToResult([], root);
     gTestData.forEach(function (data) {
       let uri = NetUtil.newURI(data.uri);
+      PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+                                           uri,
+                                           PlacesUtils.bookmarks.DEFAULT_INDEX,
+                                           data.title);
       PlacesUtils.tagging.tagURI(uri, ["test-tag"]);
       compareArrayToResult([data], root);
       PlacesUtils.tagging.untagURI(uri, ["test-tag"]);
       compareArrayToResult([], root);
     });
   });
 });
--- a/toolkit/components/reader/JSDOMParser.js
+++ b/toolkit/components/reader/JSDOMParser.js
@@ -278,16 +278,17 @@
     "embed": true,
     "hr": true,
     "img": true,
     "input": true,
     "link": true,
     "meta": true,
     "param": true,
     "source": true,
+    "wbr": true
   };
 
   var whitespace = [" ", "\t", "\n", "\r"];
 
   // See http://www.w3schools.com/dom/dom_nodetype.asp
   var nodeTypes = {
     ELEMENT_NODE: 1,
     ATTRIBUTE_NODE: 2,
--- a/toolkit/components/reader/Readability.js
+++ b/toolkit/components/reader/Readability.js
@@ -104,30 +104,30 @@ Readability.prototype = {
   // tight the competition is among candidates.
   DEFAULT_N_TOP_CANDIDATES: 5,
 
   // The maximum number of pages to loop through before we call
   // it quits and just show a link.
   DEFAULT_MAX_PAGES: 5,
 
   // Element tags to score by default.
-  DEFAULT_TAGS_TO_SCORE: ["SECTION", "P", "TD", "PRE"],
+  DEFAULT_TAGS_TO_SCORE: "section,h2,h3,h4,h5,h6,p,td,pre".toUpperCase().split(","),
 
   // All of the regular expressions in use within readability.
   // Defined up here so we don't instantiate them repeatedly in loops.
   REGEXPS: {
-    unlikelyCandidates: /banner|combx|comment|community|disqus|extra|foot|header|menu|remark|rss|share|shoutbox|sidebar|skyscraper|sponsor|ad-break|agegate|pagination|pager|popup/i,
+    unlikelyCandidates: /banner|combx|comment|community|disqus|extra|foot|header|menu|related|remark|rss|share|shoutbox|sidebar|skyscraper|sponsor|ad-break|agegate|pagination|pager|popup/i,
     okMaybeItsACandidate: /and|article|body|column|main|shadow/i,
     positive: /article|body|content|entry|hentry|main|page|pagination|post|text|blog|story/i,
     negative: /hidden|banner|combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i,
     extraneous: /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|utility/i,
     byline: /byline|author|dateline|writtenby/i,
     replaceFonts: /<(\/?)font[^>]*>/gi,
     normalize: /\s{2,}/g,
-    videos: /https?:\/\/(www\.)?(dailymotion|youtube|youtube-nocookie|player\.vimeo)\.com/i,
+    videos: /\/\/(www\.)?(dailymotion|youtube|youtube-nocookie|player\.vimeo)\.com/i,
     nextLink: /(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i,
     prevLink: /(prev|earl|old|new|<|«)/i,
     whitespace: /^\s*$/,
     hasContent: /\S$/,
   },
 
   DIV_TO_P_ELEMS: [ "A", "BLOCKQUOTE", "DL", "DIV", "IMG", "OL", "P", "PRE", "TABLE", "UL", "SELECT" ],
 
@@ -734,17 +734,22 @@ Readability.prototype = {
           if (!ancestor.tagName)
             return;
 
           if (typeof(ancestor.readability) === 'undefined') {
             this._initializeNode(ancestor);
             candidates.push(ancestor);
           }
 
-          ancestor.readability.contentScore += contentScore / (level === 0 ? 1 : level * 2);
+          // Node score divider:
+          // - parent:             1 (no division)
+          // - grandparent:        2
+          // - great grandparent+: ancestor level * 3
+          var scoreDivider = level === 0 ? 1 : level === 1 ? 2 : level * 3;
+          ancestor.readability.contentScore += contentScore / scoreDivider;
         });
       });
 
       // After we've calculated scores, loop through all of the possible
       // candidate nodes we found and find the one with the highest score.
       var topCandidates = [];
       for (var c = 0, cl = candidates.length; c < cl; c += 1) {
         var candidate = candidates[c];
--- a/toolkit/components/reader/ReaderMode.jsm
+++ b/toolkit/components/reader/ReaderMode.jsm
@@ -3,25 +3,36 @@
  * 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";
 
 this.EXPORTED_SYMBOLS = ["ReaderMode"];
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
+// Constants for telemetry.
+const DOWNLOAD_SUCCESS = 0;
+const DOWNLOAD_ERROR_XHR = 1;
+const DOWNLOAD_ERROR_NO_DOC = 2;
+
+const PARSE_SUCCESS = 0;
+const PARSE_ERROR_TOO_MANY_ELEMENTS = 1;
+const PARSE_ERROR_WORKER = 2;
+const PARSE_ERROR_NO_ARTICLE = 3;
+
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 Cu.importGlobalProperties(["XMLHttpRequest"]);
 
 XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", "resource://services-common/utils.js");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderWorker", "resource://gre/modules/reader/ReaderWorker.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "Readability", function() {
   let scope = {};
   scope.dump = this.dump;
   Services.scriptloader.loadSubScript("resource://gre/modules/reader/Readability.js", scope);
   return scope["Readability"];
 });
 
@@ -164,35 +175,43 @@ this.ReaderMode = {
    * Downloads and parses a document from a URL.
    *
    * @param url URL to download and parse.
    * @return {Promise}
    * @resolves JS object representing the article, or null if no article is found.
    */
   downloadAndParseDocument: Task.async(function* (url) {
     let uri = Services.io.newURI(url, null, null);
-    let doc = yield this._downloadDocument(url);
+    TelemetryStopwatch.start("READER_MODE_DOWNLOAD_MS");
+    let doc = yield this._downloadDocument(url).catch(e => {
+      TelemetryStopwatch.finish("READER_MODE_DOWNLOAD_MS");
+      throw e;
+    });
+    TelemetryStopwatch.finish("READER_MODE_DOWNLOAD_MS");
     return yield this._readerParse(uri, doc);
   }),
 
   _downloadDocument: function (url) {
+    let histogram = Services.telemetry.getHistogramById("READER_MODE_DOWNLOAD_RESULT");
     return new Promise((resolve, reject) => {
       let xhr = new XMLHttpRequest();
       xhr.open("GET", url, true);
       xhr.onerror = evt => reject(evt.error);
       xhr.responseType = "document";
       xhr.onload = evt => {
         if (xhr.status !== 200) {
           reject("Reader mode XHR failed with status: " + xhr.status);
+          histogram.add(DOWNLOAD_ERROR_XHR);
           return;
         }
 
         let doc = xhr.responseXML;
         if (!doc) {
           reject("Reader mode XHR didn't return a document");
+          histogram.add(DOWNLOAD_ERROR_NO_DOC);
           return;
         }
 
         // Manually follow a meta refresh tag if one exists.
         let meta = doc.querySelector("meta[http-equiv=refresh]");
         if (meta) {
           let content = meta.getAttribute("content");
           if (content) {
@@ -200,16 +219,17 @@ this.ReaderMode = {
             if (urlIndex > -1) {
               let url = content.substring(urlIndex + 4);
               this._downloadDocument(url).then((doc) => resolve(doc));
               return;
             }
           }
         }
         resolve(doc);
+        histogram.add(DOWNLOAD_SUCCESS);
       }
       xhr.send();
     });
   },
 
 
   /**
    * Retrieves an article from the cache given an article URI.
@@ -287,55 +307,65 @@ this.ReaderMode = {
    * in readerWorker.js.
    *
    * @param uri The article URI.
    * @param doc The document to parse.
    * @return {Promise}
    * @resolves JS object representing the article, or null if no article is found.
    */
   _readerParse: Task.async(function* (uri, doc) {
+    let histogram = Services.telemetry.getHistogramById("READER_MODE_PARSE_RESULT");
     if (this.parseNodeLimit) {
       let numTags = doc.getElementsByTagName("*").length;
       if (numTags > this.parseNodeLimit) {
         this.log("Aborting parse for " + uri.spec + "; " + numTags + " elements found");
+        histogram.add(PARSE_ERROR_TOO_MANY_ELEMENTS);
         return null;
       }
     }
 
     let uriParam = {
       spec: uri.spec,
       host: uri.host,
       prePath: uri.prePath,
       scheme: uri.scheme,
       pathBase: Services.io.newURI(".", null, uri).spec
     };
 
+    TelemetryStopwatch.start("READER_MODE_SERIALIZE_DOM_MS");
     let serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
                      createInstance(Ci.nsIDOMSerializer);
-    let serializedDoc = yield Promise.resolve(serializer.serializeToString(doc));
+    let serializedDoc = serializer.serializeToString(doc);
+    TelemetryStopwatch.finish("READER_MOD_SERIALIZE_DOM_MS");
 
+    TelemetryStopwatch.start("READER_MODE_WORKER_PARSE_MS");
     let article = null;
     try {
       article = yield ReaderWorker.post("parseDocument", [uriParam, serializedDoc]);
     } catch (e) {
       Cu.reportError("Error in ReaderWorker: " + e);
+      histogram.add(PARSE_ERROR_WORKER);
     }
+    TelemetryStopwatch.finish("READER_MODE_WORKER_PARSE_MS");
 
     if (!article) {
       this.log("Worker did not return an article");
+      histogram.add(PARSE_ERROR_NO_ARTICLE);
       return null;
     }
 
     // Readability returns a URI object, but we only care about the URL.
     article.url = article.uri.spec;
     delete article.uri;
 
     let flags = Ci.nsIDocumentEncoder.OutputSelectionOnly | Ci.nsIDocumentEncoder.OutputAbsoluteLinks;
     article.title = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils)
                                                     .convertToPlainText(article.title, flags, 0);
+
+    histogram.add(PARSE_SUCCESS);
     return article;
   }),
 
   get _cryptoHash() {
     delete this._cryptoHash;
     return this._cryptoHash = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
   },
 
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -7788,10 +7788,44 @@
     "expires_in_version": "50",
     "kind": "count",
     "description": "Count ServiceWorkers that really did get a thread created for them"
   },
   "SERVICE_WORKER_SPAWN_GETS_QUEUED": {
     "expires_in_version": "50",
     "kind": "count",
     "description": "Tracking whether a ServiceWorker spawn gets queued due to hitting max workers per domain limit"
+  },
+  "READER_MODE_SERIALIZE_DOM_MS": {
+    "expires_in_version": "42",
+    "kind": "exponential",
+    "high": "5000",
+    "n_buckets": 15,
+    "description": "Time (ms) to serialize a DOM to send to the reader worker"
+  },
+  "READER_MODE_WORKER_PARSE_MS": {
+    "expires_in_version": "42",
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": 30,
+    "description": "Time (ms) for the reader worker to parse a document"
+  },
+  "READER_MODE_DOWNLOAD_MS": {
+    "expires_in_version": "42",
+    "kind": "exponential",
+    "low": 50,
+    "high": "40000",
+    "n_buckets": 60,
+    "description": "Time (ms) to download a document to show in reader mode"
+  },
+  "READER_MODE_PARSE_RESULT" : {
+    "expires_in_version": "42",
+    "kind": "enumerated",
+    "n_values": 5,
+    "description": "The result of trying to parse a document to show in reader view (0=Success, 1=Error too many elements, 2=Error in worker, 3=Error no article)"
+  },
+  "READER_MODE_DOWNLOAD_RESULT" : {
+    "expires_in_version": "42",
+    "kind": "enumerated",
+    "n_values": 5,
+    "description": "The result of trying to download a document to show in reader view (0=Success, 1=Error XHR, 2=Error no document)"
   }
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/addon.js
@@ -0,0 +1,335 @@
+/* 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";
+
+let { Ci, Cu } = require("chrome");
+let Services = require("Services");
+let { ActorPool } = require("devtools/server/actors/common");
+let { TabSources } = require("./utils/TabSources");
+let makeDebugger = require("./utils/make-debugger");
+let { ConsoleAPIListener } = require("devtools/toolkit/webconsole/utils");
+let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
+let { dbg_assert, update } = DevToolsUtils;
+
+loader.lazyRequireGetter(this, "AddonThreadActor", "devtools/server/actors/script", true);
+loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
+loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
+loader.lazyRequireGetter(this, "WebConsoleActor", "devtools/server/actors/webconsole", true);
+
+loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
+
+function BrowserAddonActor(aConnection, aAddon) {
+  this.conn = aConnection;
+  this._addon = aAddon;
+  this._contextPool = new ActorPool(this.conn);
+  this.conn.addActorPool(this._contextPool);
+  this._threadActor = null;
+  this._global = null;
+
+  this._shouldAddNewGlobalAsDebuggee = this._shouldAddNewGlobalAsDebuggee.bind(this);
+
+  this.makeDebugger = makeDebugger.bind(null, {
+    findDebuggees: this._findDebuggees.bind(this),
+    shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee
+  });
+
+  AddonManager.addAddonListener(this);
+}
+exports.BrowserAddonActor = BrowserAddonActor;
+
+BrowserAddonActor.prototype = {
+  actorPrefix: "addon",
+
+  get exited() {
+    return !this._addon;
+  },
+
+  get id() {
+    return this._addon.id;
+  },
+
+  get url() {
+    return this._addon.sourceURI ? this._addon.sourceURI.spec : undefined;
+  },
+
+  get attached() {
+    return this._threadActor;
+  },
+
+  get global() {
+    return this._global;
+  },
+
+  get sources() {
+    if (!this._sources) {
+      dbg_assert(this.threadActor, "threadActor should exist when creating sources.");
+      this._sources = new TabSources(this._threadActor, this._allowSource);
+    }
+    return this._sources;
+  },
+
+
+  form: function BAA_form() {
+    dbg_assert(this.actorID, "addon should have an actorID.");
+    if (!this._consoleActor) {
+      this._consoleActor = new AddonConsoleActor(this._addon, this.conn, this);
+      this._contextPool.addActor(this._consoleActor);
+    }
+
+    return {
+      actor: this.actorID,
+      id: this.id,
+      name: this._addon.name,
+      url: this.url,
+      debuggable: this._addon.isDebuggable,
+      consoleActor: this._consoleActor.actorID,
+
+      traits: {
+        highlightable: false,
+        networkMonitor: false,
+      },
+    };
+  },
+
+  disconnect: function BAA_disconnect() {
+    this.conn.removeActorPool(this._contextPool);
+    this._contextPool = null;
+    this._consoleActor = null;
+    this._addon = null;
+    this._global = null;
+    AddonManager.removeAddonListener(this);
+  },
+
+  setOptions: function BAA_setOptions(aOptions) {
+    if ("global" in aOptions) {
+      this._global = aOptions.global;
+    }
+  },
+
+  onDisabled: function BAA_onDisabled(aAddon) {
+    if (aAddon != this._addon) {
+      return;
+    }
+
+    this._global = null;
+  },
+
+  onUninstalled: function BAA_onUninstalled(aAddon) {
+    if (aAddon != this._addon) {
+      return;
+    }
+
+    if (this.attached) {
+      this.onDetach();
+      this.conn.send({ from: this.actorID, type: "tabDetached" });
+    }
+
+    this.disconnect();
+  },
+
+  onAttach: function BAA_onAttach() {
+    if (this.exited) {
+      return { type: "exited" };
+    }
+
+    if (!this.attached) {
+      this._threadActor = new AddonThreadActor(this.conn, this);
+      this._contextPool.addActor(this._threadActor);
+    }
+
+    return { type: "tabAttached", threadActor: this._threadActor.actorID };
+  },
+
+  onDetach: function BAA_onDetach() {
+    if (!this.attached) {
+      return { error: "wrongState" };
+    }
+
+    this._contextPool.removeActor(this._threadActor);
+
+    this._threadActor = null;
+    this._sources = null;
+
+    return { type: "detached" };
+  },
+
+  preNest: function() {
+    let e = Services.wm.getEnumerator(null);
+    while (e.hasMoreElements()) {
+      let win = e.getNext();
+      let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowUtils);
+      windowUtils.suppressEventHandling(true);
+      windowUtils.suspendTimeouts();
+    }
+  },
+
+  postNest: function() {
+    let e = Services.wm.getEnumerator(null);
+    while (e.hasMoreElements()) {
+      let win = e.getNext();
+      let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowUtils);
+      windowUtils.resumeTimeouts();
+      windowUtils.suppressEventHandling(false);
+    }
+  },
+
+  /**
+   * Return true if the given global is associated with this addon and should be
+   * added as a debuggee, false otherwise.
+   */
+  _shouldAddNewGlobalAsDebuggee: function (aGlobal) {
+    const global = unwrapDebuggerObjectGlobal(aGlobal);
+    try {
+      // This will fail for non-Sandbox objects, hence the try-catch block.
+      let metadata = Cu.getSandboxMetadata(global);
+      if (metadata) {
+        return metadata.addonID === this.id;
+      }
+    } catch (e) {}
+
+    if (global instanceof Ci.nsIDOMWindow) {
+      let id = {};
+      if (mapURIToAddonID(global.document.documentURIObject, id)) {
+        return id.value === this.id;
+      }
+      return false;
+    }
+
+    // Check the global for a __URI__ property and then try to map that to an
+    // add-on
+    let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__");
+    if (uridescriptor && "value" in uridescriptor && uridescriptor.value) {
+      let uri;
+      try {
+        uri = Services.io.newURI(uridescriptor.value, null, null);
+      }
+      catch (e) {
+        DevToolsUtils.reportException(
+          "BrowserAddonActor.prototype._shouldAddNewGlobalAsDebuggee",
+          new Error("Invalid URI: " + uridescriptor.value)
+        );
+        return false;
+      }
+
+      let id = {};
+      if (mapURIToAddonID(uri, id)) {
+        return id.value === this.id;
+      }
+    }
+
+    return false;
+  },
+
+  /**
+   * Override the eligibility check for scripts and sources to make
+   * sure every script and source with a URL is stored when debugging
+   * add-ons.
+   */
+  _allowSource: function(aSource) {
+    // XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it.
+    if (aSource.url === "resource://gre/modules/addons/XPIProvider.jsm") {
+      return false;
+    }
+
+    return true;
+  },
+
+  /**
+   * Yield the current set of globals associated with this addon that should be
+   * added as debuggees.
+   */
+  _findDebuggees: function (dbg) {
+    return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee);
+  }
+};
+
+BrowserAddonActor.prototype.requestTypes = {
+  "attach": BrowserAddonActor.prototype.onAttach,
+  "detach": BrowserAddonActor.prototype.onDetach
+};
+
+/**
+ * The AddonConsoleActor implements capabilities needed for the add-on web
+ * console feature.
+ *
+ * @constructor
+ * @param object aAddon
+ *        The add-on that this console watches.
+ * @param object aConnection
+ *        The connection to the client, DebuggerServerConnection.
+ * @param object aParentActor
+ *        The parent BrowserAddonActor actor.
+ */
+function AddonConsoleActor(aAddon, aConnection, aParentActor)
+{
+  this.addon = aAddon;
+  WebConsoleActor.call(this, aConnection, aParentActor);
+}
+
+AddonConsoleActor.prototype = Object.create(WebConsoleActor.prototype);
+
+update(AddonConsoleActor.prototype, {
+  constructor: AddonConsoleActor,
+
+  actorPrefix: "addonConsole",
+
+  /**
+   * The add-on that this console watches.
+   */
+  addon: null,
+
+  /**
+   * The main add-on JS global
+   */
+  get window() {
+    return this.parentActor.global;
+  },
+
+  /**
+   * Destroy the current AddonConsoleActor instance.
+   */
+  disconnect: function ACA_disconnect()
+  {
+    WebConsoleActor.prototype.disconnect.call(this);
+    this.addon = null;
+  },
+
+  /**
+   * Handler for the "startListeners" request.
+   *
+   * @param object aRequest
+   *        The JSON request object received from the Web Console client.
+   * @return object
+   *         The response object which holds the startedListeners array.
+   */
+  onStartListeners: function ACA_onStartListeners(aRequest)
+  {
+    let startedListeners = [];
+
+    while (aRequest.listeners.length > 0) {
+      let listener = aRequest.listeners.shift();
+      switch (listener) {
+        case "ConsoleAPI":
+          if (!this.consoleAPIListener) {
+            this.consoleAPIListener =
+              new ConsoleAPIListener(null, this, "addon/" + this.addon.id);
+            this.consoleAPIListener.init();
+          }
+          startedListeners.push(listener);
+          break;
+      }
+    }
+    return {
+      startedListeners: startedListeners,
+      nativeConsoleAPI: true,
+      traits: this.traits,
+    };
+  },
+});
+
+AddonConsoleActor.prototype.requestTypes = Object.create(WebConsoleActor.prototype.requestTypes);
+AddonConsoleActor.prototype.requestTypes.startListeners = AddonConsoleActor.prototype.onStartListeners;
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -5298,8 +5298,37 @@ function findEntryPointsForLine(scripts,
 function setBreakpointAtEntryPoints(actor, entryPoints) {
   for (let { script, offsets } of entryPoints) {
     actor.addScript(script);
     for (let offset of offsets) {
       script.setBreakpoint(offset, actor);
     }
   }
 }
+
+/**
+ * Unwrap a global that is wrapped in a |Debugger.Object|, or if the global has
+ * become a dead object, return |undefined|.
+ *
+ * @param Debugger.Object wrappedGlobal
+ *        The |Debugger.Object| which wraps a global.
+ *
+ * @returns {Object|undefined}
+ *          Returns the unwrapped global object or |undefined| if unwrapping
+ *          failed.
+ */
+exports.unwrapDebuggerObjectGlobal = wrappedGlobal => {
+  try {
+    // Because of bug 991399 we sometimes get nuked window references here. We
+    // just bail out in that case.
+    //
+    // Note that addon sandboxes have a DOMWindow as their prototype. So make
+    // sure that we can touch the prototype too (whatever it is), in case _it_
+    // is it a nuked window reference. We force stringification to make sure
+    // that any dead object proxies make themselves known.
+    let global = wrappedGlobal.unsafeDereference();
+    Object.getPrototypeOf(global) + "";
+    return global;
+  }
+  catch (e) {
+    return undefined;
+  }
+};
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -8,25 +8,25 @@
 
 let { Ci, Cu } = require("chrome");
 let Services = require("Services");
 let promise = require("promise");
 let { ActorPool, createExtraActors, appendExtraActors } = require("devtools/server/actors/common");
 let { DebuggerServer } = require("devtools/server/main");
 let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 let { dbg_assert } = DevToolsUtils;
-let { TabSources, isHiddenSource } = require("./utils/TabSources");
+let { TabSources } = require("./utils/TabSources");
 let makeDebugger = require("./utils/make-debugger");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true);
-loader.lazyRequireGetter(this, "AddonThreadActor", "devtools/server/actors/script", true);
 loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/script", true);
-loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
+loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
+loader.lazyRequireGetter(this, "BrowserAddonActor", "devtools/server/actors/addon", true);
 loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 
 // Assumptions on events module:
 // events needs to be dispatched synchronously,
 // by calling the listeners in the order or registration.
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 
 loader.lazyRequireGetter(this, "StyleSheetActor", "devtools/server/actors/stylesheets", true);
@@ -107,45 +107,16 @@ function sendShutdownEvent() {
     evt.initEvent("Debugger:Shutdown", true, false);
     win.document.documentElement.dispatchEvent(evt);
   }
 }
 
 exports.sendShutdownEvent = sendShutdownEvent;
 
 /**
- * Unwrap a global that is wrapped in a |Debugger.Object|, or if the global has
- * become a dead object, return |undefined|.
- *
- * @param Debugger.Object wrappedGlobal
- *        The |Debugger.Object| which wraps a global.
- *
- * @returns {Object|undefined}
- *          Returns the unwrapped global object or |undefined| if unwrapping
- *          failed.
- */
-const unwrapDebuggerObjectGlobal = wrappedGlobal => {
-  try {
-    // Because of bug 991399 we sometimes get nuked window references here. We
-    // just bail out in that case.
-    //
-    // Note that addon sandboxes have a DOMWindow as their prototype. So make
-    // sure that we can touch the prototype too (whatever it is), in case _it_
-    // is it a nuked window reference. We force stringification to make sure
-    // that any dead object proxies make themselves known.
-    let global = wrappedGlobal.unsafeDereference();
-    Object.getPrototypeOf(global) + "";
-    return global;
-  }
-  catch (e) {
-    return undefined;
-  }
-};
-
-/**
  * Construct a root actor appropriate for use in a server running in a
  * browser. The returned root actor:
  * - respects the factories registered with DebuggerServer.addGlobalActor,
  * - uses a BrowserTabList to supply tab actors,
  * - sends all navigator:browser window documents a Debugger:Shutdown event
  *   when it exits.
  *
  * * @param aConnection DebuggerServerConnection
@@ -2000,248 +1971,16 @@ BrowserAddonList.prototype.onInstalled =
 
 BrowserAddonList.prototype.onUninstalled = function (aAddon) {
   this._actorByAddonId.delete(aAddon.id);
   this._onListChanged();
 };
 
 exports.BrowserAddonList = BrowserAddonList;
 
-function BrowserAddonActor(aConnection, aAddon) {
-  this.conn = aConnection;
-  this._addon = aAddon;
-  this._contextPool = new ActorPool(this.conn);
-  this.conn.addActorPool(this._contextPool);
-  this._threadActor = null;
-  this._global = null;
-
-  this._shouldAddNewGlobalAsDebuggee = this._shouldAddNewGlobalAsDebuggee.bind(this);
-
-  this.makeDebugger = makeDebugger.bind(null, {
-    findDebuggees: this._findDebuggees.bind(this),
-    shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee
-  });
-
-  AddonManager.addAddonListener(this);
-}
-
-BrowserAddonActor.prototype = {
-  actorPrefix: "addon",
-
-  get exited() {
-    return !this._addon;
-  },
-
-  get id() {
-    return this._addon.id;
-  },
-
-  get url() {
-    return this._addon.sourceURI ? this._addon.sourceURI.spec : undefined;
-  },
-
-  get attached() {
-    return this._threadActor;
-  },
-
-  get global() {
-    return this._global;
-  },
-
-  get sources() {
-    if (!this._sources) {
-      dbg_assert(this.threadActor, "threadActor should exist when creating sources.");
-      this._sources = new TabSources(this._threadActor, this._allowSource);
-    }
-    return this._sources;
-  },
-
-
-  form: function BAA_form() {
-    dbg_assert(this.actorID, "addon should have an actorID.");
-    if (!this._consoleActor) {
-      let {AddonConsoleActor} = require("devtools/server/actors/webconsole");
-      this._consoleActor = new AddonConsoleActor(this._addon, this.conn, this);
-      this._contextPool.addActor(this._consoleActor);
-    }
-
-    return {
-      actor: this.actorID,
-      id: this.id,
-      name: this._addon.name,
-      url: this.url,
-      debuggable: this._addon.isDebuggable,
-      consoleActor: this._consoleActor.actorID,
-
-      traits: {
-        highlightable: false,
-        networkMonitor: false,
-      },
-    };
-  },
-
-  disconnect: function BAA_disconnect() {
-    this.conn.removeActorPool(this._contextPool);
-    this._contextPool = null;
-    this._consoleActor = null;
-    this._addon = null;
-    this._global = null;
-    AddonManager.removeAddonListener(this);
-  },
-
-  setOptions: function BAA_setOptions(aOptions) {
-    if ("global" in aOptions) {
-      this._global = aOptions.global;
-    }
-  },
-
-  onDisabled: function BAA_onDisabled(aAddon) {
-    if (aAddon != this._addon) {
-      return;
-    }
-
-    this._global = null;
-  },
-
-  onUninstalled: function BAA_onUninstalled(aAddon) {
-    if (aAddon != this._addon) {
-      return;
-    }
-
-    if (this.attached) {
-      this.onDetach();
-      this.conn.send({ from: this.actorID, type: "tabDetached" });
-    }
-
-    this.disconnect();
-  },
-
-  onAttach: function BAA_onAttach() {
-    if (this.exited) {
-      return { type: "exited" };
-    }
-
-    if (!this.attached) {
-      this._threadActor = new AddonThreadActor(this.conn, this);
-      this._contextPool.addActor(this._threadActor);
-    }
-
-    return { type: "tabAttached", threadActor: this._threadActor.actorID };
-  },
-
-  onDetach: function BAA_onDetach() {
-    if (!this.attached) {
-      return { error: "wrongState" };
-    }
-
-    this._contextPool.removeActor(this._threadActor);
-
-    this._threadActor = null;
-    this._sources = null;
-
-    return { type: "detached" };
-  },
-
-  preNest: function() {
-    let e = Services.wm.getEnumerator(null);
-    while (e.hasMoreElements()) {
-      let win = e.getNext();
-      let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIDOMWindowUtils);
-      windowUtils.suppressEventHandling(true);
-      windowUtils.suspendTimeouts();
-    }
-  },
-
-  postNest: function() {
-    let e = Services.wm.getEnumerator(null);
-    while (e.hasMoreElements()) {
-      let win = e.getNext();
-      let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIDOMWindowUtils);
-      windowUtils.resumeTimeouts();
-      windowUtils.suppressEventHandling(false);
-    }
-  },
-
-  /**
-   * Return true if the given global is associated with this addon and should be
-   * added as a debuggee, false otherwise.
-   */
-  _shouldAddNewGlobalAsDebuggee: function (aGlobal) {
-    const global = unwrapDebuggerObjectGlobal(aGlobal);
-    try {
-      // This will fail for non-Sandbox objects, hence the try-catch block.
-      let metadata = Cu.getSandboxMetadata(global);
-      if (metadata) {
-        return metadata.addonID === this.id;
-      }
-    } catch (e) {}
-
-    if (global instanceof Ci.nsIDOMWindow) {
-      let id = {};
-      if (mapURIToAddonID(global.document.documentURIObject, id)) {
-        return id.value === this.id;
-      }
-      return false;
-    }
-
-    // Check the global for a __URI__ property and then try to map that to an
-    // add-on
-    let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__");
-    if (uridescriptor && "value" in uridescriptor && uridescriptor.value) {
-      let uri;
-      try {
-        uri = Services.io.newURI(uridescriptor.value, null, null);
-      }
-      catch (e) {
-        DevToolsUtils.reportException(
-          "BrowserAddonActor.prototype._shouldAddNewGlobalAsDebuggee",
-          new Error("Invalid URI: " + uridescriptor.value)
-        );
-        return false;
-      }
-
-      let id = {};
-      if (mapURIToAddonID(uri, id)) {
-        return id.value === this.id;
-      }
-    }
-
-    return false;
-  },
-
-  /**
-   * Override the eligibility check for scripts and sources to make
-   * sure every script and source with a URL is stored when debugging
-   * add-ons.
-   */
-  _allowSource: function(aSource) {
-    // XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it.
-    if (aSource.url === "resource://gre/modules/addons/XPIProvider.jsm") {
-      return false;
-    }
-
-    return true;
-  },
-
-  /**
-   * Yield the current set of globals associated with this addon that should be
-   * added as debuggees.
-   */
-  _findDebuggees: function (dbg) {
-    return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee);
-  }
-};
-
-BrowserAddonActor.prototype.requestTypes = {
-  "attach": BrowserAddonActor.prototype.onAttach,
-  "detach": BrowserAddonActor.prototype.onDetach
-};
-
 /**
  * The DebuggerProgressListener object is an nsIWebProgressListener which
  * handles onStateChange events for the inspected browser. If the user tries to
  * navigate away from a paused page, the listener makes sure that the debuggee
  * is resumed before the navigation begins.
  *
  * @param TabActor aTabActor
  *        The tab actor associated with this listener.
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -4,17 +4,16 @@
  * 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 { Cc, Ci, Cu } = require("chrome");
 const { DebuggerServer, ActorPool } = require("devtools/server/main");
 const { EnvironmentActor, LongStringActor, ObjectActor, ThreadActor } = require("devtools/server/actors/script");
-const { update } = require("devtools/toolkit/DevToolsUtils");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyGetter(this, "NetworkMonitor", () => {
   return require("devtools/toolkit/webconsole/network-monitor")
          .NetworkMonitor;
@@ -730,18 +729,16 @@ WebConsoleActor.prototype =
             }
             messages.push(message);
           });
           break;
         }
       }
     }
 
-    messages.sort(function(a, b) { return a.timeStamp - b.timeStamp; });
-
     return {
       from: this.actorID,
       messages: messages,
     };
   },
 
   /**
    * Handler for the "evaluateJSAsync" request. This method evaluates the given
@@ -1548,101 +1545,16 @@ WebConsoleActor.prototype.requestTypes =
   clearMessagesCache: WebConsoleActor.prototype.onClearMessagesCache,
   getPreferences: WebConsoleActor.prototype.onGetPreferences,
   setPreferences: WebConsoleActor.prototype.onSetPreferences,
   sendHTTPRequest: WebConsoleActor.prototype.onSendHTTPRequest
 };
 
 exports.WebConsoleActor = WebConsoleActor;
 
-
-/**
- * The AddonConsoleActor implements capabilities needed for the add-on web
- * console feature.
- *
- * @constructor
- * @param object aAddon
- *        The add-on that this console watches.
- * @param object aConnection
- *        The connection to the client, DebuggerServerConnection.
- * @param object aParentActor
- *        The parent BrowserAddonActor actor.
- */
-function AddonConsoleActor(aAddon, aConnection, aParentActor)
-{
-  this.addon = aAddon;
-  WebConsoleActor.call(this, aConnection, aParentActor);
-}
-
-AddonConsoleActor.prototype = Object.create(WebConsoleActor.prototype);
-
-update(AddonConsoleActor.prototype, {
-  constructor: AddonConsoleActor,
-
-  actorPrefix: "addonConsole",
-
-  /**
-   * The add-on that this console watches.
-   */
-  addon: null,
-
-  /**
-   * The main add-on JS global
-   */
-  get window() {
-    return this.parentActor.global;
-  },
-
-  /**
-   * Destroy the current AddonConsoleActor instance.
-   */
-  disconnect: function ACA_disconnect()
-  {
-    WebConsoleActor.prototype.disconnect.call(this);
-    this.addon = null;
-  },
-
-  /**
-   * Handler for the "startListeners" request.
-   *
-   * @param object aRequest
-   *        The JSON request object received from the Web Console client.
-   * @return object
-   *         The response object which holds the startedListeners array.
-   */
-  onStartListeners: function ACA_onStartListeners(aRequest)
-  {
-    let startedListeners = [];
-
-    while (aRequest.listeners.length > 0) {
-      let listener = aRequest.listeners.shift();
-      switch (listener) {
-        case "ConsoleAPI":
-          if (!this.consoleAPIListener) {
-            this.consoleAPIListener =
-              new ConsoleAPIListener(null, this, "addon/" + this.addon.id);
-            this.consoleAPIListener.init();
-          }
-          startedListeners.push(listener);
-          break;
-      }
-    }
-    return {
-      startedListeners: startedListeners,
-      nativeConsoleAPI: true,
-      traits: this.traits,
-    };
-  },
-});
-
-AddonConsoleActor.prototype.requestTypes = Object.create(WebConsoleActor.prototype.requestTypes);
-AddonConsoleActor.prototype.requestTypes.startListeners = AddonConsoleActor.prototype.onStartListeners;
-
-exports.AddonConsoleActor = AddonConsoleActor;
-
 /**
  * Creates an actor for a network event.
  *
  * @constructor
  * @param object aChannel
  *        The nsIChannel associated with this event.
  * @param object aWebConsoleActor
  *        The parent WebConsoleActor instance for this object.
@@ -1687,16 +1599,17 @@ NetworkEventActor.prototype =
   /**
    * Returns a grip for this actor for returning in a protocol message.
    */
   grip: function NEA_grip()
   {
     return {
       actor: this.actorID,
       startedDateTime: this._startedDateTime,
+      timeStamp: Date.parse(this._startedDateTime),
       url: this._request.url,
       method: this._request.method,
       isXHR: this._isXHR,
       fromCache: this._fromCache,
       private: this._private,
     };
   },
 
--- a/toolkit/devtools/server/moz.build
+++ b/toolkit/devtools/server/moz.build
@@ -29,16 +29,17 @@ EXTRA_JS_MODULES.devtools.server += [
     'child.js',
     'content-globals.js',
     'main.js',
     'protocol.js',
 ]
 
 EXTRA_JS_MODULES.devtools.server.actors += [
     'actors/actor-registry.js',
+    'actors/addon.js',
     'actors/animation.js',
     'actors/call-watcher.js',
     'actors/canvas.js',
     'actors/child-process.js',
     'actors/childtab.js',
     'actors/chrome.js',
     'actors/common.js',
     'actors/csscoverage.js',
--- a/toolkit/devtools/webconsole/client.js
+++ b/toolkit/devtools/webconsole/client.js
@@ -3,16 +3,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/. */
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
+const EventEmitter = require("devtools/toolkit/event-emitter");
 
 loader.lazyImporter(this, "LongStringClient", "resource://gre/modules/devtools/dbg-client.jsm");
 
 /**
  * A WebConsoleClient is used as a front end for the WebConsoleActor that is
  * created on the server, hiding implementation details.
  *
  * @param object aDebuggerClient
@@ -23,47 +24,174 @@ loader.lazyImporter(this, "LongStringCli
  */
 function WebConsoleClient(aDebuggerClient, aResponse)
 {
   this._actor = aResponse.from;
   this._client = aDebuggerClient;
   this._longStrings = {};
   this.traits = aResponse.traits || {};
   this.events = [];
+  this._networkRequests = new Map();
 
   this.pendingEvaluationResults = new Map();
   this.onEvaluationResult = this.onEvaluationResult.bind(this);
+  this.onNetworkEvent = this._onNetworkEvent.bind(this);
+  this.onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
 
   this._client.addListener("evaluationResult", this.onEvaluationResult);
+  this._client.addListener("networkEvent", this.onNetworkEvent);
+  this._client.addListener("networkEventUpdate", this.onNetworkEventUpdate);
+  EventEmitter.decorate(this);
 }
 
 exports.WebConsoleClient = WebConsoleClient;
 
 WebConsoleClient.prototype = {
   _longStrings: null,
   traits: null,
 
+  /**
+   * Holds the network requests currently displayed by the Web Console. Each key
+   * represents the connection ID and the value is network request information.
+   * @private
+   * @type object
+   */
+  _networkRequests: null,
+
+  getNetworkRequest(actorId) {
+    return this._networkRequests.get(actorId);
+  },
+
+  hasNetworkRequest(actorId) {
+    return this._networkRequests.has(actorId);
+  },
+
+  removeNetworkRequest(actorId) {
+    this._networkRequests.delete(actorId);
+  },
+
+  getNetworkEvents() {
+    return this._networkRequests.values();
+  },
+
   get actor() { return this._actor; },
 
   /**
+   * The "networkEvent" message type handler. We redirect any message to
+   * the UI for displaying.
+   *
+   * @private
+   * @param string type
+   *        Message type.
+   * @param object packet
+   *        The message received from the server.
+   */
+  _onNetworkEvent: function (type, packet)
+  {
+    if (packet.from == this._actor) {
+      let actor = packet.eventActor;
+      let networkInfo = {
+        _type: "NetworkEvent",
+        timeStamp: actor.timeStamp,
+        node: null,
+        actor: actor.actor,
+        discardRequestBody: true,
+        discardResponseBody: true,
+        startedDateTime: actor.startedDateTime,
+        request: {
+          url: actor.url,
+          method: actor.method,
+        },
+        isXHR: actor.isXHR,
+        response: {},
+        timings: {},
+        updates: [], // track the list of network event updates
+        private: actor.private,
+        fromCache: actor.fromCache
+      };
+      this._networkRequests.set(actor.actor, networkInfo);
+
+      this.emit("networkEvent", networkInfo);
+    }
+  },
+
+  /**
+   * The "networkEventUpdate" message type handler. We redirect any message to
+   * the UI for displaying.
+   *
+   * @private
+   * @param string type
+   *        Message type.
+   * @param object packet
+   *        The message received from the server.
+   */
+  _onNetworkEventUpdate: function (type, packet)
+  {
+    let networkInfo = this.getNetworkRequest(packet.from);
+    if (!networkInfo) {
+      return;
+    }
+
+    networkInfo.updates.push(packet.updateType);
+
+    switch (packet.updateType) {
+      case "requestHeaders":
+        networkInfo.request.headersSize = packet.headersSize;
+        break;
+      case "requestPostData":
+        networkInfo.discardRequestBody = packet.discardRequestBody;
+        networkInfo.request.bodySize = packet.dataSize;
+        break;
+      case "responseStart":
+        networkInfo.response.httpVersion = packet.response.httpVersion;
+        networkInfo.response.status = packet.response.status;
+        networkInfo.response.statusText = packet.response.statusText;
+        networkInfo.response.headersSize = packet.response.headersSize;
+        networkInfo.response.remoteAddress = packet.response.remoteAddress;
+        networkInfo.response.remotePort = packet.response.remotePort;
+        networkInfo.discardResponseBody = packet.response.discardResponseBody;
+        break;
+      case "responseContent":
+        networkInfo.response.content = {
+          mimeType: packet.mimeType,
+        };
+        networkInfo.response.bodySize = packet.contentSize;
+        networkInfo.response.transferredSize = packet.transferredSize;
+        networkInfo.discardResponseBody = packet.discardResponseBody;
+        break;
+      case "eventTimings":
+        networkInfo.totalTime = packet.totalTime;
+        break;
+      case "securityInfo":
+        networkInfo.securityInfo = packet.state;
+        break;
+    }
+
+    this.emit("networkEventUpdate", {
+      packet: packet,
+      networkInfo
+    });
+  },
+
+  /**
    * Retrieve the cached messages from the server.
    *
    * @see this.CACHED_MESSAGES
-   * @param array aTypes
+   * @param array types
    *        The array of message types you want from the server. See
    *        this.CACHED_MESSAGES for known types.
    * @param function aOnResponse
    *        The function invoked when the response is received.
    */
-  getCachedMessages: function WCC_getCachedMessages(aTypes, aOnResponse)
+  getCachedMessages: function WCC_getCachedMessages(types, aOnResponse)
   {
     let packet = {
       to: this._actor,
       type: "getCachedMessages",
-      messageTypes: aTypes,
+      messageTypes: types,
     };
     this._client.request(packet, aOnResponse);
   },
 
   /**
    * Inspect the properties of an object.
    *
    * @param string aActor
@@ -468,15 +596,23 @@ WebConsoleClient.prototype = {
    * detaches from the console actor.
    *
    * @param function aOnResponse
    *        Function to invoke when the server response is received.
    */
   detach: function WCC_detach(aOnResponse)
   {
     this._client.removeListener("evaluationResult", this.onEvaluationResult);
+    this._client.removeListener("networkEvent", this.onNetworkEvent);
+    this._client.removeListener("networkEventUpdate", this.onNetworkEventUpdate);
     this.stopListeners(null, aOnResponse);
     this._longStrings = null;
     this._client = null;
     this.pendingEvaluationResults.clear();
     this.pendingEvaluationResults = null;
+    this.clearNetworkRequests();
+    this._networkRequests = null;
   },
+
+  clearNetworkRequests: function () {
+    this._networkRequests.clear();
+  }
 };
--- a/toolkit/devtools/webconsole/utils.js
+++ b/toolkit/devtools/webconsole/utils.js
@@ -1491,23 +1491,16 @@ ConsoleAPIListener.prototype =
     CONSOLE_WORKER_IDS.forEach((id) => {
       messages = messages.concat(ConsoleAPIStorage.getEvents(id));
     });
 
     if (this.consoleID) {
       messages = messages.filter((m) => m.consoleID == this.consoleID);
     }
 
-    // ConsoleAPIStorage gives up messages sorted, but we ask for different
-    // blocks of events and we must sort them again in order to show them in the
-    // proper order.
-    messages = messages.sort(function(a, b) {
-      return a.timeStamp - b.timeStamp;
-    });
-
     if (aIncludePrivate) {
       return messages;
     }
 
     return messages.filter((m) => !m.private);
   },
 
   /**
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1352,18 +1352,28 @@ function verifyZipSignedState(aFile, aAd
  * @param  aAddon
  *         the add-on object to verify
  * @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant.
  */
 function verifyDirSignedState(aDir, aAddon) {
   if (!SIGNED_TYPES.has(aAddon.type))
     return Promise.resolve(undefined);
 
-  // TODO: Get the certificate for an unpacked add-on (bug 1038072)
-  return Promise.resolve(AddonManager.SIGNEDSTATE_MISSING);
+  let certDB = Cc["@mozilla.org/security/x509certdb;1"]
+               .getService(Ci.nsIX509CertDB);
+
+  let root = Ci.nsIX509CertDB.AddonsPublicRoot;
+  if (!REQUIRE_SIGNING && Preferences.get(PREF_XPI_SIGNATURES_DEV_ROOT, false))
+    root = Ci.nsIX509CertDB.AddonsStageRoot;
+
+  return new Promise(resolve => {
+    certDB.verifySignedDirectoryAsync(root, aDir, (aRv, aCert) => {
+      resolve(getSignedStatus(aRv, aCert, aAddon.id));
+    });
+  });
 }
 
 /**
  * Verifies that a bundle's contents are all correctly signed by an
  * AMO-issued certificate
  *
  * @param  aBundle
  *         the nsIFile for the bundle to check, either a directory or zip file
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -231,16 +231,22 @@ skip-if = buildapp == "mulet"
 # PluginProvider.jsm is not shipped on Android
 skip-if = os == "android"
 [test_pluginBlocklistCtp.js]
 # Bug 676992: test consistently fails on Android
 fail-if = buildapp == "mulet" || os == "android"
 [test_pref_properties.js]
 [test_registry.js]
 [test_safemode.js]
+[test_signed_verify.js]
+[test_signed_inject.js]
+skip-if = true
+[test_signed_install.js]
+run-sequentially = Uses hardcoded ports in xpi files.
+[test_signed_migrate.js]
 [test_startup.js]
 # Bug 676992: test consistently fails on Android
 fail-if = os == "android"
 [test_syncGUID.js]
 [test_strictcompatibility.js]
 [test_targetPlatforms.js]
 [test_theme.js]
 # Bug 676992: test consistently fails on Android
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -17,18 +17,12 @@ skip-if = appname != "firefox"
 [test_isReady.js]
 [test_metadata_update.js]
 [test_pluginInfoURL.js]
 [test_provider_markSafe.js]
 [test_provider_shutdown.js]
 [test_provider_unsafe_access_shutdown.js]
 [test_provider_unsafe_access_startup.js]
 [test_shutdown.js]
-[test_signed_verify.js]
-[test_signed_inject.js]
-skip-if = true
-[test_signed_install.js]
-run-sequentially = Uses hardcoded ports in xpi files.
-[test_signed_migrate.js]
 [test_XPIcancel.js]
 [test_XPIStates.js]
 
 [include:xpcshell-shared.ini]
--- a/toolkit/mozapps/installer/windows/nsis/common.nsh
+++ b/toolkit/mozapps/installer/windows/nsis/common.nsh
@@ -1636,18 +1636,18 @@
     ExecWait '"$SYSDIR\regsvr32.exe" /s /u "${DLL}"'
     ${EnableX64FSRedirection}
   ${Else}
     UnRegDLL "${DLL}"
   ${EndIf}
 
 !macroend
 
-!define RegisterDLL `!insertmacro RegisterDLL`
-!define UnregisterDLL `!insertmacro UnregisterDLL`
+!define RegisterDLL "!insertmacro RegisterDLL"
+!define UnregisterDLL "!insertmacro UnregisterDLL"
 
 
 ################################################################################
 # Macros for retrieving existing install paths
 
 /**
  * Finds a second installation of the application so we can make informed
  * decisions about registry operations. This uses SHCTX to determine the
@@ -4327,34 +4327,31 @@
 !macro OnInstallUninstallCall
   !verbose push
   !verbose ${_MOZFUNC_VERBOSE}
   Call OnInstallUninstall
   !verbose pop
 !macroend
 
 /**
- * Parses the precomplete file to remove an installation's files and directories.
- *
- * @param   _PROGRESSBAR
- *          The progress bar to update using PBM_STEPIT. Can also be "false" if
- *          updating a progressbar isn't needed.
- * @param   _INSTALL_STEP_COUNTER
- *          The install step counter to increment. The variable specified in
- *          this parameter is also updated. Can also be "false" if a counter
- *          isn't needed.
- * $R2 = false if all files were deleted or moved to the tobedeleted directory.
+ * Parses the precomplete file to remove an installation's files and
+ * directories.
+ *
+ * @param   _CALLBACK
+ *          The function address of a callback function for progress or "false"
+ *          if there is no callback function.
+ *
+ * $R3 = false if all files were deleted or moved to the tobedeleted directory.
  *       true if file(s) could not be moved to the tobedeleted directory.
- * $R3 = Path to temporary precomplete file.
- * $R4 = File handle for the temporary precomplete file.
- * $R5 = String returned from FileRead.
- * $R6 = First seven characters of the string returned from FileRead.
- * $R7 = Temporary file path used to rename files that are in use.
- * $R8 = _PROGRESSBAR
- * $R9 = _INSTALL_STEP_COUNTER
+ * $R4 = Path to temporary precomplete file.
+ * $R5 = File handle for the temporary precomplete file.
+ * $R6 = String returned from FileRead.
+ * $R7 = First seven characters of the string returned from FileRead.
+ * $R8 = Temporary file path used to rename files that are in use.
+ * $R9 = _CALLBACK
  */
 !macro RemovePrecompleteEntries
 
   !ifndef ${_MOZFUNC_UN}RemovePrecompleteEntries
     !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
     !insertmacro ${_MOZFUNC_UN_TMP}GetParent
     !insertmacro ${_MOZFUNC_UN_TMP}TrimNewLines
     !insertmacro ${_MOZFUNC_UN_TMP}WordReplace
@@ -4363,146 +4360,133 @@
     !undef _MOZFUNC_UN_TMP
 
     !verbose push
     !verbose ${_MOZFUNC_VERBOSE}
     !define ${_MOZFUNC_UN}RemovePrecompleteEntries "!insertmacro ${_MOZFUNC_UN}RemovePrecompleteEntriesCall"
 
     Function ${_MOZFUNC_UN}RemovePrecompleteEntries
       Exch $R9
-      Exch 1
-      Exch $R8
+      Push $R8
       Push $R7
       Push $R6
       Push $R5
       Push $R4
       Push $R3
-      Push $R2
 
       ${If} ${FileExists} "$INSTDIR\precomplete"
-        StrCpy $R2 "false"
+        StrCpy $R3 "false"
 
         RmDir /r "$INSTDIR\${TO_BE_DELETED}"
         CreateDirectory "$INSTDIR\${TO_BE_DELETED}"
-        GetTempFileName $R3 "$INSTDIR\${TO_BE_DELETED}"
-        Delete "$R3"
-        Rename "$INSTDIR\precomplete" "$R3"
+        GetTempFileName $R4 "$INSTDIR\${TO_BE_DELETED}"
+        Delete "$R4"
+        Rename "$INSTDIR\precomplete" "$R4"
 
         ClearErrors
         ; Rename and then remove files
-        FileOpen $R4 "$R3" r
+        FileOpen $R5 "$R4" r
         ${Do}
-          FileRead $R4 $R5
+          FileRead $R5 $R6
           ${If} ${Errors}
             ${Break}
           ${EndIf}
 
-          ${${_MOZFUNC_UN}TrimNewLines} "$R5" $R5
+          ${${_MOZFUNC_UN}TrimNewLines} "$R6" $R6
           ; Replace all occurrences of "/" with "\".
-          ${${_MOZFUNC_UN}WordReplace} "$R5" "/" "\" "+" $R5
+          ${${_MOZFUNC_UN}WordReplace} "$R6" "/" "\" "+" $R6
 
           ; Copy the first 7 chars
-          StrCpy $R6 "$R5" 7
-          ${If} "$R6" == "remove "
+          StrCpy $R7 "$R6" 7
+          ${If} "$R7" == "remove "
             ; Copy the string starting after the 8th char
-            StrCpy $R5 "$R5" "" 8
+            StrCpy $R6 "$R6" "" 8
             ; Copy all but the last char to remove the double quote.
-            StrCpy $R5 "$R5" -1
-            ${If} ${FileExists} "$INSTDIR\$R5"
+            StrCpy $R6 "$R6" -1
+            ${If} ${FileExists} "$INSTDIR\$R6"
               ${Unless} "$R9" == "false"
-                IntOp $R9 $R9 + 2
-              ${EndUnless}
-              ${Unless} "$R8" == "false"
-                SendMessage $R8 ${PBM_STEPIT} 0 0
-                SendMessage $R8 ${PBM_STEPIT} 0 0
+                Call $R9
               ${EndUnless}
 
               ClearErrors
-              Delete "$INSTDIR\$R5"
+              Delete "$INSTDIR\$R6"
               ${If} ${Errors}
-                GetTempFileName $R7 "$INSTDIR\${TO_BE_DELETED}"
-                Delete "$R7"
+                GetTempFileName $R8 "$INSTDIR\${TO_BE_DELETED}"
+                Delete "$R8"
                 ClearErrors
-                Rename "$INSTDIR\$R5" "$R7"
+                Rename "$INSTDIR\$R6" "$R8"
                 ${Unless} ${Errors}
-                  Delete /REBOOTOK "$R7"
+                  Delete /REBOOTOK "$R8"
 
                   ClearErrors
                 ${EndUnless}
 !ifdef __UNINSTALL__
                 ${If} ${Errors}
-                  Delete /REBOOTOK "$INSTDIR\$R5"
-                  StrCpy $R2 "true"
+                  Delete /REBOOTOK "$INSTDIR\$R6"
+                  StrCpy $R3 "true"
                   ClearErrors
                 ${EndIf}
 !endif
               ${EndIf}
             ${EndIf}
-          ${ElseIf} "$R6" == "rmdir $\""
+          ${ElseIf} "$R7" == "rmdir $\""
             ; Copy the string starting after the 7th char.
-            StrCpy $R5 "$R5" "" 7
+            StrCpy $R6 "$R6" "" 7
             ; Copy all but the last two chars to remove the slash and the double quote.
-            StrCpy $R5 "$R5" -2
-            ${If} ${FileExists} "$INSTDIR\$R5"
+            StrCpy $R6 "$R6" -2
+            ${If} ${FileExists} "$INSTDIR\$R6"
               ; Ignore directory removal errors
-              RmDir "$INSTDIR\$R5"
+              RmDir "$INSTDIR\$R6"
               ClearErrors
             ${EndIf}
           ${EndIf}
         ${Loop}
-        FileClose $R4
+        FileClose $R5
 
         ; Delete the temporary precomplete file
-        Delete /REBOOTOK "$R3"
+        Delete /REBOOTOK "$R4"
 
         RmDir /r /REBOOTOK "$INSTDIR\${TO_BE_DELETED}"
 
         ${If} ${RebootFlag}
-        ${AndIf} "$R2" == "false"
+        ${AndIf} "$R3" == "false"
           ; Clear the reboot flag if all files were deleted or moved to the
           ; tobedeleted directory.
           SetRebootFlag false
         ${EndIf}
       ${EndIf}
 
       ClearErrors
 
-      Pop $R2
       Pop $R3
       Pop $R4
       Pop $R5
       Pop $R6
       Pop $R7
-      Exch $R8
-      Exch 1
+      Pop $R8
       Exch $R9
     FunctionEnd
 
     !verbose pop
   !endif
 !macroend
 
-!macro RemovePrecompleteEntriesCall _PROGRESSBAR _INSTALL_STEP_COUNTER
+!macro RemovePrecompleteEntriesCall _CALLBACK
   !verbose push
-  Push "${_PROGRESSBAR}"
-  Push "${_INSTALL_STEP_COUNTER}"
+  Push "${_CALLBACK}"
   !verbose ${_MOZFUNC_VERBOSE}
   Call RemovePrecompleteEntries
-  Pop ${_INSTALL_STEP_COUNTER}
   !verbose pop
 !macroend
 
-!macro un.RemovePrecompleteEntriesCall _PROGRESSBAR _INSTALL_STEP_COUNTER
+!macro un.RemovePrecompleteEntriesCall _CALLBACK
   !verbose push
   !verbose ${_MOZFUNC_VERBOSE}
-  Push "${_PROGRESSBAR}"
-  Push "${_INSTALL_STEP_COUNTER}"
+  Push "${_CALLBACK}"
   Call un.RemovePrecompleteEntries
-  Pop ${_INSTALL_STEP_COUNTER}
-  Pop $0
   !verbose pop
 !macroend
 
 !macro un.RemovePrecompleteEntries
   !ifndef un.RemovePrecompleteEntries
     !verbose push
     !verbose ${_MOZFUNC_VERBOSE}
     !undef _MOZFUNC_UN
@@ -7323,16 +7307,165 @@
     !insertmacro InitHashAppModelId
 
     !undef _MOZFUNC_UN
     !define _MOZFUNC_UN
     !verbose pop
   !endif
 !macroend
 
+################################################################################
+# Helpers for taskbar progress
+
+!ifndef CLSCTX_INPROC_SERVER
+  !define CLSCTX_INPROC_SERVER  1
+!endif
+
+!define CLSID_ITaskbarList {56fdf344-fd6d-11d0-958a-006097c9a090}
+!define IID_ITaskbarList3 {ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf}
+!define ITaskbarList3->SetProgressValue $ITaskbarList3->9
+!define ITaskbarList3->SetProgressState $ITaskbarList3->10
+
+/**
+ * Creates a single uninitialized object of the ITaskbarList class with a
+ * reference to the ITaskbarList3 interface. This object can be used to set
+ * progress and state on the installer's taskbar icon using the helper macros
+ * in this section.
+ */
+!macro ITBL3Create
+
+  !ifndef ${_MOZFUNC_UN}ITBL3Create
+    Var ITaskbarList3
+
+    !verbose push
+    !verbose ${_MOZFUNC_VERBOSE}
+    !define ${_MOZFUNC_UN}ITBL3Create "!insertmacro ${_MOZFUNC_UN}ITBL3CreateCall"
+
+    Function ${_MOZFUNC_UN}ITBL3Create
+      ; Setting to 0 allows the helper macros to detect when the object was not
+      ; created.
+      StrCpy $ITaskbarList3 0
+      ; Don't create when running silently.
+      ${Unless} ${Silent}
+        ; This is only supported on Win 7 and above.
+        ${If} ${AtLeastWin7}
+          System::Call "ole32::CoCreateInstance(g '${CLSID_ITaskbarList}', \
+                                                i 0, \
+                                                i ${CLSCTX_INPROC_SERVER}, \
+                                                g '${IID_ITaskbarList3}', \
+                                                *i .s)"
+          Pop $ITaskbarList3
+        ${EndIf}
+      ${EndUnless}
+    FunctionEnd
+
+    !verbose pop
+  !endif
+!macroend
+
+!macro ITBL3CreateCall
+  !verbose push
+  !verbose ${_MOZFUNC_VERBOSE}
+  Call ITBL3Create
+  !verbose pop
+!macroend
+
+!macro un.ITBL3CreateCall _PATH_TO_IMAGE
+  !verbose push
+  !verbose ${_MOZFUNC_VERBOSE}
+  Call un.ITBL3Create
+  !verbose pop
+!macroend
+
+!macro un.ITBL3Create
+  !ifndef un.ITBL3Create
+    !verbose push
+    !verbose ${_MOZFUNC_VERBOSE}
+    !undef _MOZFUNC_UN
+    !define _MOZFUNC_UN "un."
+
+    !insertmacro ITBL3Create
+
+    !undef _MOZFUNC_UN
+    !define _MOZFUNC_UN
+    !verbose pop
+  !endif
+!macroend
+
+/**
+ * Sets the percentage completed on the taskbar process icon progress indicator.
+ *
+ * @param   _COMPLETED
+ *          The proportion of the operation that has been completed in relation
+ *          to _TOTAL.
+ * @param   _TOTAL
+ *          The value _COMPLETED will have when the operation has completed.
+ *
+ * $R8 = _COMPLETED
+ * $R9 = _TOTAL
+ */
+!macro ITBL3SetProgressValueCall _COMPLETED _TOTAL
+  Push ${_COMPLETED}
+  Push ${_TOTAL}
+  ${CallArtificialFunction} ITBL3SetProgressValue_
+!macroend
+
+!define ITBL3SetProgressValue "!insertmacro ITBL3SetProgressValueCall"
+!define un.ITBL3SetProgressValue "!insertmacro ITBL3SetProgressValueCall"
+
+!macro ITBL3SetProgressValue_
+  Exch $R9
+  Exch 1
+  Exch $R8
+  ${If} ${AtLeastWin7}
+  ${AndIf} $ITaskbarList3 <> 0
+    System::Call "${ITaskbarList3->SetProgressValue}(i$HWNDPARENT, l$R8, l$R9)"
+  ${EndIf}
+  Exch $R8
+  Exch 1
+  Exch $R9
+!macroend
+
+; Normal state / no progress bar
+!define TBPF_NOPROGRESS       0x00000000
+; Marquee style progress bar
+!define TBPF_INDETERMINATE    0x00000001
+; Standard progress bar
+!define TBPF_NORMAL           0x00000002
+; Red taskbar button to indicate an error occurred
+!define TBPF_ERROR            0x00000004
+; Yellow taskbar button to indicate user attention (input) is required to
+; resume progress
+!define TBPF_PAUSED           0x00000008
+
+/**
+ * Sets the state on the taskbar process icon progress indicator.
+ *
+ * @param   _STATE
+ *          The state to set on the taskbar icon progress indicator. Only one of
+ *          the states defined above should be specified.
+ *
+ * $R9 = _STATE
+ */
+!macro ITBL3SetProgressStateCall _STATE
+  Push ${_STATE}
+  ${CallArtificialFunction} ITBL3SetProgressState_
+!macroend
+
+!define ITBL3SetProgressState "!insertmacro ITBL3SetProgressStateCall"
+!define un.ITBL3SetProgressState "!insertmacro ITBL3SetProgressStateCall"
+
+!macro ITBL3SetProgressState_
+  Exch $R9
+  ${If} ${AtLeastWin7}
+  ${AndIf} $ITaskbarList3 <> 0
+    System::Call "${ITaskbarList3->SetProgressState}(i$HWNDPARENT, i$R9)"
+  ${EndIf}
+  Exch $R9
+!macroend
 
 ################################################################################
 # Helpers for the new user interface
 
 !ifndef MAXDWORD
   !define MAXDWORD 0xffffffff
 !endif
 
@@ -7425,17 +7558,17 @@
   ${NSD_AddExStyle} $R0 ${WS_EX_TRANSPARENT}|${WS_EX_TOPMOST}
 
   Pop $R0
   Pop $2
   Pop $1
   Exch $0
   Pop ${HANDLE}
 !macroend
-!define SetStretchedTransparentImage `!insertmacro __SetStretchedTransparentImage`
+!define SetStretchedTransparentImage "!insertmacro __SetStretchedTransparentImage"
 
 /**
  * Removes a single style from a control.
  *
  * _HANDLE the handle of the control
  * _STYLE  the style to remove
  */
 !macro _RemoveStyle _HANDLE _STYLE
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -492,17 +492,17 @@ function hasUpdateMutex() {
   if (!gUpdateMutexHandle) {
     gUpdateMutexHandle = createMutex(getPerInstallationMutexName(true), false);
   }
   return !!gUpdateMutexHandle;
 }
 
 XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() {
   let useService = false;
-  if (shouldUseService() && isServiceInstalled()) {
+  if (shouldUseService()) {
     // No need to perform directory write checks, the maintenance service will
     // be able to write to all directories.
     LOG("gCanApplyUpdates - bypass the write checks because we'll use the service");
     useService = true;
   }
 
   if (!useService) {
     try {
@@ -643,18 +643,17 @@ XPCOMUtils.defineLazyGetter(this, "gCanS
 function getCanStageUpdates() {
   // If background updates are disabled, then just bail out!
   if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGING_ENABLED, false)) {
     LOG("getCanStageUpdates - staging updates is disabled by preference " +
         PREF_APP_UPDATE_STAGING_ENABLED);
     return false;
   }
 
-  if (AppConstants.platform == "win" && isServiceInstalled() &&
-      shouldUseService()) {
+  if (AppConstants.platform == "win" && shouldUseService()) {
     // No need to perform directory write checks, the maintenance service will
     // be able to write to all directories.
     LOG("getCanStageUpdates - able to stage updates using the service");
     return true;
   }
 
   // For Gonk, the updater will remount the /system partition to move staged
   // files into place.
@@ -1020,23 +1019,22 @@ function releaseSDCardMountLock() {
   if (gSDCardMountLock) {
     gSDCardMountLock.unlock();
     gSDCardMountLock = null;
   }
 }
 
 /**
  * Determines if the service should be used to attempt an update
- * or not.  For now this is only when PREF_APP_UPDATE_SERVICE_ENABLED
- * is true and we have Firefox.
+ * or not.
  *
  * @return  true if the service should be used for updates.
  */
 function shouldUseService() {
-  if (AppConstants.MOZ_MAINTENANCE_SERVICE) {
+  if (AppConstants.MOZ_MAINTENANCE_SERVICE && isServiceInstalled()) {
     return getPref("getBoolPref",
                    PREF_APP_UPDATE_SERVICE_ENABLED, false);
   }
   return false;
 }
 
 /**
  * Determines if the service is is installed and enabled or not.
@@ -3491,20 +3489,25 @@ Checker.prototype = {
     url = url.replace(/%DISTRIBUTION_VERSION%/g,
                       getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
     url = url.replace(/%CUSTOM%/g, getPref("getCharPref", PREF_APP_UPDATE_CUSTOM, ""));
     url = url.replace(/\+/g, "%2B");
 
     if (AppConstants.platform == "gonk") {
       let sysLibs = {};
       Cu.import("resource://gre/modules/systemlibs.js", sysLibs);
+      let productDevice = sysLibs.libcutils.property_get("ro.product.device");
+      let buildType = sysLibs.libcutils.property_get("ro.build.type");
       url = url.replace(/%PRODUCT_MODEL%/g,
                         sysLibs.libcutils.property_get("ro.product.model"));
-      url = url.replace(/%PRODUCT_DEVICE%/g,
-                        sysLibs.libcutils.property_get("ro.product.device"));
+      if (buildType == "user") {
+        url = url.replace(/%PRODUCT_DEVICE%/g, productDevice);
+      } else {
+        url = url.replace(/%PRODUCT_DEVICE%/g, productDevice + "-" + buildType);
+      }
       url = url.replace(/%B2G_VERSION%/g,
                         getPref("getCharPref", PREF_APP_B2G_VERSION, null));
     }
 
     if (force) {
       url += (url.indexOf("?") != -1 ? "&" : "?") + "force=1";
     }
 
@@ -4209,17 +4212,17 @@ Downloader.prototype = {
       AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                                AUSTLMY.DWNLD_ERR_PATCH_SIZE_LARGER);
       this.cancel(Cr.NS_ERROR_UNEXPECTED);
       return;
     }
 
     if (maxProgress != this._patch.size) {
       LOG("Downloader:onProgress - maxProgress: " + maxProgress +
-          " is not equal to expectd patch size: " + this._patch.size);
+          " is not equal to expected patch size: " + this._patch.size);
       // It's important that we use a different code than
       // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
       // between a hash error and a wrong download error.
       AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                                AUSTLMY.DWNLD_ERR_PATCH_SIZE_NOT_EQUAL);
       this.cancel(Cr.NS_ERROR_UNEXPECTED);
       return;
     }