From 025492f274b37a291510410f5d3eb5c501ac6b1f Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Mon, 16 Dec 2013 18:53:17 +0000 Subject: [PATCH 01/36] Bug 949434 - Australis' overflowable toolbar might have race condition causing test failures, r=jaws --HG-- extra : rebase_source : 2af293e19a8f91c3cebc0dc24b617e67ae1534b2 --- .../components/customizableui/src/CustomizableUI.jsm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/browser/components/customizableui/src/CustomizableUI.jsm b/browser/components/customizableui/src/CustomizableUI.jsm index 1e96ebe4e0c3..9f3056711e83 100644 --- a/browser/components/customizableui/src/CustomizableUI.jsm +++ b/browser/components/customizableui/src/CustomizableUI.jsm @@ -3039,6 +3039,12 @@ function OverflowableToolbar(aToolbarNode) { this._enabled = true; this._toolbar.setAttribute("overflowable", "true"); + let doc = this._toolbar.ownerDocument; + this._target = this._toolbar.customizationTarget; + this._list = doc.getElementById(this._toolbar.getAttribute("overflowtarget")); + this._list.toolbox = this._toolbar.toolbox; + this._list.customizationTarget = this._list; + Services.obs.addObserver(this, "browser-delayed-startup-finished", false); } @@ -3055,12 +3061,7 @@ OverflowableToolbar.prototype = { }, init: function() { - this._target = this._toolbar.customizationTarget; let doc = this._toolbar.ownerDocument; - this._list = doc.getElementById(this._toolbar.getAttribute("overflowtarget")); - this._list.toolbox = this._toolbar.toolbox; - this._list.customizationTarget = this._list; - let window = doc.defaultView; window.addEventListener("resize", this); window.gNavToolbox.addEventListener("customizationstarting", this); From 47913a7b1a96540d9e524a7f0f8d1a3ac1c40828 Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Mon, 16 Dec 2013 11:44:20 +0000 Subject: [PATCH 02/36] Bug 923548 - fix Australis download glow assets, r=jaws --- .../linux/downloads/download-glow-small.png | Bin 556 -> 0 bytes .../themes/linux/downloads/download-glow.png | Bin 723 -> 3207 bytes browser/themes/linux/jar.mn | 1 - .../themes/osx/downloads/download-glow.png | Bin 570 -> 676 bytes .../themes/osx/downloads/download-glow@2x.png | Bin 1165 -> 1205 bytes 5 files changed, 1 deletion(-) delete mode 100644 browser/themes/linux/downloads/download-glow-small.png diff --git a/browser/themes/linux/downloads/download-glow-small.png b/browser/themes/linux/downloads/download-glow-small.png deleted file mode 100644 index 0dbf602c732d74b1382a98fb8dddff8edf88a9dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 556 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKVEpXq;uvD#pX_m8<@klKCs_K@ z|CyVyC$Y=ikcrgw(09J&7AeBK?7vyjnLBFbk{bWyC7X{p6rIR&tdb16etLm~!N8~^826>q6ZV8j9c`EwUZv+?xZ&8p?OF*uNpQ_V;hqa6 zF8p;mFRIa&$l$@F(!NG)$#liKoh)sP4u(9;4oNTA8on4E(GxvvzyMKlmtjI&z!phG zcTtbPD@uVNH42tnGOn__&aS=UH1WCM=a&D63tltwgJe!*EfVjvVR^Ncv2|C{v$C15 zHdr>iVyA!o*qL+y3{9kVuaPh*1q`09elF{r5}E+$VCT*N diff --git a/browser/themes/linux/downloads/download-glow.png b/browser/themes/linux/downloads/download-glow.png index 7514317fedf29c162dea25a9e3fdcde262c9e30e..749d4ac42bdfe20bed8ee47d04ae88e416786e2b 100644 GIT binary patch literal 3207 zcmV;240!X2P)4Tx07%E3mUmQC*A|D*y?1({%`gH|hTglt0MdJtUPWP;8DJ;_4l^{dA)*2i zMMRn+NKnLp(NH8-M6nPQRImpm2q-ZaMN}+rM%Ih2ti1Q~^84egZ|$@9x%=$B&srA% zlBX}1mj+7#kjfMAgFKw+5s^`J>;QlP9$S?PR%=$HTzo3l9?ED;xoI3-JvF1F8#m>QQXW*8-Az9>Nv%ZWK* zkqtikEV84R*{M9Xh{ZXlvs2k(?iKO2Od&_ah_8qXGr62B5#JKAMv5?%E8;ie*i;TP z0{|3BY!`4?i6S-;F^L}%f`(o2L0Dz>ZZyndax(`h}FNp#{ zx{a}MR#uh~m%}m=7xWMPPlvyuufAs_KJJh5&|Nw4Oks+EF0LCZEhSCJr)Q)ySsc3I zpNIG#2mW;)20@&74xhslMTCi_jLS<9wVTK03b<)JI+ypKn)naH{-njZ7KzgM5l~}{ zfYfy=Kz{89C<+lE(fh?+|D$id_%I-TdEqLPi*x_)H~nY9rQ#)noA5c#B`Ac>67n+_ z_r%Wu$9dISw03U@r;Pdb`_%=KWKZEBGfDjQHqKX(I48#TT zN1~8;gpaI8ijWGV0cl0Lkv`-mGK$O~Z&4T&1w}_0qHIx~s8AFOwFb2wRf4KU9Y%Ga zdQmq~W2jlwM>H9&h}K8jpuNx$=mc~Yx)5D~ZbG-CFQRXwC(y4k7z_=gjj_UbVj?j~ zn6;P^%sxyT<{V}aGme?VVzKgAeXJeUAIroFu!Yzv>{0Al>=1SW`vynEso>0T?zku% z50{Utz#YMz!42UiaSM1Uye8fT?~iBWbMU43MtnE^I(`DbK#(SA6YK~fge1ZyLM5S< zaFOtU@RCR*su8V;fkZBGBe9ZrjCh$iMtn<>A?cA^NYNxAX$R>L=^W`U=_Q#=)*?HS zqsRjC4stX30{Id7jRZx)NWx2kEwMqOMxsMvNaDF9UQ$!iNpiJhu4IMe3CZh{Gg5dd zEh!f%rqp_=8mW^~BT{qH6lqgwf9X`|66qt-SEQ$8urgXQZZd3{0-1v{7i7jM2t}RZ zLSa!hQyM83DHBu-Rh#NXO`;Z4zoQONXJut%m&u07X3N&do|YY@Av7(T7cGTWN;^&) zroCIDw8Uu%XUX;@txJZM%*!p6bCl!A70I>9-IjYNPnUO-PnO>$-zoo40i~d)5U7x) zuwUV#!pu_YQro4hrA14RFTJM-E9xl*DXvvKsMxPKr=+app_HyvrF21QMwzDUsGOu+ zu6#y$T7{xwufkO+S2?TllrBqmqNmU+>Amz>RYg@#RiSFV>VWEknzmY~TE1GF+Cz1M zIzv5Pys-#cBCZ~;MXm#GGH#)6 z)ozd6)!Y-@Tijj2>R4y()XvmDLKXQ&yjjk&I!+oQOrohQ}U>eb4k~HZbSnyy9x(W?3$*y{uH6t~>7#3G*6dj`%lF|oWk4CLGP(p*(a%)BP)E2$IF@Oj zS(EuDD=h0owsbZxyFW)SXM4_Mu6ypcYf)=iYkTrk^ETy;t#evezaCm2x4vhC`i6oH z6B|7?9^ORQl)UMue3SgL{8yX9H+L5(6>KaR-{P^QrBI@fUpTVWc5B@>)Hd$6f$iqo ztG0hEVi#R4HYu(seqX{Wx%!RiH@;dd*9H0 z$NjB!N_E9`?+$Pe+^P4d?`Y6!s5po@n0fF?V_0L~w~TL_n-rRgn?4-k9U46xbhx+K zs=4`y;*ru8xJB49eKh*$jqhB)>uNP@t#6~X6(0k~gvXwKAN&3Aai8NoCm1JMf6)A) zww=;m)B$zmbj)@pc8+#Mb`75NKH1Z4+ui=7(T|5tsh+AiEql834Bs>djZ*&hXA3QVUFm(Q=>&;8Iyl!2)z2f%ZaOm)zk?4`pJM24C zcT?`ZxR-fv;r_-4=m$j)r5;v1Qhe0#v+mDrqn4wm$6Uwy9|u3aKh7F|_DjYu?mT-%DP~ zzdZD6*{hzpfVoGnQ(rI47rl{xbNDUeZQr}_casZQ@3HSIKj?nw{^;}Z!Kc(upZ)~{ znDhK^CfpAI0093`OjJbx0000000000000000000004r4hBy9iy0000007i=!00000 z0000009BkEW2zhg000aO00000X00D=u^pZNF#rGnm;W!8|1_EZGhv<{U!WeC|1p>U zG)sLMN`M)e|1(K?7)*c|WV0G$uo{W!Dq*S^U#As;-Y8$G7hI_pT%{CQq7hP)5Lu!S zRh$lGwiRTx6kfOvT(}Kz#T{_P9a^^wSF{ROwF*_S2wkrbUbGNlwi08x6K1^>X}=b0 z!xwGF7;nfLdB8D*%Q%VHG>XwakL@I(=0&6DMx*gOr|3$m=}fQdQMB$>we(W9?^w3< zRKW9R$oF&q|NkDyeWd^Z06KJ1PE!B@0t5vH1_uZV3l0zx5)u>@78x2E8yhDmDl0EB zGc-I&N>^H2Y;JRWf{vP;ov^dJ^!NDr`uzOFcd)Hb8}N`v*J(~BHRD}W1XN-x~XxtX_M?BjIrmvoQDsN0B~p(h_@4f z1;9RUfzOd&VzU9_2>??l<2{+b^pk&(WnY07(l?mT7G4^`0k~K4*AeC3+TFH(Wa3jR zm+LzgI#sPBM8?5)pb#H!SvP?;^w(|#1?H)V&aRgJcJvftO6#f-Kb$`(B*p|mr+yej t3Yk^y$n)Kgm64RoC@D^aOIXs9;2U6UQ|=paGCTkP002ovPDHLkV1n%C@Bsh- literal 723 zcmV;^0xbQBP)yW&dNtAUTj4bbTaQkbhX_|M9O? z|4)1`M!_I4kQ~Jp_ACyCPnkQ~JpWbT&0 zX#q%%VhhrDisQ5ZBuB9YsoTVGS^$!x*n;FOqBt!8$x&=U;zkji7J%f4vH+-v3y5v7 z6$SC@gm78_lEYDafXw1Twt#KMKO@`t^+GRW*9iVc#vLczaasV9Lsy@$LFh5iJZm6k zMF}FHfKdEek*!@9Z83_4#UE`jEC7k2tLp^nO;{(g5onem);LI7E~f=_#hKolW?&6- zKbT<{jE?7jFbAsz>4{q_cqDC&q6XebPG2u?8MTuCO8-qOu*TW%jc^(cl7kp-{vW94 zQsyQFOMFtdZ!K+n|#~uf7lY<$C+E@LTWE+vXYb!fEu{cH_Idh z&*y$S^R6pON&>NG+;#pRyny>HNKL~vEpDtkB}fjch8L;_ zq?ZYw1t6DjKs5*fu{aQeWMsqoWjrD#%VtJSmCXpBB=5T4z;co&mkS_Gru zgHnnJ7K&ohHWA+x{{T@$G+=9k5`SRhU*JO{6hVCoJ_r>BLH`E9iuPHErT8L<)u{AG z?l#%G-F5b&NldOF*n{EjzM1*K zp^9{z2qDs#+GKj6J(60H83itTaDT+KPi* z@;6pM`W$TGEF(PJQG`=EeIiqSQKhqVaGzyFcK}3_eWgJx1@_Q!5Ns9XkB{z|5u@B0 zh3<(EP`P=ka`te1MsU^;+Y;bWD^S#J8WWK?DDJoyG6UBi7EeBD69rR{K7W|UZo3br zvyaXlaBg@jON=uY6{$c~%M@_tfcLfPA!aQBAl89)9Z8OfHIdR=}-$Xma%@NSl30}8o;TX)W< zUy|%h6S|ai&O*8vc0zEH79g2EvAchxU)m&HtNRPBYlLjo#r8{p0RUoq9sh_rqYEx#e!^-r|xzgTd;c>Qz9g5y%P_oX_%OD8Or z&eue8UoZnBqlBcitem`}vT9y_@5D({rc9qcbJnc6^YYDB8;us+Z8E9LYP(Vozi{#r2%Zv$0INdNRlF%(0^rMD@RAq@MU_W3IuXTd~>IYi`{7xt}-x v*ueIwx9ZyIhC{!MGW#XtC8l3)nJ>pYX?r8OPx)_YrOILWU~+H>#uobT^<&hK{)VT{36 zY65JrX5k;2rfn^tQktW8u%zdek)hIn9(?(e$1lx9KU!143xt5hR+ZZc!?Mx>;XU5Q z*GI?xK2uhO#==WOmf9At&F(;+{2|B~1vRPMITne2fSt~OiG^bt$m-nI=-PQNRMfbD z2x5L9s=*EA8Z7=b>Js7!plYgoKz5fTI3yofZ8gAMmcKCsRv@_;7FJ`k_99w zpg{Ck$=?X}snq)ds>sS2Llgvo^4MX8`VkBrQvPMHQDWl*QCa|qrkPopf%wcR7*L3< z8#Bpd8i-*aYX(NpVVHT9%meA}DjXZ!Wt9CNF2ezgD8#Z9Vr2)S27st(af);q7*{A0 zN9E1-qTP@}!1o}Fecffn26f-@lj87i6Td4d(#aq{ z$p>D|(UlkZ*(IzfimhJ`|Ji>0r=_oN)cGXA<^&*{RC+TFw8S-VDic$m`Mv&!_ex+jiD8qs+71v?p6VL7BF!Ov2@xW2d&@l+1__ByRbHF?E z@l#EK>gCW3mzrWU>nXvEp#Z9~1of-(_^B6sf!^I74igc3j^YeeRL#4S-!?GP{*64ge|zMs0k6MS zktcPfaG*?iaZAHTHH2{|<%{1R-rc|NF4t9j*JYt+5M@@2Ke=Y09=%(dJNfL6epf=4 zH4M(%LUEYH0(g?43ulk)2_j5vT^SCem41}YKt0seG=KU*E)w5(S#NzHX~mpfwd59X_`2`8JPZ z5&O=Oj57IX@(|^0jD`}vDDot?eR6K)j=oRNzZ+Vqz8{yk(B(?}diu5d-fn5A;xO~X zfSc5FL!|>{K}l%U8}AQ{!fYx~7E}XTP5P%5v;}-$(YIhF0hObXt#+mNuK)u86V|pV T?v1<;00000NkvXXu0mjfhHFY3 literal 1165 zcmV;81akX{P)&r9vqRT6hNA4KW>c7 z8Rh13Yg`G8-jH5m2+TN%pxvhtyyuMFxG`>yU)mu9x5lMFCbQvTL`f&2YW0Y!>$e{n zsc$0uGCBdVtO9 zb2gwS0r6T&;6`-|;!c5DY?B$AR6n{Ns35@D&p|Dw(TwnA0jgRM(*$Z!C(YQXTn1pM zvIS2!f?8y~8H$`4pm@7dz;6kG!8t96Yyh>bC(H=jw+z6beJzNn2el`Un-P+|3_uwH zZaD#JPaHENcoyhEK)jp;6xu18A;8U_f!bq7&5+Lm?d|obz{ef^I_d5kN?_E#Ap98U z9eIc#Rhtb+DP*BQ(WvQO?E2Ka9&u8>MVwp!eua?ks0UMc6N;mtcZ3CVup5$>kI$~> z=b4nxGU-K^IOIZs!l~&8G3(N_ebQGO;hpg{ymy|N*EjHe|2*W{VT67PdWWr$l^U>q z?>TNgzl_t6yi$+Am&$c?Ais!jFWz{M$vSJ?aG(bPMSmioxPPv|vSG+;rxA1*^axO1 zGljs?A>JMVMSA${??On{cly?@fednH7zN1BVzIo?+;JyMuIU&Fs2B%RX9e>KoKu|z z{#p|h`*r%Oe;9X@vyvD6E(YL17QT4)>fm0PrsHov?HK%P$;Nf~f7P<^D>ETXqq}`g zZ+eTIkqSUW13E&cpTA~!m&v{ImrvOkd}}Q5t;M5N!@{TZEa(%tqfx(lxg7G9OG zUpWiULJs$H*TCq+PO>&}9?r=?J~D;7M>19WO^-q&!~`O)06#4YkAe||sarc|aS1cH zTn^?0ARU?9J2<{erfk3AUSNbf0lAlBrZN_yd=A(4o53{sI++^>Kq!p2;hY4dBi+cs z&-RgfCC_Q?7Uc~=R5Cr&0Rt4Oiyg*$Q?HRZ+c=zn-~ Date: Wed, 18 Dec 2013 16:50:05 +0100 Subject: [PATCH 03/36] Bug 930641 - Remove the social.allowMultipleWorkers preference, r=mixedpuppy. --- browser/app/profile/firefox.js | 2 -- browser/base/content/browser-social.js | 8 ----- .../test/social/browser_social_chatwindow.js | 3 -- .../test/social/browser_social_marks.js | 2 -- .../test/social/browser_social_multiworker.js | 35 +------------------ .../test/social/browser_social_status.js | 2 -- .../test/social/browser_social_workercrash.js | 2 -- browser/modules/Social.jsm | 13 ------- .../modules/test/unit/social/test_social.js | 4 --- .../unit/social/test_socialDisabledStartup.js | 4 --- toolkit/components/social/FrameWorker.jsm | 7 +--- .../test/browser/browser_SocialProvider.js | 3 +- 12 files changed, 3 insertions(+), 82 deletions(-) diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index b1168b6dc814..de31be3d9367 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1304,8 +1304,6 @@ pref("social.manifest.facebook", "{\"origin\":\"https://www.facebook.com\",\"nam pref("social.sidebar.open", true); pref("social.sidebar.unload_timeout_ms", 10000); -pref("social.allowMultipleWorkers", true); - pref("dom.identity.enabled", false); // Turn on the CSP 1.0 parser for Content Security Policy headers diff --git a/browser/base/content/browser-social.js b/browser/base/content/browser-social.js index 6dd03af0869c..49213069d60c 100644 --- a/browser/base/content/browser-social.js +++ b/browser/base/content/browser-social.js @@ -1031,14 +1031,10 @@ let SocialStatusWidgetListener = { SocialStatus = { populateToolbarPalette: function() { - if (!Social.allowMultipleWorkers) - return; this._toolbarHelper.populatePalette(); }, removeProvider: function(origin) { - if (!Social.allowMultipleWorkers) - return; this._removeFrame(origin); this._toolbarHelper.removeProviderButton(origin); }, @@ -1124,8 +1120,6 @@ SocialStatus = { }, updateButton: function(origin) { - if (!Social.allowMultipleWorkers) - return; let id = this._toolbarHelper.idFromOrigin(origin); let widget = CustomizableUI.getWidget(id); if (!widget) @@ -1165,8 +1159,6 @@ SocialStatus = { }, showPopup: function(aToolbarButton) { - if (!Social.allowMultipleWorkers) - return; // attach our notification panel if necessary let origin = aToolbarButton.getAttribute("origin"); let provider = Social._getProviderFromOrigin(origin); diff --git a/browser/base/content/test/social/browser_social_chatwindow.js b/browser/base/content/test/social/browser_social_chatwindow.js index a4809f0653a6..35f8ece4f43d 100644 --- a/browser/base/content/test/social/browser_social_chatwindow.js +++ b/browser/base/content/test/social/browser_social_chatwindow.js @@ -63,15 +63,12 @@ function test() { ok(chats.children.length == 0, "no chatty children left behind"); cb(); }; - // always run chat tests with multiple workers. - Services.prefs.setBoolPref("social.allowMultipleWorkers", true); runSocialTestWithProvider(manifests, function (finishcb) { ok(Social.enabled, "Social is enabled"); ok(Social.providers[0].getWorkerPort(), "provider 0 has port"); ok(Social.providers[1].getWorkerPort(), "provider 1 has port"); ok(Social.providers[2].getWorkerPort(), "provider 2 has port"); runSocialTests(tests, undefined, postSubTest, function() { - Services.prefs.clearUserPref("social.allowMultipleWorkers"); window.moveTo(oldleft, window.screenY) window.resizeTo(oldwidth, window.outerHeight); finishcb(); diff --git a/browser/base/content/test/social/browser_social_marks.js b/browser/base/content/test/social/browser_social_marks.js index 35dd1446b05d..97d4820700dd 100644 --- a/browser/base/content/test/social/browser_social_marks.js +++ b/browser/base/content/test/social/browser_social_marks.js @@ -55,14 +55,12 @@ function openWindowAndWaitForInit(callback) { function test() { waitForExplicitFinish(); - Services.prefs.setBoolPref("social.allowMultipleWorkers", true); let toolbar = document.getElementById("nav-bar"); let currentsetAtStart = toolbar.currentSet; runSocialTestWithProvider(manifest, function () { runSocialTests(tests, undefined, undefined, function () { Services.prefs.clearUserPref("social.remote-install.enabled"); // just in case the tests failed, clear these here as well - Services.prefs.clearUserPref("social.allowMultipleWorkers"); Services.prefs.clearUserPref("social.whitelist"); ok(CustomizableUI.inDefaultState, "Should be in the default state when we finish"); CustomizableUI.reset(); diff --git a/browser/base/content/test/social/browser_social_multiworker.js b/browser/base/content/test/social/browser_social_multiworker.js index 452ab98bf961..c5731f9fb7e7 100644 --- a/browser/base/content/test/social/browser_social_multiworker.js +++ b/browser/base/content/test/social/browser_social_multiworker.js @@ -5,11 +5,9 @@ function test() { waitForExplicitFinish(); - Services.prefs.setBoolPref("social.allowMultipleWorkers", true); runSocialTestWithProvider(gProviders, function (finishcb) { Social.enabled = true; runSocialTests(tests, undefined, undefined, function() { - Services.prefs.clearUserPref("social.allowMultipleWorkers"); finishcb(); }); }); @@ -58,6 +56,7 @@ var tests = { waitForCondition(function() messageReceived == Social.providers.length, next, "received messages from all workers"); }, + testWorkerDisabling: function(next) { Social.enabled = false; is(Social.providers.length, gProviders.length, "providers still available"); @@ -66,37 +65,5 @@ var tests = { ok(!p.getWorkerPort(), "worker disabled"); } next(); - }, - - testSingleWorkerEnabling: function(next) { - // test that only one worker is enabled when we limit workers - Services.prefs.setBoolPref("social.allowMultipleWorkers", false); - Social.enabled = true; - for (let p of Social.providers) { - if (p == Social.provider) { - ok(p.enabled, "primary provider enabled"); - let port = p.getWorkerPort(); - ok(port, "primary worker enabled"); - port.close(); - } else { - ok(!p.enabled, "secondary provider is not enabled"); - ok(!p.getWorkerPort(), "secondary worker disabled"); - } - } - next(); - }, - - testMultipleWorkerEnabling: function(next) { - // test that all workers are enabled when we allow multiple workers - Social.enabled = false; - Services.prefs.setBoolPref("social.allowMultipleWorkers", true); - Social.enabled = true; - for (let p of Social.providers) { - ok(p.enabled, "provider enabled"); - let port = p.getWorkerPort(); - ok(port, "worker enabled"); - port.close(); - } - next(); } } diff --git a/browser/base/content/test/social/browser_social_status.js b/browser/base/content/test/social/browser_social_status.js index f9fcdc49c9d5..3e59e2dc0d55 100644 --- a/browser/base/content/test/social/browser_social_status.js +++ b/browser/base/content/test/social/browser_social_status.js @@ -40,12 +40,10 @@ function openWindowAndWaitForInit(callback) { function test() { waitForExplicitFinish(); - Services.prefs.setBoolPref("social.allowMultipleWorkers", true); runSocialTestWithProvider(manifest, function (finishcb) { runSocialTests(tests, undefined, undefined, function () { Services.prefs.clearUserPref("social.remote-install.enabled"); // just in case the tests failed, clear these here as well - Services.prefs.clearUserPref("social.allowMultipleWorkers"); Services.prefs.clearUserPref("social.whitelist"); ok(CustomizableUI.inDefaultState, "Should be in the default state when we finish"); CustomizableUI.reset(); diff --git a/browser/base/content/test/social/browser_social_workercrash.js b/browser/base/content/test/social/browser_social_workercrash.js index dda4cd73d1e5..d957c433ec02 100644 --- a/browser/base/content/test/social/browser_social_workercrash.js +++ b/browser/base/content/test/social/browser_social_workercrash.js @@ -13,7 +13,6 @@ let {getFrameWorkerHandle} = Cu.import("resource://gre/modules/FrameWorker.jsm", function test() { waitForExplicitFinish(); - Services.prefs.setBoolPref("social.allowMultipleWorkers", true); // We need to ensure all our workers are in the same content process. Services.prefs.setIntPref("dom.ipc.processCount", 1); @@ -22,7 +21,6 @@ function test() { runSocialTests(tests, undefined, undefined, function() { Services.prefs.clearUserPref("dom.ipc.processCount"); Services.prefs.clearUserPref("social.sidebar.open"); - Services.prefs.clearUserPref("social.allowMultipleWorkers"); finishcb(); }); }); diff --git a/browser/modules/Social.jsm b/browser/modules/Social.jsm index 4f3be99192be..3e79873dfc36 100644 --- a/browser/modules/Social.jsm +++ b/browser/modules/Social.jsm @@ -98,10 +98,6 @@ this.Social = { providers: [], _disabledForSafeMode: false, - get allowMultipleWorkers() { - return Services.prefs.getBoolPref("social.allowMultipleWorkers"); - }, - get _currentProviderPref() { try { return Services.prefs.getComplexValue("social.provider.current", @@ -130,11 +126,6 @@ this.Social = { if (this._provider == provider) return; - // Disable the previous provider, if we are not allowing multiple workers, - // since we want only one provider to be enabled at once. - if (this._provider && !Social.allowMultipleWorkers) - this._provider.enabled = false; - this._provider = provider; if (this._provider) { @@ -208,10 +199,6 @@ this.Social = { }, _updateWorkerState: function(enable) { - // ensure that our providers are all disabled, and enabled if we allow - // multiple workers - if (enable && !Social.allowMultipleWorkers) - return; [p.enabled = enable for (p of Social.providers) if (p.enabled != enable)]; }, diff --git a/browser/modules/test/unit/social/test_social.js b/browser/modules/test/unit/social/test_social.js index 0152f43e95fb..4e2471e19b41 100644 --- a/browser/modules/test/unit/social/test_social.js +++ b/browser/modules/test/unit/social/test_social.js @@ -4,10 +4,6 @@ function run_test() { // we are testing worker startup specifically - Services.prefs.setBoolPref("social.allowMultipleWorkers", true); - do_register_cleanup(function() { - Services.prefs.clearUserPref("social.allowMultipleWorkers"); - }); do_test_pending(); add_test(testStartupEnabled); add_test(testDisableAfterStartup); diff --git a/browser/modules/test/unit/social/test_socialDisabledStartup.js b/browser/modules/test/unit/social/test_socialDisabledStartup.js index b6b2f0cbf69d..a49c5521ce63 100644 --- a/browser/modules/test/unit/social/test_socialDisabledStartup.js +++ b/browser/modules/test/unit/social/test_socialDisabledStartup.js @@ -4,10 +4,6 @@ function run_test() { // we are testing worker startup specifically - Services.prefs.setBoolPref("social.allowMultipleWorkers", true); - do_register_cleanup(function() { - Services.prefs.clearUserPref("social.allowMultipleWorkers"); - }); do_test_pending(); add_test(testStartupDisabled); add_test(testEnableAfterStartup); diff --git a/toolkit/components/social/FrameWorker.jsm b/toolkit/components/social/FrameWorker.jsm index 82cafca019b2..06a86497e76b 100644 --- a/toolkit/components/social/FrameWorker.jsm +++ b/toolkit/components/social/FrameWorker.jsm @@ -199,12 +199,7 @@ function makeRemoteBrowser() { let browser = iframe.contentDocument.createElementNS(XUL_NS, "browser"); browser.setAttribute("type", "content"); browser.setAttribute("disableglobalhistory", "true"); - // for now we use the same preference that enabled multiple workers - the - // idea is that there is no point in having people help test multiple - // "old" frameworkers - so anyone who wants multiple workers is forced to - // help us test remote frameworkers too. - if (Services.prefs.getBoolPref("social.allowMultipleWorkers")) - browser.setAttribute("remote", "true"); + browser.setAttribute("remote", "true"); iframe.contentDocument.documentElement.appendChild(browser); deferred.resolve(browser); diff --git a/toolkit/components/social/test/browser/browser_SocialProvider.js b/toolkit/components/social/test/browser/browser_SocialProvider.js index 5cd5931a1978..bd7f42463a60 100644 --- a/toolkit/components/social/test/browser/browser_SocialProvider.js +++ b/toolkit/components/social/test/browser/browser_SocialProvider.js @@ -62,8 +62,7 @@ let tests = { }; SocialService.addProvider(manifest, function (provider2) { ok(provider.enabled, "provider is initially enabled"); - is(provider2.enabled, Services.prefs.getBoolPref("social.allowMultipleWorkers"), "provider2 is enabled status is correct"); - provider2.enabled = true; + ok(provider2.enabled, "provider2 is initially enabled"); let port = provider.getWorkerPort(); let port2 = provider2.getWorkerPort(); ok(port, "have port for provider"); From bf3f49a76b249bc4a1d520e9d27074f9d982e8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Qu=C3=A8ze?= Date: Wed, 18 Dec 2013 16:51:27 +0100 Subject: [PATCH 04/36] Bug 951260 - Social Status button icon stretched on retina screen, r=mixedpuppy. --- browser/base/content/browser.css | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index cdc32684720d..533d45fc69ba 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -747,18 +747,6 @@ toolbarpaletteitem[place="palette"] > toolbarbutton[type="badged"] > .toolbarbut max-height: 32px; } -@media (min-resolution: 2dppx) { - toolbarbutton[type="badged"] > .toolbarbutton-badge-container > .toolbarbutton-icon, - toolbarbutton[type="socialmark"] > .toolbarbutton-icon { - max-width: 32px; - max-height: 32px; - } - toolbarpaletteitem[place="palette"] > toolbarbutton[type="badged"] > .toolbarbutton-badge-container > .toolbarbutton-icon { - max-width: 64px; - max-height: 64px; - } -} - panelview > .social-panel-frame { width: auto; height: auto; From bff4722c5e9026ce0672dcba3788ed56145dada6 Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Wed, 18 Dec 2013 19:01:38 +0200 Subject: [PATCH 05/36] Bug 950656 - The separator string between the headers name and value isn't shown, r=past --- .../test/browser_dbg_variables-view-05.js | 20 +++++++++++++++++++ .../devtools/netmonitor/netmonitor-view.js | 3 +-- .../devtools/shared/widgets/VariablesView.jsm | 3 ++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/browser/devtools/debugger/test/browser_dbg_variables-view-05.js b/browser/devtools/debugger/test/browser_dbg_variables-view-05.js index 4881d960b66a..4f64e49375a7 100644 --- a/browser/devtools/debugger/test/browser_dbg_variables-view-05.js +++ b/browser/devtools/debugger/test/browser_dbg_variables-view-05.js @@ -17,15 +17,35 @@ function test() { ok(globalScope, "The globalScope hasn't been created correctly."); ok(localScope, "The localScope hasn't been created correctly."); + is(globalScope.target.querySelector(".separator"), null, + "No separator string should be created for scopes (1)."); + is(localScope.target.querySelector(".separator"), null, + "No separator string should be created for scopes (2)."); + let windowVar = globalScope.addItem("window"); let documentVar = globalScope.addItem("document"); ok(windowVar, "The windowVar hasn't been created correctly."); ok(documentVar, "The documentVar hasn't been created correctly."); + ok(windowVar.target.querySelector(".separator").hidden, + "No separator string should be shown for variables without a grip (1)."); + ok(documentVar.target.querySelector(".separator").hidden, + "No separator string should be shown for variables without a grip (2)."); + windowVar.setGrip({ type: "object", class: "Window" }); documentVar.setGrip({ type: "object", class: "HTMLDocument" }); + is(windowVar.target.querySelector(".separator").hidden, false, + "A separator string should now be shown after setting the grip (1)."); + is(documentVar.target.querySelector(".separator").hidden, false, + "A separator string should now be shown after setting the grip (2)."); + + is(windowVar.target.querySelector(".separator").getAttribute("value"), ": ", + "The separator string label is correct (1)."); + is(documentVar.target.querySelector(".separator").getAttribute("value"), ": ", + "The separator string label is correct (2)."); + let localVar0 = localScope.addItem("localVar0"); let localVar1 = localScope.addItem("localVar1"); let localVar2 = localScope.addItem("localVar2"); diff --git a/browser/devtools/netmonitor/netmonitor-view.js b/browser/devtools/netmonitor/netmonitor-view.js index 974a5cfdac69..c007fe42c7f2 100644 --- a/browser/devtools/netmonitor/netmonitor-view.js +++ b/browser/devtools/netmonitor/netmonitor-view.js @@ -1750,8 +1750,7 @@ NetworkDetailsView.prototype = { return promise.all(aResponse.headers.map(header => { let headerVar = headersScope.addItem(header.name, {}, true); - return gNetwork.getString(header.value) - .then(aString => headerVar.setGrip(aString)); + return gNetwork.getString(header.value).then(aString => headerVar.setGrip(aString)); })); }, diff --git a/browser/devtools/shared/widgets/VariablesView.jsm b/browser/devtools/shared/widgets/VariablesView.jsm index 81ea00ed3939..06c08522a3c1 100644 --- a/browser/devtools/shared/widgets/VariablesView.jsm +++ b/browser/devtools/shared/widgets/VariablesView.jsm @@ -2371,6 +2371,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, { this._valueLabel.classList.add(this._valueClassName); this._valueLabel.setAttribute("value", this._valueString); + this._separatorLabel.hidden = false; }, /** @@ -2454,7 +2455,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, { let separatorLabel = this._separatorLabel = document.createElement("label"); separatorLabel.className = "plain separator"; - separatorLabel.setAttribute("value", this.ownerView.separatorStr + " "); + separatorLabel.setAttribute("value", this.separatorStr + " "); let valueLabel = this._valueLabel = document.createElement("label"); valueLabel.className = "plain value"; From 251fc8c8eaeb698543f9c588ef020a35b9df882e Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Wed, 18 Dec 2013 19:01:38 +0200 Subject: [PATCH 06/36] Bug 830344 - Part 1: Remove the lazy append mechanism in the variables view, r=past --- ...r_dbg_variables-view-large-array-buffer.js | 186 --------------- browser/devtools/debugger/test/head.js | 2 - .../devtools/shared/widgets/VariablesView.jsm | 221 +++--------------- browser/devtools/webconsole/test/head.js | 1 - browser/devtools/webconsole/webconsole.js | 1 - 5 files changed, 30 insertions(+), 381 deletions(-) diff --git a/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js b/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js index c3a22a1904aa..8f4fc2f78052 100644 --- a/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js +++ b/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js @@ -12,10 +12,6 @@ let gTab, gDebuggee, gPanel, gDebugger; let gVariables; function test() { - // This is a very, very stressful test. - // Thankfully, after bug 830344 none of this will be necessary anymore. - requestLongerTimeout(10); - initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => { gTab = aTab; gDebuggee = aDebuggee; @@ -23,8 +19,6 @@ function test() { gDebugger = gPanel.panelWin; gVariables = gDebugger.DebuggerView.Variables; - gDebugger.DebuggerView.Variables.lazyAppend = true; - waitForSourceAndCaretAndScopes(gPanel, ".html", 18) .then(() => performTest()) .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) @@ -39,187 +33,7 @@ function test() { } function performTest() { - let deferred = promise.defer(); - let localScope = gVariables.getScopeAtIndex(0); - is(localScope.expanded, true, - "The local scope should be expanded by default."); - - let localEnums = localScope.target.querySelector(".variables-view-element-details.enum").childNodes; - let localNonEnums = localScope.target.querySelector(".variables-view-element-details.nonenum").childNodes; - - is(localEnums.length, 5, - "The local scope should contain all the created enumerable elements."); - is(localNonEnums.length, 0, - "The local scope should contain all the created non-enumerable elements."); - - let bufferVar = localScope.get("buffer"); - let zVar = localScope.get("z"); - - is(bufferVar.target.querySelector(".name").getAttribute("value"), "buffer", - "Should have the right property name for 'buffer'."); - is(bufferVar.target.querySelector(".value").getAttribute("value"), "ArrayBuffer", - "Should have the right property value for 'buffer'."); - ok(bufferVar.target.querySelector(".value").className.contains("token-other"), - "Should have the right token class for 'buffer'."); - - is(zVar.target.querySelector(".name").getAttribute("value"), "z", - "Should have the right property name for 'z'."); - is(zVar.target.querySelector(".value").getAttribute("value"), "Int8Array", - "Should have the right property value for 'z'."); - ok(zVar.target.querySelector(".value").className.contains("token-other"), - "Should have the right token class for 'z'."); - - EventUtils.sendMouseEvent({ type: "mousedown" }, - bufferVar.target.querySelector(".arrow"), - gDebugger); - - EventUtils.sendMouseEvent({ type: "mousedown" }, - zVar.target.querySelector(".arrow"), - gDebugger); - - // Need to wait for 0 enumerable and 2 non-enumerable properties in bufferVar, - // and 10000 enumerable and 5 non-enumerable properties in zVar. - let total = 0 + 2 + 10000 + 5; - let loaded = 0; - let paints = 0; - - // Make sure the variables view doesn't scroll while adding the properties. - let [oldX, oldY] = getScroll(); - info("Initial scroll position: " + oldX + ", " + oldY); - - waitForProperties(total, { - onLoading: function(aLoaded) { - ok(aLoaded >= loaded, - "Should have loaded more properties."); - - let [newX, newY] = getScroll(); - info("Current scroll position: " + newX + " " + newY); - is(oldX, newX, "The variables view hasn't scrolled horizontally."); - is(oldY, newY, "The variables view hasn't scrolled vertically."); - - info("Displayed " + aLoaded + " properties, not finished yet."); - - loaded = aLoaded; - paints++; - }, - onFinished: function(aLoaded) { - ok(aLoaded == total, - "Displayed all the properties."); - isnot(paints, 0, - "Debugger was unresponsive, sad panda."); - - let [newX, newY] = getScroll(); - info("Current scroll position: " + newX + ", " + newY); - is(oldX, newX, "The variables view hasn't scrolled horizontally."); - is(oldY, newY, "The variables view hasn't scrolled vertically."); - - is(bufferVar._enum.childNodes.length, 0, - "The bufferVar should contain all the created enumerable elements."); - is(bufferVar._nonenum.childNodes.length, 2, - "The bufferVar should contain all the created non-enumerable elements."); - - let bufferVarByteLengthProp = bufferVar.get("byteLength"); - let bufferVarProtoProp = bufferVar.get("__proto__"); - - is(bufferVarByteLengthProp.target.querySelector(".name").getAttribute("value"), "byteLength", - "Should have the right property name for 'byteLength'."); - is(bufferVarByteLengthProp.target.querySelector(".value").getAttribute("value"), "10000", - "Should have the right property value for 'byteLength'."); - ok(bufferVarByteLengthProp.target.querySelector(".value").className.contains("token-number"), - "Should have the right token class for 'byteLength'."); - - is(bufferVarProtoProp.target.querySelector(".name").getAttribute("value"), "__proto__", - "Should have the right property name for '__proto__'."); - is(bufferVarProtoProp.target.querySelector(".value").getAttribute("value"), "ArrayBufferPrototype", - "Should have the right property value for '__proto__'."); - ok(bufferVarProtoProp.target.querySelector(".value").className.contains("token-other"), - "Should have the right token class for '__proto__'."); - - is(zVar._enum.childNodes.length, 10000, - "The zVar should contain all the created enumerable elements."); - is(zVar._nonenum.childNodes.length, 5, - "The zVar should contain all the created non-enumerable elements."); - - let zVarByteLengthProp = zVar.get("byteLength"); - let zVarByteOffsetProp = zVar.get("byteOffset"); - let zVarProtoProp = zVar.get("__proto__"); - - is(zVarByteLengthProp.target.querySelector(".name").getAttribute("value"), "byteLength", - "Should have the right property name for 'byteLength'."); - is(zVarByteLengthProp.target.querySelector(".value").getAttribute("value"), "10000", - "Should have the right property value for 'byteLength'."); - ok(zVarByteLengthProp.target.querySelector(".value").className.contains("token-number"), - "Should have the right token class for 'byteLength'."); - - is(zVarByteOffsetProp.target.querySelector(".name").getAttribute("value"), "byteOffset", - "Should have the right property name for 'byteOffset'."); - is(zVarByteOffsetProp.target.querySelector(".value").getAttribute("value"), "0", - "Should have the right property value for 'byteOffset'."); - ok(zVarByteOffsetProp.target.querySelector(".value").className.contains("token-number"), - "Should have the right token class for 'byteOffset'."); - - is(zVarProtoProp.target.querySelector(".name").getAttribute("value"), "__proto__", - "Should have the right property name for '__proto__'."); - is(zVarProtoProp.target.querySelector(".value").getAttribute("value"), "Int8ArrayPrototype", - "Should have the right property value for '__proto__'."); - ok(zVarProtoProp.target.querySelector(".value").className.contains("token-other"), - "Should have the right token class for '__proto__'."); - - let arrayElements = zVar._enum.childNodes; - for (let i = 0, len = arrayElements.length; i < len; i++) { - let node = arrayElements[i]; - let name = node.querySelector(".name").getAttribute("value"); - let value = node.querySelector(".value").getAttribute("value"); - if (name !== i + "" || value !== "0") { - ok(false, "The array items aren't in the correct order."); - } - } - - deferred.resolve(); - }, - onTimeout: function() { - ok(false, "Timed out while polling for the properties."); - deferred.resolve(); - } - }); - - function getScroll() { - let scrollX = {}; - let scrollY = {}; - gVariables.boxObject.getPosition(scrollX, scrollY); - return [scrollX.value, scrollY.value]; - } - - return deferred.promise; -} - -function waitForProperties(aTotal, aCallbacks, aInterval = 10) { - let localScope = gVariables.getScopeAtIndex(0); - let bufferEnum = localScope.get("buffer")._enum.childNodes; - let bufferNonEnum = localScope.get("buffer")._nonenum.childNodes; - let zEnum = localScope.get("z")._enum.childNodes; - let zNonEnum = localScope.get("z")._nonenum.childNodes; - - // Poll every few milliseconds until the properties are retrieved. - let count = 0; - let intervalId = window.setInterval(() => { - // Make sure we don't wait for too long. - if (++count > 1000) { - window.clearInterval(intervalId); - aCallbacks.onTimeout(); - return; - } - // Check if we need to wait for a few more properties to be fetched. - let loaded = bufferEnum.length + bufferNonEnum.length + zEnum.length + zNonEnum.length; - if (loaded < aTotal) { - aCallbacks.onLoading(loaded); - return; - } - // We got all the properties, it's safe to callback. - window.clearInterval(intervalId); - aCallbacks.onFinished(loaded); - }, aInterval); } registerCleanupFunction(function() { diff --git a/browser/devtools/debugger/test/head.js b/browser/devtools/debugger/test/head.js index 3350f9509eb3..0662579f5433 100644 --- a/browser/devtools/debugger/test/head.js +++ b/browser/devtools/debugger/test/head.js @@ -488,8 +488,6 @@ function prepareDebugger(aDebugger) { if ("target" in aDebugger) { let variables = aDebugger.panelWin.DebuggerView.Variables; variables.lazyEmpty = false; - variables.lazyAppend = false; - variables.lazyExpand = false; variables.lazySearch = false; } else { // Nothing to do here yet. diff --git a/browser/devtools/shared/widgets/VariablesView.jsm b/browser/devtools/shared/widgets/VariablesView.jsm index 06c08522a3c1..f1931c068885 100644 --- a/browser/devtools/shared/widgets/VariablesView.jsm +++ b/browser/devtools/shared/widgets/VariablesView.jsm @@ -11,8 +11,6 @@ const Cu = Components.utils; const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties"; const LAZY_EMPTY_DELAY = 150; // ms const LAZY_EXPAND_DELAY = 50; // ms -const LAZY_APPEND_DELAY = 100; // ms -const LAZY_APPEND_BATCH = 100; // nodes const PAGE_SIZE_SCROLL_HEIGHT_RATIO = 100; const PAGE_SIZE_MAX_JUMPS = 30; const SEARCH_ACTION_MAX_DELAY = 300; // ms @@ -225,18 +223,6 @@ VariablesView.prototype = { */ lazyEmpty: false, - /** - * Specifies if nodes in this view may be added lazily. - * @see Scope.prototype._lazyAppend - */ - lazyAppend: true, - - /** - * Specifies if nodes in this view may be expanded lazily. - * @see Scope.prototype.expand - */ - lazyExpand: true, - /** * Specifies if nodes in this view may be searched lazily. */ @@ -1201,7 +1187,6 @@ function Scope(aView, aName, aFlags = {}) { this._onClick = this._onClick.bind(this); this._openEnum = this._openEnum.bind(this); this._openNonEnum = this._openNonEnum.bind(this); - this._batchAppend = this._batchAppend.bind(this); // Inherit properties and flags from the parent view. You can override // each of these directly onto any scope, variable or property instance. @@ -1227,6 +1212,11 @@ Scope.prototype = { */ shouldPrefetch: true, + /** + * The class name applied to this scope's target element. + */ + targetClassName: "variables-view-scope", + /** * Create a new Variable that is a child of this Scope. * @@ -1259,8 +1249,9 @@ Scope.prototype = { * - { value: { type: "object", class: "Object" } } * - { get: { type: "object", class: "Function" }, * set: { type: "undefined" } } - * @param boolean aRelaxed - * True if name duplicates should be allowed. + * @param boolean aRelaxed [optional] + * Pass true if name duplicates should be allowed. + * You probably shouldn't do it. Use this with caution. * @return Variable * The newly created Variable instance, null if it already exists. */ @@ -1432,32 +1423,15 @@ Scope.prototype = { * Expands the scope, showing all the added details. */ expand: function() { - if (this._isExpanded || this._locked) { + if (this._isExpanded || this._isLocked) { return; } - // If there's a large number of enumerable or non-enumerable items - // contained in this scope, painting them may take several seconds, - // even if they were already displayed before. In this case, show a throbber - // to suggest that this scope is expanding. - if (!this._isExpanding && - this._variablesView.lazyExpand && - this._store.size > LAZY_APPEND_BATCH) { - this._isExpanding = true; - - // Start spinning a throbber in this scope's title and allow a few - // milliseconds for it to be painted. - this._startThrobber(); - this.window.setTimeout(this.expand.bind(this), LAZY_EXPAND_DELAY); - return; - } - if (this._variablesView._enumVisible) { this._openEnum(); } if (this._variablesView._nonEnumVisible) { Services.tm.currentThread.dispatch({ run: this._openNonEnum }, 0); } - this._isExpanding = false; this._isExpanded = true; if (this.onexpand) { @@ -1469,7 +1443,7 @@ Scope.prototype = { * Collapses the scope, hiding all the added details. */ collapse: function() { - if (!this._isExpanded || this._locked) { + if (!this._isExpanded || this._isLocked) { return; } this._arrow.removeAttribute("open"); @@ -1576,7 +1550,7 @@ Scope.prototype = { * Gets the expand lock state. * @return boolean */ - get locked() this._locked, + get locked() this._isLocked, /** * Sets the visibility state. @@ -1606,7 +1580,7 @@ Scope.prototype = { * Sets the expand lock state. * @param boolean aFlag */ - set locked(aFlag) this._locked = aFlag, + set locked(aFlag) this._isLocked = aFlag, /** * Specifies if this target node may be focused. @@ -1699,7 +1673,7 @@ Scope.prototype = { */ _init: function(aName, aFlags) { this._idString = generateId(this._nameString = aName); - this._displayScope(aName, "variables-view-scope", "devtools-toolbar"); + this._displayScope(aName, this.targetClassName, "devtools-toolbar"); this._addEventListeners(); this.parentNode.appendChild(this._target); }, @@ -1709,17 +1683,17 @@ Scope.prototype = { * * @param string aName * The scope's name. - * @param string aClassName - * A custom class name for this scope. + * @param string aTargetClassName + * A custom class name for this scope's target element. * @param string aTitleClassName [optional] - * A custom class name for this scope's title. + * A custom class name for this scope's title element. */ - _displayScope: function(aName, aClassName, aTitleClassName) { + _displayScope: function(aName, aTargetClassName, aTitleClassName = "") { let document = this.document; let element = this._target = document.createElement("vbox"); element.id = this._idString; - element.className = aClassName; + element.className = aTargetClassName; let arrow = this._arrow = document.createElement("hbox"); arrow.className = "arrow"; @@ -1729,7 +1703,7 @@ Scope.prototype = { name.setAttribute("value", aName); let title = this._title = document.createElement("hbox"); - title.className = "title " + (aTitleClassName || ""); + title.className = "title " + aTitleClassName; title.setAttribute("align", "center"); let enumerable = this._enum = document.createElement("vbox"); @@ -1766,99 +1740,12 @@ Scope.prototype = { this.focus(); }, - /** - * Lazily appends a node to this scope's enumerable or non-enumerable - * container. Once a certain number of nodes have been batched, they - * will be appended. - * - * @param boolean aImmediateFlag - * Set to false if append calls should be dispatched synchronously - * on the current thread, to allow for a paint flush. - * @param boolean aEnumerableFlag - * Specifies if the node to append is enumerable or non-enumerable. - * @param nsIDOMNode aChild - * The child node to append. - */ - _lazyAppend: function(aImmediateFlag, aEnumerableFlag, aChild) { - // Append immediately, don't stage items and don't allow for a paint flush. - if (aImmediateFlag || !this._variablesView.lazyAppend) { - if (aEnumerableFlag) { - this._enum.appendChild(aChild); - } else { - this._nonenum.appendChild(aChild); - } - return; - } - - let window = this.window; - let batchItems = this._batchItems; - - window.clearTimeout(this._batchTimeout); - batchItems.push({ enumerableFlag: aEnumerableFlag, child: aChild }); - - // If a certain number of nodes have been batched, append all the - // staged items now. - if (batchItems.length > LAZY_APPEND_BATCH) { - // Allow for a paint flush. - Services.tm.currentThread.dispatch({ run: this._batchAppend }, 1); - return; - } - // Postpone appending the staged items for later, to allow batching - // more nodes. - this._batchTimeout = window.setTimeout(this._batchAppend, LAZY_APPEND_DELAY); - }, - - /** - * Appends all the batched nodes to this scope's enumerable and non-enumerable - * containers. - */ - _batchAppend: function() { - let document = this.document; - let batchItems = this._batchItems; - - // Create two document fragments, one for enumerable nodes, and one - // for non-enumerable nodes. - let frags = [document.createDocumentFragment(), document.createDocumentFragment()]; - - for (let item of batchItems) { - frags[~~item.enumerableFlag].appendChild(item.child); - } - batchItems.length = 0; - this._enum.appendChild(frags[1]); - this._nonenum.appendChild(frags[0]); - }, - - /** - * Starts spinning a throbber in this scope's title. - */ - _startThrobber: function() { - if (this._throbber) { - this._throbber.hidden = false; - return; - } - let throbber = this._throbber = this.document.createElement("hbox"); - throbber.className = "variables-view-throbber"; - throbber.setAttribute("optional-visibility", ""); - this._title.insertBefore(throbber, this._spacer); - }, - - /** - * Stops spinning the throbber in this scope's title. - */ - _stopThrobber: function() { - if (!this._throbber) { - return; - } - this._throbber.hidden = true; - }, - /** * Opens the enumerable items container. */ _openEnum: function() { this._arrow.setAttribute("open", ""); this._enum.setAttribute("open", ""); - this._stopThrobber(); }, /** @@ -1866,7 +1753,6 @@ Scope.prototype = { */ _openNonEnum: function() { this._nonenum.setAttribute("open", ""); - this._stopThrobber(); }, /** @@ -2108,10 +1994,7 @@ Scope.prototype = { _fetched: false, _retrieved: false, _committed: false, - _batchItems: null, - _batchTimeout: null, - _locked: false, - _isExpanding: false, + _isLocked: false, _isExpanded: false, _isContentVisible: true, _isHeaderVisible: true, @@ -2125,7 +2008,6 @@ Scope.prototype = { _title: null, _enum: null, _nonenum: null, - _throbber: null }; // Creating maps and arrays thousands of times for variables or properties @@ -2134,7 +2016,6 @@ Scope.prototype = { DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_store", Map); DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_enumItems", Array); DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_nonEnumItems", Array); -DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_batchItems", Array); /** * A Variable is a Scope holding Property instances. @@ -2173,6 +2054,11 @@ Variable.prototype = Heritage.extend(Scope.prototype, { return this.name == "window" || this.name == "this"; }, + /** + * The class name applied to this variable's target element. + */ + targetClassName: "variables-view-variable variable-or-property", + /** * Create a new Property that is a child of Variable. * @@ -2415,33 +2301,21 @@ Variable.prototype = Heritage.extend(Scope.prototype, { */ _init: function(aName, aDescriptor) { this._idString = generateId(this._nameString = aName); - this._displayScope(aName, "variables-view-variable variable-or-property"); - + this._displayScope(aName, this.targetClassName); this._displayVariable(); this._customizeVariable(); this._prepareTooltips(); this._setAttributes(); this._addEventListeners(); - this._onInit(this.ownerView._store.size < LAZY_APPEND_BATCH); - }, - - /** - * Called when this variable has finished initializing, and is ready to - * be attached to the owner view. - * - * @param boolean aImmediateFlag - * @see Scope.prototype._lazyAppend - */ - _onInit: function(aImmediateFlag) { if (this._initialDescriptor.enumerable || this._nameString == "this" || this._nameString == "" || this._nameString == "") { - this.ownerView._lazyAppend(aImmediateFlag, true, this._target); + this.ownerView._enum.appendChild(this._target); this.ownerView._enumItems.push(this); } else { - this.ownerView._lazyAppend(aImmediateFlag, false, this._target); + this.ownerView._nonenum.appendChild(this._target); this.ownerView._nonEnumItems.push(this); } }, @@ -2868,42 +2742,9 @@ function Property(aVar, aName, aDescriptor) { Property.prototype = Heritage.extend(Variable.prototype, { /** - * Initializes this property's id, view and binds event listeners. - * - * @param string aName - * The property's name. - * @param object aDescriptor - * The property's descriptor. + * The class name applied to this property's target element. */ - _init: function(aName = "", aDescriptor) { - this._idString = generateId(this._nameString = aName); - this._displayScope(aName, "variables-view-property variable-or-property"); - - this._displayVariable(); - this._customizeVariable(); - this._prepareTooltips(); - this._setAttributes(); - this._addEventListeners(); - - this._onInit(this.ownerView._store.size < LAZY_APPEND_BATCH); - }, - - /** - * Called when this property has finished initializing, and is ready to - * be attached to the owner view. - * - * @param boolean aImmediateFlag - * @see Scope.prototype._lazyAppend - */ - _onInit: function(aImmediateFlag) { - if (this._initialDescriptor.enumerable) { - this.ownerView._lazyAppend(aImmediateFlag, true, this._target); - this.ownerView._enumItems.push(this); - } else { - this.ownerView._lazyAppend(aImmediateFlag, false, this._target); - this.ownerView._nonEnumItems.push(this); - } - } + targetClassName: "variables-view-property variable-or-property" }); /** @@ -3365,7 +3206,6 @@ Editable.prototype = { this._variable.collapse(); this._variable.hideArrow(); this._variable.locked = true; - this._variable._stopThrobber(); }, /** @@ -3383,7 +3223,6 @@ Editable.prototype = { this._variable.locked = false; this._variable.twisty = this._prevExpandable; this._variable.expanded = this._prevExpanded; - this._variable._stopThrobber(); }, /** diff --git a/browser/devtools/webconsole/test/head.js b/browser/devtools/webconsole/test/head.js index c60e972e605a..1cd21643bab8 100644 --- a/browser/devtools/webconsole/test/head.js +++ b/browser/devtools/webconsole/test/head.js @@ -796,7 +796,6 @@ function openDebugger(aOptions = {}) let panelWin = panel.panelWin; panel._view.Variables.lazyEmpty = false; - panel._view.Variables.lazyAppend = false; let resolveObject = { target: target, diff --git a/browser/devtools/webconsole/webconsole.js b/browser/devtools/webconsole/webconsole.js index 5e477227fc98..3596285520fe 100644 --- a/browser/devtools/webconsole/webconsole.js +++ b/browser/devtools/webconsole/webconsole.js @@ -3431,7 +3431,6 @@ JSTerm.prototype = { view.emptyText = l10n.getStr("emptyPropertiesList"); view.searchEnabled = !aOptions.hideFilterInput; view.lazyEmpty = this._lazyVariablesView; - view.lazyAppend = this._lazyVariablesView; VariablesViewController.attach(view, { getEnvironmentClient: aGrip => { From fa7e4622cb5a0b94482c5cee77a6f9ef55442ad2 Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Wed, 18 Dec 2013 19:01:38 +0200 Subject: [PATCH 07/36] Bug 830344 - Part 2: Implement pagination when expanding objects with lots of properties, r=past --- ...rowser_dbg_variables-view-accessibility.js | 2 +- ...r_dbg_variables-view-large-array-buffer.js | 206 +++++++++++++++++- .../debugger/test/doc_large-array-buffer.html | 7 +- .../netmonitor/test/browser_net_json-long.js | 13 +- .../shared/widgets/SideMenuWidget.jsm | 1 + .../devtools/shared/widgets/VariablesView.jsm | 123 +++++++++-- browser/devtools/shared/widgets/widgets.css | 9 +- browser/themes/linux/devtools/widgets.css | 16 +- browser/themes/osx/devtools/widgets.css | 16 +- browser/themes/windows/devtools/widgets.css | 16 +- 10 files changed, 346 insertions(+), 63 deletions(-) diff --git a/browser/devtools/debugger/test/browser_dbg_variables-view-accessibility.js b/browser/devtools/debugger/test/browser_dbg_variables-view-accessibility.js index 861e80460a10..fb76d6ebb36d 100644 --- a/browser/devtools/debugger/test/browser_dbg_variables-view-accessibility.js +++ b/browser/devtools/debugger/test/browser_dbg_variables-view-accessibility.js @@ -61,7 +61,7 @@ function performTest() { gVariablesView.switch = function() {}; gVariablesView.delete = function() {}; gVariablesView.rawObject = test; - gVariablesView.pageSize = 5; + gVariablesView.scrollPageSize = 5; return Task.spawn(function() { yield waitForTick(); diff --git a/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js b/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js index 8f4fc2f78052..3065f4aee7f0 100644 --- a/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js +++ b/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js @@ -9,7 +9,7 @@ const TAB_URL = EXAMPLE_URL + "doc_large-array-buffer.html"; let gTab, gDebuggee, gPanel, gDebugger; -let gVariables; +let gVariables, gEllipsis; function test() { initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => { @@ -18,9 +18,12 @@ function test() { gPanel = aPanel; gDebugger = gPanel.panelWin; gVariables = gDebugger.DebuggerView.Variables; + gEllipsis = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data; - waitForSourceAndCaretAndScopes(gPanel, ".html", 18) - .then(() => performTest()) + waitForSourceAndCaretAndScopes(gPanel, ".html", 23) + .then(() => initialChecks()) + .then(() => verifyFirstLevel()) + .then(() => verifyNextLevels()) .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) .then(null, aError => { ok(false, "Got an error: " + aError.message + "\n" + aError.stack); @@ -32,8 +35,202 @@ function test() { }); } -function performTest() { +function initialChecks() { + let localScope = gVariables.getScopeAtIndex(0); + let bufferVar = localScope.get("buffer"); + let arrayVar = localScope.get("largeArray"); + let objectVar = localScope.get("largeObject"); + ok(bufferVar, "There should be a 'buffer' variable present in the scope."); + ok(arrayVar, "There should be a 'largeArray' variable present in the scope."); + ok(objectVar, "There should be a 'largeObject' variable present in the scope."); + + is(bufferVar.target.querySelector(".name").getAttribute("value"), "buffer", + "Should have the right property name for 'buffer'."); + is(bufferVar.target.querySelector(".value").getAttribute("value"), "ArrayBuffer", + "Should have the right property value for 'buffer'."); + ok(bufferVar.target.querySelector(".value").className.contains("token-other"), + "Should have the right token class for 'buffer'."); + + is(arrayVar.target.querySelector(".name").getAttribute("value"), "largeArray", + "Should have the right property name for 'largeArray'."); + is(arrayVar.target.querySelector(".value").getAttribute("value"), "Int8Array", + "Should have the right property value for 'largeArray'."); + ok(arrayVar.target.querySelector(".value").className.contains("token-other"), + "Should have the right token class for 'largeArray'."); + + is(objectVar.target.querySelector(".name").getAttribute("value"), "largeObject", + "Should have the right property name for 'largeObject'."); + is(objectVar.target.querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for 'largeObject'."); + ok(objectVar.target.querySelector(".value").className.contains("token-other"), + "Should have the right token class for 'largeObject'."); + + is(bufferVar.expanded, false, + "The 'buffer' variable shouldn't be expanded."); + is(arrayVar.expanded, false, + "The 'largeArray' variable shouldn't be expanded."); + is(objectVar.expanded, false, + "The 'largeObject' variable shouldn't be expanded."); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 2); + arrayVar.expand(); + objectVar.expand(); + return finished; +} + +function verifyFirstLevel() { + let localScope = gVariables.getScopeAtIndex(0); + let arrayVar = localScope.get("largeArray"); + let objectVar = localScope.get("largeObject"); + + let arrayEnums = arrayVar.target.querySelector(".variables-view-element-details.enum").childNodes; + let arrayNonEnums = arrayVar.target.querySelector(".variables-view-element-details.nonenum").childNodes; + is(arrayEnums.length, 0, + "The 'largeArray' shouldn't contain any enumerable elements."); + is(arrayNonEnums.length, 9, + "The 'largeArray' should contain all the created non-enumerable elements."); + + let objectEnums = objectVar.target.querySelector(".variables-view-element-details.enum").childNodes; + let objectNonEnums = objectVar.target.querySelector(".variables-view-element-details.nonenum").childNodes; + is(objectEnums.length, 0, + "The 'largeObject' shouldn't contain any enumerable elements."); + is(objectNonEnums.length, 5, + "The 'largeObject' should contain all the created non-enumerable elements."); + + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), + 0 + gEllipsis + 1999, "The first page in the 'largeArray' is named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), + "", "The first page in the 'largeArray' should not have a corresponding value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), + 2000 + gEllipsis + 3999, "The second page in the 'largeArray' is named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), + "", "The second page in the 'largeArray' should not have a corresponding value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"), + 4000 + gEllipsis + 5999, "The third page in the 'largeArray' is named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[2].getAttribute("value"), + "", "The third page in the 'largeArray' should not have a corresponding value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"), + 6000 + gEllipsis + 9999, "The fourth page in the 'largeArray' is named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[3].getAttribute("value"), + "", "The fourth page in the 'largeArray' should not have a corresponding value."); + + is(objectVar.target.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), + 0 + gEllipsis + 1999, "The first page in the 'largeObject' is named correctly."); + is(objectVar.target.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), + "", "The first page in the 'largeObject' should not have a corresponding value."); + is(objectVar.target.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), + 2000 + gEllipsis + 3999, "The second page in the 'largeObject' is named correctly."); + is(objectVar.target.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), + "", "The second page in the 'largeObject' should not have a corresponding value."); + is(objectVar.target.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"), + 4000 + gEllipsis + 5999, "The thrid page in the 'largeObject' is named correctly."); + is(objectVar.target.querySelectorAll(".variables-view-property .value")[2].getAttribute("value"), + "", "The thrid page in the 'largeObject' should not have a corresponding value."); + is(objectVar.target.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"), + 6000 + gEllipsis + 9999, "The fourth page in the 'largeObject' is named correctly."); + is(objectVar.target.querySelectorAll(".variables-view-property .value")[3].getAttribute("value"), + "", "The fourth page in the 'largeObject' should not have a corresponding value."); + + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[4].getAttribute("value"), + "length", "The other properties 'largeArray' are named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[4].getAttribute("value"), + "10000", "The other properties 'largeArray' have the correct value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[5].getAttribute("value"), + "buffer", "The other properties 'largeArray' are named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[5].getAttribute("value"), + "ArrayBuffer", "The other properties 'largeArray' have the correct value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[6].getAttribute("value"), + "byteLength", "The other properties 'largeArray' are named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[6].getAttribute("value"), + "10000", "The other properties 'largeArray' have the correct value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[7].getAttribute("value"), + "byteOffset", "The other properties 'largeArray' are named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[7].getAttribute("value"), + "0", "The other properties 'largeArray' have the correct value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[8].getAttribute("value"), + "__proto__", "The other properties 'largeArray' are named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[8].getAttribute("value"), + "Int8ArrayPrototype", "The other properties 'largeArray' have the correct value."); + + is(objectVar.target.querySelectorAll(".variables-view-property .name")[4].getAttribute("value"), + "__proto__", "The other properties 'largeObject' are named correctly."); + is(objectVar.target.querySelectorAll(".variables-view-property .value")[4].getAttribute("value"), + "Object", "The other properties 'largeObject' have the correct value."); +} + +function verifyNextLevels() { + let localScope = gVariables.getScopeAtIndex(0); + let objectVar = localScope.get("largeObject"); + + let lastPage1 = objectVar.get(6000 + gEllipsis + 9999); + ok(lastPage1, "The last page in the first level was retrieved successfully."); + lastPage1.expand(); + + let pageEnums1 = lastPage1.target.querySelector(".variables-view-element-details.enum").childNodes; + let pageNonEnums1 = lastPage1.target.querySelector(".variables-view-element-details.nonenum").childNodes; + is(pageEnums1.length, 0, + "The last page in the first level shouldn't contain any enumerable elements."); + is(pageNonEnums1.length, 4, + "The last page in the first level should contain all the created non-enumerable elements."); + + is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), + 6000 + gEllipsis + 6999, "The first page in this level named correctly (1)."); + is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), + 7000 + gEllipsis + 7999, "The second page in this level named correctly (1)."); + is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"), + 8000 + gEllipsis + 8999, "The third page in this level named correctly (1)."); + is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"), + 9000 + gEllipsis + 9999, "The fourth page in this level named correctly (1)."); + + let lastPage2 = lastPage1.get(9000 + gEllipsis + 9999); + ok(lastPage2, "The last page in the second level was retrieved successfully."); + lastPage2.expand(); + + let pageEnums2 = lastPage2.target.querySelector(".variables-view-element-details.enum").childNodes; + let pageNonEnums2 = lastPage2.target.querySelector(".variables-view-element-details.nonenum").childNodes; + is(pageEnums2.length, 0, + "The last page in the second level shouldn't contain any enumerable elements."); + is(pageNonEnums2.length, 4, + "The last page in the second level should contain all the created non-enumerable elements."); + + is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), + 9000 + gEllipsis + 9199, "The first page in this level named correctly (2)."); + is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), + 9200 + gEllipsis + 9399, "The second page in this level named correctly (2)."); + is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"), + 9400 + gEllipsis + 9599, "The third page in this level named correctly (2)."); + is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"), + 9600 + gEllipsis + 9999, "The fourth page in this level named correctly (2)."); + + let lastPage3 = lastPage2.get(9600 + gEllipsis + 9999); + ok(lastPage3, "The last page in the third level was retrieved successfully."); + lastPage3.expand(); + + let pageEnums3 = lastPage3.target.querySelector(".variables-view-element-details.enum").childNodes; + let pageNonEnums3 = lastPage3.target.querySelector(".variables-view-element-details.nonenum").childNodes; + is(pageEnums3.length, 400, + "The last page in the third level should contain all the created enumerable elements."); + is(pageNonEnums3.length, 0, + "The last page in the third level shouldn't contain any non-enumerable elements."); + + is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), + 9600, "The properties in this level are named correctly (3)."); + is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), + 9601, "The properties in this level are named correctly (3)."); + is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[398].getAttribute("value"), + 9998, "The properties in this level are named correctly (3)."); + is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[399].getAttribute("value"), + 9999, "The properties in this level are named correctly (3)."); + + is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), + 399, "The properties in this level have the correct value (3)."); + is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), + 398, "The properties in this level have the correct value (3)."); + is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[398].getAttribute("value"), + 1, "The properties in this level have the correct value (3)."); + is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[399].getAttribute("value"), + 0, "The properties in this level have the correct value (3)."); } registerCleanupFunction(function() { @@ -42,4 +239,5 @@ registerCleanupFunction(function() { gPanel = null; gDebugger = null; gVariables = null; + gEllipsis = null; }); diff --git a/browser/devtools/debugger/test/doc_large-array-buffer.html b/browser/devtools/debugger/test/doc_large-array-buffer.html index 6e0a62f3783a..b8545e57cd7f 100644 --- a/browser/devtools/debugger/test/doc_large-array-buffer.html +++ b/browser/devtools/debugger/test/doc_large-array-buffer.html @@ -14,7 +14,12 @@ diff --git a/browser/devtools/netmonitor/test/browser_net_json-long.js b/browser/devtools/netmonitor/test/browser_net_json-long.js index 5e1b551abc9b..4eea879f4a1f 100644 --- a/browser/devtools/netmonitor/test/browser_net_json-long.js +++ b/browser/devtools/netmonitor/test/browser_net_json-long.js @@ -61,14 +61,14 @@ function test() { is(tabpanel.querySelectorAll(".variables-view-scope").length, 1, "There should be 1 json scope displayed in this tabpanel."); - is(tabpanel.querySelectorAll(".variables-view-property").length, 6057, - "There should be 6057 json properties displayed in this tabpanel."); + is(tabpanel.querySelectorAll(".variables-view-property").length, 6143, + "There should be 6143 json properties displayed in this tabpanel."); is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0, "The empty notice should not be displayed in this tabpanel."); let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0]; - let names = ".variables-view-property .name"; - let values = ".variables-view-property .value"; + let names = ".variables-view-property > .title > .name"; + let values = ".variables-view-property > .title > .value"; is(jsonScope.querySelector(".name").getAttribute("value"), L10N.getStr("jsonScopeName"), @@ -83,11 +83,6 @@ function test() { "greeting", "The second json property name was incorrect."); is(jsonScope.querySelectorAll(values)[1].getAttribute("value"), "\"Hello long string JSON!\"", "The second json property value was incorrect."); - - is(Array.slice(jsonScope.querySelectorAll(names), -1).shift().getAttribute("value"), - "__proto__", "The last json property name was incorrect."); - is(Array.slice(jsonScope.querySelectorAll(values), -1).shift().getAttribute("value"), - "Object", "The last json property value was incorrect."); } }); diff --git a/browser/devtools/shared/widgets/SideMenuWidget.jsm b/browser/devtools/shared/widgets/SideMenuWidget.jsm index afc60b10edd0..2580bf2ec43b 100644 --- a/browser/devtools/shared/widgets/SideMenuWidget.jsm +++ b/browser/devtools/shared/widgets/SideMenuWidget.jsm @@ -177,6 +177,7 @@ SideMenuWidget.prototype = { this._orderedMenuElementsArray.splice( this._orderedMenuElementsArray.indexOf(aChild), 1); + this._itemsByElement.delete(aChild); if (this._selectedItem == aChild) { diff --git a/browser/devtools/shared/widgets/VariablesView.jsm b/browser/devtools/shared/widgets/VariablesView.jsm index f1931c068885..233360add63d 100644 --- a/browser/devtools/shared/widgets/VariablesView.jsm +++ b/browser/devtools/shared/widgets/VariablesView.jsm @@ -11,6 +11,8 @@ const Cu = Components.utils; const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties"; const LAZY_EMPTY_DELAY = 150; // ms const LAZY_EXPAND_DELAY = 50; // ms +const SCROLL_PAGE_SIZE_DEFAULT = 0; +const APPEND_PAGE_SIZE_DEFAULT = 500; const PAGE_SIZE_SCROLL_HEIGHT_RATIO = 100; const PAGE_SIZE_MAX_JUMPS = 30; const SEARCH_ACTION_MAX_DELAY = 300; // ms @@ -228,6 +230,19 @@ VariablesView.prototype = { */ lazySearch: true, + /** + * The number of elements in this container to jump when Page Up or Page Down + * keys are pressed. If falsy, then the page size will be based on the + * container height. + */ + scrollPageSize: SCROLL_PAGE_SIZE_DEFAULT, + + /** + * The maximum number of elements allowed in a scope, variable or property + * that allows pagination when appending children. + */ + appendPageSize: APPEND_PAGE_SIZE_DEFAULT, + /** * Function called each time a variable or property's value is changed via * user interaction. If null, then value changes are disabled. @@ -807,14 +822,14 @@ VariablesView.prototype = { case e.DOM_VK_PAGE_UP: // Rewind a certain number of elements based on the container height. - this.focusItemAtDelta(-(this.pageSize || Math.min(Math.floor(this._list.scrollHeight / + this.focusItemAtDelta(-(this.scrollPageSize || Math.min(Math.floor(this._list.scrollHeight / PAGE_SIZE_SCROLL_HEIGHT_RATIO), PAGE_SIZE_MAX_JUMPS))); return; case e.DOM_VK_PAGE_DOWN: // Advance a certain number of elements based on the container height. - this.focusItemAtDelta(+(this.pageSize || Math.min(Math.floor(this._list.scrollHeight / + this.focusItemAtDelta(+(this.scrollPageSize || Math.min(Math.floor(this._list.scrollHeight / PAGE_SIZE_SCROLL_HEIGHT_RATIO), PAGE_SIZE_MAX_JUMPS))); return; @@ -868,13 +883,6 @@ VariablesView.prototype = { } }, - /** - * The number of elements in this container to jump when Page Up or Page Down - * keys are pressed. If falsy, then the page size will be based on the - * container height. - */ - pageSize: 0, - /** * Sets the text displayed in this container when there are no available items. * @param string aValue @@ -1190,6 +1198,8 @@ function Scope(aView, aName, aFlags = {}) { // Inherit properties and flags from the parent view. You can override // each of these directly onto any scope, variable or property instance. + this.scrollPageSize = aView.scrollPageSize; + this.appendPageSize = aView.appendPageSize; this.eval = aView.eval; this.switch = aView.switch; this.delete = aView.delete; @@ -1212,6 +1222,11 @@ Scope.prototype = { */ shouldPrefetch: true, + /** + * Whether this Scope should paginate its contents. + */ + allowPaginate: false, + /** * The class name applied to this scope's target element. */ @@ -1289,14 +1304,84 @@ Scope.prototype = { * Additional options for adding the properties. Supported options: * - sorted: true to sort all the properties before adding them * - callback: function invoked after each item is added + * @param string aKeysType [optional] + * Helper argument in the case of paginated items. Can be either + * "just-strings" or "just-numbers". Humans shouldn't use this argument. */ - addItems: function(aItems, aOptions = {}) { + addItems: function(aItems, aOptions = {}, aKeysType = "") { let names = Object.keys(aItems); + // Building the view when inspecting an object with a very large number of + // properties may take a long time. To avoid blocking the UI, group + // the items into several lazily populated pseudo-items. + let exceedsThreshold = names.length >= this.appendPageSize; + let shouldPaginate = exceedsThreshold && aKeysType != "just-strings"; + if (shouldPaginate && this.allowPaginate) { + // Group the items to append into two separate arrays, one containing + // number-like keys, the other one containing string keys. + if (aKeysType == "just-numbers") { + var numberKeys = names; + var stringKeys = []; + } else { + var numberKeys = []; + var stringKeys = []; + for (let name of names) { + // Be very careful. Avoid Infinity, NaN and non Natural number keys. + let coerced = +name; + if (Number.isInteger(coerced) && coerced > -1) { + numberKeys.push(name); + } else { + stringKeys.push(name); + } + } + } + + // This object contains a very large number of properties, but they're + // almost all strings that can't be coerced to numbers. Don't paginate. + if (numberKeys.length < this.appendPageSize) { + this.addItems(aItems, aOptions, "just-strings"); + return; + } + + // Slices a section of the { name: descriptor } data properties. + let paginate = (aArray, aBegin = 0, aEnd = aArray.length) => { + let store = {} + for (let i = aBegin; i < aEnd; i++) { + let name = aArray[i]; + store[name] = aItems[name]; + } + return store; + }; + + // Creates a pseudo-item that populates itself with the data properties + // from the corresponding page range. + let createRangeExpander = (aArray, aBegin, aEnd, aOptions, aKeyTypes) => { + let rangeVar = this.addItem(aArray[aBegin] + Scope.ellipsis + aArray[aEnd - 1]); + rangeVar.onexpand = () => { + let pageItems = paginate(aArray, aBegin, aEnd); + rangeVar.addItems(pageItems, aOptions, aKeyTypes); + } + rangeVar.showArrow(); + rangeVar.target.setAttribute("pseudo-item", ""); + }; + + // Divide the number keys into quarters. + let page = +Math.round(numberKeys.length / 4).toPrecision(1); + createRangeExpander(numberKeys, 0, page, aOptions, "just-numbers"); + createRangeExpander(numberKeys, page, page * 2, aOptions, "just-numbers"); + createRangeExpander(numberKeys, page * 2, page * 3, aOptions, "just-numbers"); + createRangeExpander(numberKeys, page * 3, numberKeys.length, aOptions, "just-numbers"); + + // Append all the string keys together. + this.addItems(paginate(stringKeys), aOptions, "just-strings"); + return; + } + // Sort all of the properties before adding them, if preferred. - if (aOptions.sorted) { + if (aOptions.sorted && aKeysType != "just-numbers") { names.sort(); } + // Add the properties to the current scope. for (let name of names) { let descriptor = aItems[name]; @@ -2017,6 +2102,10 @@ DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_store", Map); DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_enumItems", Array); DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_nonEnumItems", Array); +// An ellipsis symbol (usually "…") used for localization. +XPCOMUtils.defineLazyGetter(Scope, "ellipsis", () => + Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data); + /** * A Variable is a Scope holding Property instances. * Iterable via "for (let [name, property] of instance) { }". @@ -2048,12 +2137,19 @@ function Variable(aScope, aName, aDescriptor) { Variable.prototype = Heritage.extend(Scope.prototype, { /** - * Whether this Scope should be prefetched when it is remoted. + * Whether this Variable should be prefetched when it is remoted. */ - get shouldPrefetch(){ + get shouldPrefetch() { return this.name == "window" || this.name == "this"; }, + /** + * Whether this Variable should paginate its contents. + */ + get allowPaginate() { + return this.name != "window" && this.name != "this"; + }, + /** * The class name applied to this variable's target element. */ @@ -3114,7 +3210,6 @@ let generateId = (function() { }; })(); - /** * An Editable encapsulates the UI of an edit box that overlays a label, * allowing the user to edit the value. diff --git a/browser/devtools/shared/widgets/widgets.css b/browser/devtools/shared/widgets/widgets.css index c3c678d8e9af..e932b451b960 100644 --- a/browser/devtools/shared/widgets/widgets.css +++ b/browser/devtools/shared/widgets/widgets.css @@ -64,7 +64,14 @@ display: none; } -.variable-or-property[pseudo-item] > tooltip { +.variable-or-property[pseudo-item] > tooltip, +.variable-or-property[pseudo-item] > .title > .variables-view-edit, +.variable-or-property[pseudo-item] > .title > .variables-view-delete, +.variable-or-property[pseudo-item] > .title > .variables-view-add-property, +.variable-or-property[pseudo-item] > .title > .variable-or-property-frozen-label, +.variable-or-property[pseudo-item] > .title > .variable-or-property-sealed-label, +.variable-or-property[pseudo-item] > .title > .variable-or-property-non-extensible-label, +.variable-or-property[pseudo-item] > .title > .variable-or-property-non-writable-icon { display: none; } diff --git a/browser/themes/linux/devtools/widgets.css b/browser/themes/linux/devtools/widgets.css index 78d0df0ddcab..8342525997d9 100644 --- a/browser/themes/linux/devtools/widgets.css +++ b/browser/themes/linux/devtools/widgets.css @@ -451,18 +451,8 @@ color: #fff; } -.variables-view-scope > .variables-view-element-details:not(:empty) { - -moz-margin-start: 2px; - -moz-margin-end: 1px; -} - /* Generic variables traits */ -.variables-view-variable { - -moz-margin-start: 1px; - -moz-margin-end: 1px; -} - .variables-view-variable:not(:last-child) { border-bottom: 1px solid rgba(128, 128, 128, .15); } @@ -481,8 +471,12 @@ -moz-box-flex: 1; } +.variable-or-property > .title > .arrow { + -moz-margin-start: 3px; +} + .variable-or-property:not([untitled]) > .variables-view-element-details { - -moz-margin-start: 10px; + -moz-margin-start: 7px; } /* Traits applied when variables or properties are changed or overridden */ diff --git a/browser/themes/osx/devtools/widgets.css b/browser/themes/osx/devtools/widgets.css index fb6ac2e4408f..6b8b33893f60 100644 --- a/browser/themes/osx/devtools/widgets.css +++ b/browser/themes/osx/devtools/widgets.css @@ -445,18 +445,8 @@ color: #fff; } -.variables-view-scope > .variables-view-element-details:not(:empty) { - -moz-margin-start: 2px; - -moz-margin-end: 1px; -} - /* Generic variables traits */ -.variables-view-variable { - -moz-margin-start: 1px; - -moz-margin-end: 1px; -} - .variables-view-variable:not(:last-child) { border-bottom: 1px solid rgba(128, 128, 128, .15); } @@ -475,8 +465,12 @@ -moz-box-flex: 1; } +.variable-or-property > .title > .arrow { + -moz-margin-start: 3px; +} + .variable-or-property:not([untitled]) > .variables-view-element-details { - -moz-margin-start: 10px; + -moz-margin-start: 7px; } /* Traits applied when variables or properties are changed or overridden */ diff --git a/browser/themes/windows/devtools/widgets.css b/browser/themes/windows/devtools/widgets.css index 89bb14536de0..e154432dd754 100644 --- a/browser/themes/windows/devtools/widgets.css +++ b/browser/themes/windows/devtools/widgets.css @@ -448,18 +448,8 @@ color: #fff; } -.variables-view-scope > .variables-view-element-details:not(:empty) { - -moz-margin-start: 2px; - -moz-margin-end: 1px; -} - /* Generic variables traits */ -.variables-view-variable { - -moz-margin-start: 1px; - -moz-margin-end: 1px; -} - .variables-view-variable:not(:last-child) { border-bottom: 1px solid rgba(128, 128, 128, .15); } @@ -478,8 +468,12 @@ -moz-box-flex: 1; } +.variable-or-property > .title > .arrow { + -moz-margin-start: 3px; +} + .variable-or-property:not([untitled]) > .variables-view-element-details { - -moz-margin-start: 10px; + -moz-margin-start: 7px; } /* Traits applied when variables or properties are changed or overridden */ From f3e677541ed21765432f5752af7bd58018455fbb Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Wed, 18 Dec 2013 19:10:16 +0200 Subject: [PATCH 08/36] Bug 951674 - Vertical splitter isn't shown for the Events tab, r=fitzgen --- .../devtools/shared/widgets/VariablesView.jsm | 2 -- browser/themes/linux/devtools/debugger.css | 4 ++-- browser/themes/linux/devtools/widgets.css | 6 +++--- browser/themes/osx/devtools/debugger.css | 4 ++-- browser/themes/osx/devtools/widgets.css | 6 +++--- browser/themes/shared/devtools/common.css | 16 ++++++++-------- browser/themes/shared/devtools/profiler.inc.css | 6 +++--- .../themes/shared/devtools/shadereditor.inc.css | 4 ++-- browser/themes/windows/devtools/debugger.css | 4 ++-- browser/themes/windows/devtools/widgets.css | 6 +++--- 10 files changed, 28 insertions(+), 30 deletions(-) diff --git a/browser/devtools/shared/widgets/VariablesView.jsm b/browser/devtools/shared/widgets/VariablesView.jsm index 233360add63d..2c2b4ba20520 100644 --- a/browser/devtools/shared/widgets/VariablesView.jsm +++ b/browser/devtools/shared/widgets/VariablesView.jsm @@ -907,7 +907,6 @@ VariablesView.prototype = { label.className = "variables-view-empty-notice"; label.setAttribute("value", this._emptyTextValue); - this._parent.setAttribute("empty", ""); this._parent.appendChild(label); this._emptyTextNode = label; }, @@ -920,7 +919,6 @@ VariablesView.prototype = { return; } - this._parent.removeAttribute("empty"); this._parent.removeChild(this._emptyTextNode); this._emptyTextNode = null; }, diff --git a/browser/themes/linux/devtools/debugger.css b/browser/themes/linux/devtools/debugger.css index ee736ec8fd85..12222ae5dc65 100644 --- a/browser/themes/linux/devtools/debugger.css +++ b/browser/themes/linux/devtools/debugger.css @@ -14,7 +14,7 @@ } #sources-and-editor-splitter { - -moz-border-start-color: transparent; + border-color: transparent; } /* Sources toolbar */ @@ -377,7 +377,7 @@ } #globalsearch + .devtools-horizontal-splitter { - -moz-border-top-colors: #bfbfbf; + border-color: #bfbfbf; } .dbg-source-results { diff --git a/browser/themes/linux/devtools/widgets.css b/browser/themes/linux/devtools/widgets.css index 8342525997d9..a4b9ca72baab 100644 --- a/browser/themes/linux/devtools/widgets.css +++ b/browser/themes/linux/devtools/widgets.css @@ -271,7 +271,7 @@ .side-menu-widget-container { /* Hack: force hardware acceleration */ - transform: translateX(0px); + transform: translateZ(1px); } .side-menu-widget-container[theme="dark"] { @@ -437,9 +437,9 @@ /* VariablesView */ -.variables-view-container:not([empty]) { +.variables-view-container { /* Hack: force hardware acceleration */ - transform: translateX(1px); + transform: translateZ(1px); } .variables-view-empty-notice { diff --git a/browser/themes/osx/devtools/debugger.css b/browser/themes/osx/devtools/debugger.css index 55dfeee48bde..362e94fecdc7 100644 --- a/browser/themes/osx/devtools/debugger.css +++ b/browser/themes/osx/devtools/debugger.css @@ -16,7 +16,7 @@ } #sources-and-editor-splitter { - -moz-border-start-color: transparent; + border-color: transparent; } /* Sources toolbar */ @@ -379,7 +379,7 @@ } #globalsearch + .devtools-horizontal-splitter { - -moz-border-top-colors: #bfbfbf; + border-color: #bfbfbf; } .dbg-source-results { diff --git a/browser/themes/osx/devtools/widgets.css b/browser/themes/osx/devtools/widgets.css index 6b8b33893f60..cd7b48ce2d4f 100644 --- a/browser/themes/osx/devtools/widgets.css +++ b/browser/themes/osx/devtools/widgets.css @@ -271,7 +271,7 @@ .side-menu-widget-container { /* Hack: force hardware acceleration */ - transform: translateX(0px); + transform: translateZ(1px); } .side-menu-widget-container[theme="dark"] { @@ -431,9 +431,9 @@ /* VariablesView */ -.variables-view-container:not([empty]) { +.variables-view-container { /* Hack: force hardware acceleration */ - transform: translateX(1px); + transform: translateZ(1px); } .variables-view-empty-notice { diff --git a/browser/themes/shared/devtools/common.css b/browser/themes/shared/devtools/common.css index df70486fbb63..1cf692ae987d 100644 --- a/browser/themes/shared/devtools/common.css +++ b/browser/themes/shared/devtools/common.css @@ -26,23 +26,23 @@ -moz-appearance: none; background-image: none; background-color: transparent; - border: 1px solid black; - border-width: 1px 0 0 0; + border: 0; + border-bottom: 1px solid black; min-height: 3px; height: 3px; - margin-bottom: -3px; + margin-top: -2px; position: relative; } .devtools-side-splitter { -moz-appearance: none; background-image: none; - border: 0; - -moz-border-start: 1px solid black; - min-width: 0; - width: 3px; background-color: transparent; - -moz-margin-end: -3px; + border: 0; + -moz-border-end: 1px solid black; + min-width: 3px; + width: 3px; + -moz-margin-start: -3px; position: relative; cursor: e-resize; } diff --git a/browser/themes/shared/devtools/profiler.inc.css b/browser/themes/shared/devtools/profiler.inc.css index ed6c994bbc07..4ef3ca3ca713 100644 --- a/browser/themes/shared/devtools/profiler.inc.css +++ b/browser/themes/shared/devtools/profiler.inc.css @@ -13,7 +13,7 @@ } .devtools-toolbar { - min-height: 33px; + min-height: 33px; } .profiler-sidebar { @@ -21,7 +21,7 @@ } .profiler-sidebar + .devtools-side-splitter { - -moz-border-start-color: transparent; + border-color: transparent; } .profiler-sidebar-item { @@ -86,4 +86,4 @@ #profiler-start[checked] { -moz-image-region: rect(0px,32px,16px,16px); -} \ No newline at end of file +} diff --git a/browser/themes/shared/devtools/shadereditor.inc.css b/browser/themes/shared/devtools/shadereditor.inc.css index ef93a59b399d..5dc226ffe46a 100644 --- a/browser/themes/shared/devtools/shadereditor.inc.css +++ b/browser/themes/shared/devtools/shadereditor.inc.css @@ -46,7 +46,7 @@ } #shaders-pane + .devtools-side-splitter { - -moz-border-start-color: transparent; + border-color: transparent; } .side-menu-widget-item-checkbox { @@ -88,7 +88,7 @@ /* Shader source editors */ #editors-splitter { - -moz-border-start-color: rgb(61,69,76); + border-color: rgb(61,69,76); } .editor-label { diff --git a/browser/themes/windows/devtools/debugger.css b/browser/themes/windows/devtools/debugger.css index 6343a0fd5a40..db72ec7cb944 100644 --- a/browser/themes/windows/devtools/debugger.css +++ b/browser/themes/windows/devtools/debugger.css @@ -14,7 +14,7 @@ } #sources-and-editor-splitter { - -moz-border-start-color: transparent; + border-color: transparent; } /* Sources toolbar */ @@ -377,7 +377,7 @@ } #globalsearch + .devtools-horizontal-splitter { - -moz-border-top-colors: #bfbfbf; + border-color: #bfbfbf; } .dbg-source-results { diff --git a/browser/themes/windows/devtools/widgets.css b/browser/themes/windows/devtools/widgets.css index e154432dd754..1565cfeddb8c 100644 --- a/browser/themes/windows/devtools/widgets.css +++ b/browser/themes/windows/devtools/widgets.css @@ -275,7 +275,7 @@ .side-menu-widget-container { /* Hack: force hardware acceleration */ - transform: translateX(0px); + transform: translateZ(1px); } .side-menu-widget-container[theme="dark"] { @@ -434,9 +434,9 @@ /* VariablesView */ -.variables-view-container:not([empty]) { +.variables-view-container { /* Hack: force hardware acceleration */ - transform: translateX(1px); + transform: translateZ(1px); } .variables-view-empty-notice { From abf7280748db97f7156955ae69d95d8e1e016ab3 Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Wed, 18 Dec 2013 19:10:17 +0200 Subject: [PATCH 09/36] Bug 951694 - Sources pane can overlap content over the source editor, r=fitzgen --- browser/themes/linux/devtools/debugger.css | 7 ++----- browser/themes/linux/devtools/widgets.css | 1 - browser/themes/osx/devtools/debugger.css | 7 ++----- browser/themes/osx/devtools/widgets.css | 1 - browser/themes/windows/devtools/debugger.css | 7 ++----- browser/themes/windows/devtools/widgets.css | 1 - 6 files changed, 6 insertions(+), 18 deletions(-) diff --git a/browser/themes/linux/devtools/debugger.css b/browser/themes/linux/devtools/debugger.css index 12222ae5dc65..a4686aef98d8 100644 --- a/browser/themes/linux/devtools/debugger.css +++ b/browser/themes/linux/devtools/debugger.css @@ -5,10 +5,6 @@ /* Sources and breakpoints pane */ -#sources-pane { - min-width: 50px; -} - #sources-pane > tabs { -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */ } @@ -72,7 +68,8 @@ #black-boxed-message, #source-progress-container { background: url(background-noise-toolbar.png) rgb(61,69,76); - /* Prevent the container deck from aquiring the height from this message. */ + /* Prevent the container deck from aquiring the size from this message. */ + min-width: 1px; min-height: 1px; color: white; } diff --git a/browser/themes/linux/devtools/widgets.css b/browser/themes/linux/devtools/widgets.css index a4b9ca72baab..2d8d1a11bb3b 100644 --- a/browser/themes/linux/devtools/widgets.css +++ b/browser/themes/linux/devtools/widgets.css @@ -6,7 +6,6 @@ /* Generic pane helpers */ .generic-toggled-side-pane { - min-width: 50px; -moz-margin-start: 0px !important; /* Unfortunately, transitions don't work properly with locale-aware properties, so both the left and right margins are set via js, while the start margin diff --git a/browser/themes/osx/devtools/debugger.css b/browser/themes/osx/devtools/debugger.css index 362e94fecdc7..808775e3eed9 100644 --- a/browser/themes/osx/devtools/debugger.css +++ b/browser/themes/osx/devtools/debugger.css @@ -7,10 +7,6 @@ /* Sources and breakpoints pane */ -#sources-pane { - min-width: 50px; -} - #sources-pane > tabs { -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */ } @@ -74,7 +70,8 @@ #black-boxed-message, #source-progress-container { background: url(background-noise-toolbar.png) rgb(61,69,76); - /* Prevent the container deck from aquiring the height from this message. */ + /* Prevent the container deck from aquiring the size from this message. */ + min-width: 1px; min-height: 1px; color: white; } diff --git a/browser/themes/osx/devtools/widgets.css b/browser/themes/osx/devtools/widgets.css index cd7b48ce2d4f..592387f3ba1f 100644 --- a/browser/themes/osx/devtools/widgets.css +++ b/browser/themes/osx/devtools/widgets.css @@ -6,7 +6,6 @@ /* Generic pane helpers */ .generic-toggled-side-pane { - min-width: 50px; -moz-margin-start: 0px !important; /* Unfortunately, transitions don't work properly with locale-aware properties, so both the left and right margins are set via js, while the start margin diff --git a/browser/themes/windows/devtools/debugger.css b/browser/themes/windows/devtools/debugger.css index db72ec7cb944..e1221e8daf5b 100644 --- a/browser/themes/windows/devtools/debugger.css +++ b/browser/themes/windows/devtools/debugger.css @@ -5,10 +5,6 @@ /* Sources and breakpoints pane */ -#sources-pane { - min-width: 50px; -} - #sources-pane > tabs { -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */ } @@ -72,7 +68,8 @@ #black-boxed-message, #source-progress-container { background: url(background-noise-toolbar.png) rgb(61,69,76); - /* Prevent the container deck from aquiring the height from this message. */ + /* Prevent the container deck from aquiring the size from this message. */ + min-width: 1px; min-height: 1px; color: white; } diff --git a/browser/themes/windows/devtools/widgets.css b/browser/themes/windows/devtools/widgets.css index 1565cfeddb8c..9df4907700c0 100644 --- a/browser/themes/windows/devtools/widgets.css +++ b/browser/themes/windows/devtools/widgets.css @@ -6,7 +6,6 @@ /* Generic pane helpers */ .generic-toggled-side-pane { - min-width: 50px; -moz-margin-start: 0px !important; /* Unfortunately, transitions don't work properly with locale-aware properties, so both the left and right margins are set via js, while the start margin From 1d719236e622c79045f50ffd970f489409363b91 Mon Sep 17 00:00:00 2001 From: David Rajchenbach-Teller Date: Wed, 18 Dec 2013 12:39:53 -0500 Subject: [PATCH 10/36] Bug 899276 - Don't collect/save private tabs. r=ttaubert --- .../content/content-sessionStore.js | 25 +++++++ .../sessionstore/src/SessionSaver.jsm | 36 +++++++--- .../sessionstore/src/SessionStore.jsm | 5 ++ .../components/sessionstore/test/browser.ini | 1 + .../sessionstore/test/browser_privatetabs.js | 70 +++++++++++++++++++ .../components/sessionstore/test/content.js | 7 ++ 6 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 browser/components/sessionstore/test/browser_privatetabs.js diff --git a/browser/components/sessionstore/content/content-sessionStore.js b/browser/components/sessionstore/content/content-sessionStore.js index 54b95e3bcf9d..5b9732b410ee 100644 --- a/browser/components/sessionstore/content/content-sessionStore.js +++ b/browser/components/sessionstore/content/content-sessionStore.js @@ -308,6 +308,30 @@ let SessionStorageListener = { Ci.nsISupportsWeakReference]) }; +/** + * Listen for changes to the privacy status of the tab. + * By definition, tabs start in non-private mode. + * + * Causes a SessionStore:update message to be sent for + * field "isPrivate". This message contains + * |true| if the tab is now private + * |null| if the tab is now public - the field is therefore + * not saved. + */ +let PrivacyListener = { + init: function() { + docShell.addWeakPrivacyTransitionObserver(this); + }, + + // Ci.nsIPrivacyTransitionObserver + privateModeChanged: function(enabled) { + MessageQueue.push("isPrivate", () => enabled || null); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrivacyTransitionObserver, + Ci.nsISupportsWeakReference]) +}; + /** * A message queue that takes collected data and will take care of sending it * to the chrome process. It allows flushing using synchronous messages and @@ -466,3 +490,4 @@ ProgressListener.init(); PageStyleListener.init(); SessionStorageListener.init(); DocShellCapabilitiesListener.init(); +PrivacyListener.init(); diff --git a/browser/components/sessionstore/src/SessionSaver.jsm b/browser/components/sessionstore/src/SessionSaver.jsm index 7d93bd220d0c..b813fc6ef515 100644 --- a/browser/components/sessionstore/src/SessionSaver.jsm +++ b/browser/components/sessionstore/src/SessionSaver.jsm @@ -72,7 +72,7 @@ this.SessionSaver = Object.freeze({ * Immediately saves the current session to disk. */ run: function () { - SessionSaverInternal.run(); + return SessionSaverInternal.run(); }, /** @@ -129,7 +129,7 @@ let SessionSaverInternal = { * Immediately saves the current session to disk. */ run: function () { - this._saveState(true /* force-update all windows */); + return this._saveState(true /* force-update all windows */); }, /** @@ -192,12 +192,24 @@ let SessionSaverInternal = { stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS"); let state = SessionStore.getCurrentState(forceUpdateAllWindows); - // Forget about private windows. + // Forget about private windows and tabs. for (let i = state.windows.length - 1; i >= 0; i--) { - if (state.windows[i].isPrivate) { - state.windows.splice(i, 1); - if (state.selectedWindow >= i) { - state.selectedWindow--; + let win = state.windows[i]; + if (win.isPrivate || false) { // The whole window is private, remove it + state.windows.splice(i, 1); + if (state.selectedWindow >= i) { + state.selectedWindow--; + } + continue; + } + // The window is not private, but its tabs still might + for (let j = win.tabs.length - 1; j >= 0 ; --j) { + let tab = win.tabs[j]; + if (tab.isPrivate || false) { + win.tabs.splice(j, 1); + if (win.selected >= j) { + win.selected--; + } } } } @@ -209,6 +221,10 @@ let SessionSaverInternal = { } } + // Note that closed private tabs are never stored (see + // SessionStoreInternal.onTabClose), so we do not need to remove + // them. + // Make sure that we keep the previous session if we started with a single // private window and no non-private windows have been opened, yet. if (state.deferredInitialState) { @@ -235,7 +251,7 @@ let SessionSaverInternal = { #endif stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS"); - this._writeState(state); + return this._writeState(state); }, /** @@ -278,7 +294,7 @@ let SessionSaverInternal = { // Don't touch the file if an observer has deleted all state data. if (!data) { - return; + return Promise.resolve(); } // We update the time stamp before writing so that we don't write again @@ -290,7 +306,7 @@ let SessionSaverInternal = { // Write (atomically) to a session file, using a tmp file. Once the session // file is successfully updated, save the time stamp of the last save and // notify the observers. - SessionFile.write(data).then(() => { + return SessionFile.write(data).then(() => { this.updateLastSaveTime(); notify(null, "sessionstore-state-write-complete"); }, Cu.reportError); diff --git a/browser/components/sessionstore/src/SessionStore.jsm b/browser/components/sessionstore/src/SessionStore.jsm index 10d8d781b263..63472818c5bb 100644 --- a/browser/components/sessionstore/src/SessionStore.jsm +++ b/browser/components/sessionstore/src/SessionStore.jsm @@ -1311,6 +1311,11 @@ let SessionStoreInternal = { // Get the latest data for this tab (generally, from the cache) let tabState = TabState.collectSync(aTab); + // Don't save private tabs + if (tabState.isPrivate || false) { + return; + } + // store closed-tab data for undo if (this._shouldSaveTabState(tabState)) { let tabTitle = aTab.label; diff --git a/browser/components/sessionstore/test/browser.ini b/browser/components/sessionstore/test/browser.ini index 8f0c479efa72..03061c5f1e78 100644 --- a/browser/components/sessionstore/test/browser.ini +++ b/browser/components/sessionstore/test/browser.ini @@ -59,6 +59,7 @@ support-files = [browser_merge_closed_tabs.js] [browser_pageshow.js] [browser_pageStyle.js] +[browser_privatetabs.js] [browser_sessionStorage.js] [browser_swapDocShells.js] [browser_tabStateCache.js] diff --git a/browser/components/sessionstore/test/browser_privatetabs.js b/browser/components/sessionstore/test/browser_privatetabs.js new file mode 100644 index 000000000000..87a2d6a9f760 --- /dev/null +++ b/browser/components/sessionstore/test/browser_privatetabs.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +let Imports = {}; +Cu.import("resource:///modules/sessionstore/SessionSaver.jsm", Imports); +let {Promise, Task, SessionSaver} = Imports; + +add_task(function cleanup() { + info("Forgetting closed tabs"); + while (ss.getClosedTabCount(window)) { + ss.forgetClosedTab(window, 0); + } +}); + +add_task(function() { + let URL_PUBLIC = "http://example.com/public/" + Math.random(); + let URL_PRIVATE = "http://example.com/private/" + Math.random(); + let tab1, tab2; + try { + // Setup a public tab and a private tab + info("Setting up public tab"); + tab1 = gBrowser.addTab(URL_PUBLIC); + yield promiseBrowserLoaded(tab1.linkedBrowser); + + info("Setting up private tab"); + tab2 = gBrowser.addTab(); + yield promiseBrowserLoaded(tab2.linkedBrowser); + yield setUsePrivateBrowsing(tab2.linkedBrowser, true); + tab2.linkedBrowser.loadURI(URL_PRIVATE); + yield promiseBrowserLoaded(tab2.linkedBrowser); + + info("Flush to make sure chrome received all data."); + SyncHandlers.get(tab2.linkedBrowser).flush(); + + info("Checking out state"); + yield SessionSaver.run(); + let path = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"); + let data = yield OS.File.read(path); + let state = new TextDecoder().decode(data); + info("State: " + state); + // Ensure that sessionstore.js only knows about the public tab + ok(state.indexOf(URL_PUBLIC) != -1, "State contains public tab"); + ok(state.indexOf(URL_PRIVATE) == -1, "State does not contain private tab"); + + // Ensure that we can close and undo close the public tab but not the private tab + gBrowser.removeTab(tab2); + tab2 = null; + + gBrowser.removeTab(tab1); + tab1 = null; + + tab1 = ss.undoCloseTab(window, 0); + ok(true, "Public tab supports undo close"); + + is(ss.getClosedTabCount(window), 0, "Private tab does not support undo close"); + + } finally { + if (tab1) { + gBrowser.removeTab(tab1); + } + if (tab2) { + gBrowser.removeTab(tab2); + } + } +}); + +function setUsePrivateBrowsing(browser, val) { + return sendMessage(browser, "ss-test:setUsePrivateBrowsing", val); +} + diff --git a/browser/components/sessionstore/test/content.js b/browser/components/sessionstore/test/content.js index 74961099d5af..c931147c95c2 100644 --- a/browser/components/sessionstore/test/content.js +++ b/browser/components/sessionstore/test/content.js @@ -52,3 +52,10 @@ addMessageListener("ss-test:setAuthorStyleDisabled", function (msg) { markupDocumentViewer.authorStyleDisabled = msg.data; sendSyncMessage("ss-test:setAuthorStyleDisabled"); }); + +addMessageListener("ss-test:setUsePrivateBrowsing", function (msg) { + let loadContext = + docShell.QueryInterface(Ci.nsILoadContext); + loadContext.usePrivateBrowsing = msg.data; + sendAsyncMessage("ss-test:setUsePrivateBrowsing"); +}); From 2fa4abe0c2bb2a6b22bc3253a23ff2baa3af9736 Mon Sep 17 00:00:00 2001 From: Patrick Brosset Date: Wed, 18 Dec 2013 12:40:08 -0500 Subject: [PATCH 11/36] Bug 935417 - Better word wrapping in the css rule view. r=harth --- browser/devtools/styleinspector/ruleview.css | 2 +- browser/themes/linux/devtools/ruleview.css | 6 ++++++ browser/themes/osx/devtools/ruleview.css | 7 ++++++- browser/themes/windows/devtools/ruleview.css | 6 ++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/browser/devtools/styleinspector/ruleview.css b/browser/devtools/styleinspector/ruleview.css index 808660d6f944..9768f2ca7a1c 100644 --- a/browser/devtools/styleinspector/ruleview.css +++ b/browser/devtools/styleinspector/ruleview.css @@ -51,7 +51,7 @@ .ruleview-header { vertical-align: middle; - height: 1.5em; + min-height: 1.5em; line-height: 1.5em; } diff --git a/browser/themes/linux/devtools/ruleview.css b/browser/themes/linux/devtools/ruleview.css index d4f75ab67640..e96b1a191097 100644 --- a/browser/themes/linux/devtools/ruleview.css +++ b/browser/themes/linux/devtools/ruleview.css @@ -22,6 +22,7 @@ padding: 1px 4px; margin-top: 4px; -moz-user-select: none; + word-wrap: break-word; } .ruleview-rule-source:hover { @@ -115,6 +116,7 @@ .ruleview-property { border-left: 2px solid transparent; + clear: right; } .ruleview-property > * { @@ -135,6 +137,10 @@ border-bottom-color: hsl(0,0%,50%); } +.ruleview-selector { + word-wrap: break-word; +} + .ruleview-selector-separator, .ruleview-selector-unmatched { color: #888; } diff --git a/browser/themes/osx/devtools/ruleview.css b/browser/themes/osx/devtools/ruleview.css index 680055de8feb..2e49d87300df 100644 --- a/browser/themes/osx/devtools/ruleview.css +++ b/browser/themes/osx/devtools/ruleview.css @@ -21,6 +21,7 @@ border-bottom-style: solid; padding: 1px 4px; -moz-user-select: none; + word-wrap: break-word; } .ruleview-rule-pseudo-element { @@ -119,6 +120,7 @@ .ruleview-property { border-left: 2px solid transparent; + clear: right; } .ruleview-property > * { @@ -139,7 +141,10 @@ border-bottom-color: hsl(0,0%,50%); } +.ruleview-selector { + word-wrap: break-word; +} + .ruleview-selector-separator, .ruleview-selector-unmatched { color: #888; } - diff --git a/browser/themes/windows/devtools/ruleview.css b/browser/themes/windows/devtools/ruleview.css index d4f75ab67640..e96b1a191097 100644 --- a/browser/themes/windows/devtools/ruleview.css +++ b/browser/themes/windows/devtools/ruleview.css @@ -22,6 +22,7 @@ padding: 1px 4px; margin-top: 4px; -moz-user-select: none; + word-wrap: break-word; } .ruleview-rule-source:hover { @@ -115,6 +116,7 @@ .ruleview-property { border-left: 2px solid transparent; + clear: right; } .ruleview-property > * { @@ -135,6 +137,10 @@ border-bottom-color: hsl(0,0%,50%); } +.ruleview-selector { + word-wrap: break-word; +} + .ruleview-selector-separator, .ruleview-selector-unmatched { color: #888; } From 1e7389d30e8535ad85e24eded0f6ebb80d7a9eef Mon Sep 17 00:00:00 2001 From: Frederik Braun Date: Wed, 18 Dec 2013 12:59:21 +0100 Subject: [PATCH 12/36] Bug 948894 - Move inline scripts for about:apps into AboutApps.js. r=mleibovic --- mobile/android/chrome/content/aboutApps.js | 16 +++++++++++++--- mobile/android/chrome/content/aboutApps.xhtml | 10 +++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/mobile/android/chrome/content/aboutApps.js b/mobile/android/chrome/content/aboutApps.js index 343077e429a4..f2cc471425b1 100644 --- a/mobile/android/chrome/content/aboutApps.js +++ b/mobile/android/chrome/content/aboutApps.js @@ -23,15 +23,17 @@ XPCOMUtils.defineLazyGetter(window, "gChromeWin", function() .getInterface(Ci.nsIDOMWindow) .QueryInterface(Ci.nsIDOMChromeWindow)); +document.addEventListener("DOMContentLoaded", onLoad, false); + var AppsUI = { uninstall: null, shortcut: null }; -function openLink(aElement) { +function openLink(aEvent) { try { let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); - let url = formatter.formatURLPref(aElement.getAttribute("pref")); + let url = formatter.formatURLPref(aEvent.target.getAttribute("pref")); let BrowserApp = gChromeWin.BrowserApp; BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id }); } catch (ex) {} @@ -41,7 +43,9 @@ var ContextMenus = { target: null, init: function() { - document.addEventListener("contextmenu", ContextMenus, false); + document.addEventListener("contextmenu", this, false); + document.getElementById("addToHomescreenLabel").addEventListener("click", this.addToHomescreen, false); + document.getElementById("uninstallLabel").addEventListener("click", this.uninstall, false); }, handleEvent: function(event) { @@ -82,6 +86,11 @@ function onLoad(aEvent) { link.setAttribute("href", url); } catch (e) {} + let elmts = document.querySelectorAll("[pref]"); + for (let i = 0; i < elmts.length; i++) { + elmts[i]..addEventListener("click", openLink, false)); + } + navigator.mozApps.mgmt.oninstall = onInstall; navigator.mozApps.mgmt.onuninstall = onUninstall; updateList(); @@ -155,3 +164,4 @@ function onUninstall(aEvent) { document.getElementById("main-container").classList.add("hidden"); } } + diff --git a/mobile/android/chrome/content/aboutApps.xhtml b/mobile/android/chrome/content/aboutApps.xhtml index d863aaa9db51..13eba766a489 100644 --- a/mobile/android/chrome/content/aboutApps.xhtml +++ b/mobile/android/chrome/content/aboutApps.xhtml @@ -27,16 +27,16 @@ - + - - + +
&aboutApps.header;
-
+
-
+
&aboutApps.browseMarketplace;
From 5f91122058f629c7ddd451acaeef392ae93a7a79 Mon Sep 17 00:00:00 2001 From: Albert Juhe Date: Wed, 18 Dec 2013 12:40:36 -0500 Subject: [PATCH 13/36] Bug 950667 - DevTools CSS - Use an attribute instead of .highlighted class for styling tabs like the paused debugger. r=bgrins --- browser/devtools/framework/toolbox.js | 4 +- .../themes/shared/devtools/toolbars.inc.css | 38 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/browser/devtools/framework/toolbox.js b/browser/devtools/framework/toolbox.js index dcfb9c07a804..11f0bbe29bfc 100644 --- a/browser/devtools/framework/toolbox.js +++ b/browser/devtools/framework/toolbox.js @@ -796,7 +796,7 @@ Toolbox.prototype = { */ highlightTool: function(id) { let tab = this.doc.getElementById("toolbox-tab-" + id); - tab && tab.classList.add("highlighted"); + tab && tab.setAttribute("highlighted", "true"); }, /** @@ -807,7 +807,7 @@ Toolbox.prototype = { */ unhighlightTool: function(id) { let tab = this.doc.getElementById("toolbox-tab-" + id); - tab && tab.classList.remove("highlighted"); + tab && tab.removeAttribute("highlighted"); }, /** diff --git a/browser/themes/shared/devtools/toolbars.inc.css b/browser/themes/shared/devtools/toolbars.inc.css index 7e418b90c5f1..8b76d577bb91 100644 --- a/browser/themes/shared/devtools/toolbars.inc.css +++ b/browser/themes/shared/devtools/toolbars.inc.css @@ -51,7 +51,7 @@ display: none; } -.devtools-toolbarbutton:not([checked=true]):hover:active { +.devtools-toolbarbutton:not([checked]):hover:active { border-color: hsla(210,8%,5%,.6); background: linear-gradient(hsla(220,6%,10%,.3), hsla(212,7%,57%,.15) 65%, hsla(212,7%,57%,.3)); box-shadow: 0 0 3px hsla(210,8%,5%,.25) inset, 0 1px 3px hsla(210,8%,5%,.25) inset, 0 1px 0 hsla(210,16%,76%,.15); @@ -282,28 +282,28 @@ background-image: linear-gradient(hsla(206,37%,4%,.4), hsla(206,37%,4%,.4)), @smallSeparator@; } -.devtools-sidebar-tabs > tabs > tab[selected=true] + tab { +.devtools-sidebar-tabs > tabs > tab[selected] + tab { background-image: linear-gradient(transparent, transparent), @solidSeparator@; } -.devtools-sidebar-tabs > tabs > tab[selected=true] + tab:hover { +.devtools-sidebar-tabs > tabs > tab[selected] + tab:hover { background-image: linear-gradient(hsla(206,37%,4%,.2), hsla(206,37%,4%,.2)), @solidSeparator@; } -.devtools-sidebar-tabs > tabs > tab[selected=true] + tab:hover:active { +.devtools-sidebar-tabs > tabs > tab[selected] + tab:hover:active { background-image: linear-gradient(hsla(206,37%,4%,.4), hsla(206,37%,4%,.4)), @solidSeparator@; } -.devtools-sidebar-tabs > tabs > tab[selected=true] { +.devtools-sidebar-tabs > tabs > tab[selected] { color: #f5f7fa; background-image: linear-gradient(#1d4f73, #1d4f73), @solidSeparator@; } -.devtools-sidebar-tabs > tabs > tab[selected=true]:hover { +.devtools-sidebar-tabs > tabs > tab[selected]:hover { background-image: linear-gradient(#274f64, #274f64), @solidSeparator@; } -.devtools-sidebar-tabs > tabs > tab[selected=true]:hover:active { +.devtools-sidebar-tabs > tabs > tab[selected]:hover:active { background-image: linear-gradient(#1f3e4f, #1f3e4f), @solidSeparator@; } @@ -520,7 +520,7 @@ } .devtools-tab:active > image, -.devtools-tab[selected=true] > image { +.devtools-tab[selected] > image { opacity: 1; } @@ -534,7 +534,7 @@ color: #f5f7fa; } -#toolbox-tabs .devtools-tab[selected=true] { +#toolbox-tabs .devtools-tab[selected] { color: #f5f7fa; background-color: #1a4666; box-shadow: 0 2px 0 #d7f1ff inset, @@ -542,32 +542,32 @@ 0 -2px 0 rgba(0,0,0,.2) inset; } -.devtools-tab[selected=true]:not(:first-child), -.devtools-tab.highlighted:not(:first-child) { +.devtools-tab[selected]:not(:first-child), +.devtools-tab[highlighted]:not(:first-child) { border-width: 0; -moz-padding-start: 1px; } -.devtools-tab[selected=true]:last-child, -.devtools-tab.highlighted:last-child { +.devtools-tab[selected]:last-child, +.devtools-tab[highlighted]:last-child { -moz-padding-end: 1px; } -.devtools-tab[selected=true] + .devtools-tab, -.devtools-tab.highlighted + .devtools-tab { +.devtools-tab[selected] + .devtools-tab, +.devtools-tab[highlighted] + .devtools-tab { -moz-border-start-width: 0; -moz-padding-start: 1px; } -.devtools-tab:not([selected=true]).highlighted { +.devtools-tab:not([selected])[highlighted] { color: #f5f7fa; background-color: hsla(99,100%,14%,.2); box-shadow: 0 2px 0 #7bc107 inset; } -.devtools-tab:not(.highlighted) > .highlighted-icon, -.devtools-tab[selected=true] > .highlighted-icon, -.devtools-tab:not([selected=true]).highlighted > .default-icon { +.devtools-tab:not([highlighted]) > .highlighted-icon, +.devtools-tab[selected] > .highlighted-icon, +.devtools-tab:not([selected])[highlighted] > .default-icon { visibility: collapse; } From 85f83be255add667d93fb0560515013654fbd541 Mon Sep 17 00:00:00 2001 From: Jed Parsons Date: Tue, 17 Dec 2013 11:14:11 -0800 Subject: [PATCH 14/36] Bug 951118 - TypeError when null account is verified. r=ferjm --- services/fxaccounts/FxAccounts.jsm | 2 +- .../tests/xpcshell/test_accounts.js | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm index 542f54283a0f..1fef2944ee65 100644 --- a/services/fxaccounts/FxAccounts.jsm +++ b/services/fxaccounts/FxAccounts.jsm @@ -144,7 +144,7 @@ InternalMethods.prototype = { } if (!this.whenKeysReadyPromise) { this.whenKeysReadyPromise = Promise.defer(); - this.fetchAndUnwrapKeys(data.keyFetchToken) + return this.fetchAndUnwrapKeys(data.keyFetchToken) .then((data) => { if (this.whenKeysReadyPromise) { this.whenKeysReadyPromise.resolve(data); diff --git a/services/fxaccounts/tests/xpcshell/test_accounts.js b/services/fxaccounts/tests/xpcshell/test_accounts.js index 888432a58ed2..64089d9f9854 100644 --- a/services/fxaccounts/tests/xpcshell/test_accounts.js +++ b/services/fxaccounts/tests/xpcshell/test_accounts.js @@ -262,7 +262,8 @@ add_test(function test_polling_timeout() { }); }); -add_task(function test_getKeys() { +add_test(function test_getKeys() { + do_test_pending(); let fxa = new MockFxAccounts(); let user = getTestUser("eusebius"); @@ -272,19 +273,21 @@ add_task(function test_getKeys() { fxa.setSignedInUser(user).then(() => { fxa.getSignedInUser().then((user) => { // Before getKeys, we have no keys - do_check_eq(!!data.kA, false); - do_check_eq(!!data.kB, false); + do_check_eq(!!user.kA, false); + do_check_eq(!!user.kB, false); // And we still have a key-fetch token to use - do_check_eq(!!data.keyFetchToken, true); + do_check_eq(!!user.keyFetchToken, true); fxa.internal.getKeys().then(() => { fxa.getSignedInUser().then((user) => { // Now we should have keys - do_check_eq(fxa.internal.isUserEmailVerified(data), true); - do_check_eq(!!data.isVerified, true); - do_check_eq(data.kA, expandHex("11")); - do_check_eq(data.kB, expandHex("66")); - do_check_eq(data.keyFetchToken, undefined); + do_check_eq(fxa.internal.isUserEmailVerified(user), true); + do_check_eq(!!user.isVerified, true); + do_check_eq(user.kA, expandHex("11")); + do_check_eq(user.kB, expandHex("66")); + do_check_eq(user.keyFetchToken, undefined); + do_test_finished(); + run_next_test(); }); }); }); From 4bae8066d93e91d533a2d31d3138fd1643a7fc9f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 17 Dec 2013 22:34:23 -0600 Subject: [PATCH 15/36] Bug 951518 - Added missing ending whitespace to help text. r=flod --- .../locales/en-US/chrome/browser/devtools/connection-screen.dtd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/locales/en-US/chrome/browser/devtools/connection-screen.dtd b/browser/locales/en-US/chrome/browser/devtools/connection-screen.dtd index 9e3ffe1c61dc..7bcd3c26c851 100644 --- a/browser/locales/en-US/chrome/browser/devtools/connection-screen.dtd +++ b/browser/locales/en-US/chrome/browser/devtools/connection-screen.dtd @@ -23,7 +23,7 @@ - + From 0d0b379772d6d6f1c83f17c07043957cb7dbe974 Mon Sep 17 00:00:00 2001 From: Paul Rouget Date: Wed, 18 Dec 2013 12:41:28 -0500 Subject: [PATCH 16/36] Bug 951658 - Kill the inspector front even if walker.release() failed. r=pbrosset --- browser/devtools/inspector/inspector-panel.js | 6 +++++- toolkit/devtools/server/protocol.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/browser/devtools/inspector/inspector-panel.js b/browser/devtools/inspector/inspector-panel.js index e55ed90095db..7416acd2caff 100644 --- a/browser/devtools/inspector/inspector-panel.js +++ b/browser/devtools/inspector/inspector-panel.js @@ -515,7 +515,11 @@ InspectorPanel.prototype = { if (this.walker) { this.walker.off("new-root", this.onNewRoot); this._destroyPromise = this.walker.release() - .then(() => this._inspector.destroy()) + .then(() => this._inspector.destroy(), + (e) => { + console.error("Walker.release() failed: " + e); + return this._inspector.destroy(); + }) .then(() => { this._inspector = null; }, console.error); diff --git a/toolkit/devtools/server/protocol.js b/toolkit/devtools/server/protocol.js index 4bfe63ba2240..9632e75ec846 100644 --- a/toolkit/devtools/server/protocol.js +++ b/toolkit/devtools/server/protocol.js @@ -1058,7 +1058,7 @@ let Front = Class({ // Remaining packets must be responses. if (this._requests.length === 0) { - let msg = "Unexpected packet from " + this.actorID + ", " + packet.type; + let msg = "Unexpected packet " + this.actorID + ", " + JSON.stringify(packet); let err = Error(msg); console.error(err); throw err; From 9e19463e0b7e273ad64023c52334fd265545f0da Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Wed, 18 Dec 2013 09:55:32 -0800 Subject: [PATCH 17/36] Bug 946656 - Add debug output to ToolbarTitleTextChangeVerifier. r=margaret --- .../base/tests/helpers/WaitHelper.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/mobile/android/base/tests/helpers/WaitHelper.java b/mobile/android/base/tests/helpers/WaitHelper.java index 860a4ce54d36..41ef34989131 100644 --- a/mobile/android/base/tests/helpers/WaitHelper.java +++ b/mobile/android/base/tests/helpers/WaitHelper.java @@ -103,9 +103,8 @@ public final class WaitHelper { } }, CHANGE_WAIT_MS); - if (hasTimedOut) { - sContext.dumpLog(verifier.getClass().getName() + " timed out."); - } + sContext.dumpLog(verifier.getLogTag() + + (hasTimedOut ? "timed out." : "was satisfied.")); } } @@ -115,6 +114,8 @@ public final class WaitHelper { * returned from hasStateChanged, indicating this change of status. */ private static interface ChangeVerifier { + public String getLogTag(); + /** * Stores the initial state of the system. This system state is used to diff against * the end state to determine if the system has changed. Since this is just a diff @@ -126,14 +127,23 @@ public final class WaitHelper { } private static class ToolbarTitleTextChangeVerifier implements ChangeVerifier { + private static final String LOGTAG = + ToolbarTitleTextChangeVerifier.class.getSimpleName() + ": "; + // A regex that matches the page title that shows up while the page is loading. private static final String LOADING_REGEX = "^[A-Za-z]{3,9}://"; - private CharSequence oldTitleText; + private CharSequence mOldTitleText; + + @Override + public String getLogTag() { + return LOGTAG; + } @Override public void storeState() { - oldTitleText = sToolbar.getPotentiallyInconsistentTitle(); + mOldTitleText = sToolbar.getPotentiallyInconsistentTitle(); + sContext.dumpLog(LOGTAG + "stored title, \"" + mOldTitleText + "\"."); } @Override @@ -148,7 +158,12 @@ public final class WaitHelper { // loaded from the server and set as the final page title; we ignore the // intermediate URL loading state here. final boolean isLoading = title.toString().matches(LOADING_REGEX); - return !isLoading && !oldTitleText.equals(title); + final boolean hasStateChanged = !isLoading && !mOldTitleText.equals(title); + + if (hasStateChanged) { + sContext.dumpLog(LOGTAG + "state changed to title, \"" + title + "\"."); + } + return hasStateChanged; } } } From 338bb63885ef54cb858127475ad4969320ffe016 Mon Sep 17 00:00:00 2001 From: Irving Reid Date: Wed, 18 Dec 2013 12:24:52 -0500 Subject: [PATCH 18/36] Bug 883739 - Set the correct pref so test_update.js does not access services.a.m.o. r=Mossop --- toolkit/mozapps/extensions/test/xpcshell/head_addons.js | 2 ++ .../extensions/test/xpcshell/test_AddonRepository.js | 1 - .../extensions/test/xpcshell/test_AddonRepository_cache.js | 6 ++---- .../extensions/test/xpcshell/test_compatoverrides.js | 3 +-- toolkit/mozapps/extensions/test/xpcshell/test_install.js | 2 +- .../extensions/test/xpcshell/test_install_strictcompat.js | 2 +- toolkit/mozapps/extensions/test/xpcshell/test_update.js | 3 ++- .../extensions/test/xpcshell/test_update_ignorecompat.js | 1 - .../extensions/test/xpcshell/test_update_strictcompat.js | 1 - 9 files changed, 9 insertions(+), 12 deletions(-) diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index c46d159a3ab7..ec9f701192ae 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -12,6 +12,8 @@ const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion"; const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion"; +const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; +const PREF_GETADDONS_BYIDS_PERFORMANCE = "extensions.getAddons.getWithPerformance.url"; // Forcibly end the test if it runs longer than 15 minutes const TIMEOUT_MS = 900000; diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js index f9c0470ee86f..ae3ca152a975 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js @@ -11,7 +11,6 @@ var gServer = new HttpServer(); gServer.start(-1); const PREF_GETADDONS_BROWSEADDONS = "extensions.getAddons.browseAddons"; -const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; const PREF_GETADDONS_BROWSERECOMMENDED = "extensions.getAddons.recommended.browseURL"; const PREF_GETADDONS_GETRECOMMENDED = "extensions.getAddons.recommended.url"; const PREF_GETADDONS_BROWSESEARCHRESULTS = "extensions.getAddons.search.browseURL"; diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js index 600f3d81b3f0..439d368bb6eb 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js @@ -14,8 +14,6 @@ const BASE_URL = "http://localhost:" + PORT; const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; const PREF_GETADDONS_CACHE_TYPES = "extensions.getAddons.cache.types"; -const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; -const PREF_GETADDONS_BYIDS_PERF = "extensions.getAddons.getWithPerformance.url"; const GETADDONS_RESULTS = BASE_URL + "/data/test_AddonRepository_cache.xml"; const GETADDONS_EMPTY = BASE_URL + "/data/test_AddonRepository_empty.xml"; const GETADDONS_FAILED = BASE_URL + "/data/test_AddonRepository_failed.xml"; @@ -705,7 +703,7 @@ function run_test_12() { // database, and that XPI add-ons still do not use any of repository properties function run_test_13() { check_database_exists(true); - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERF, GETADDONS_EMPTY); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, GETADDONS_EMPTY); trigger_background_update(function() { // Database should have been deleted @@ -738,7 +736,7 @@ function run_test_14() { // Tests that the XPI add-ons correctly use the repository properties when // caching is enabled and the repository information is available function run_test_15() { - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERF, GETADDONS_RESULTS); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, GETADDONS_RESULTS); trigger_background_update(function() { AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) { diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_compatoverrides.js b/toolkit/mozapps/extensions/test/xpcshell/test_compatoverrides.js index ea695c97ad6b..63a79ea1e5ca 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_compatoverrides.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_compatoverrides.js @@ -6,7 +6,6 @@ const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; -const PREF_GETADDONS_BYIDS = "extensions.getAddons.getWithPerformance.url"; Components.utils.import("resource://testing-common/httpd.js"); var gServer = new HttpServer(); @@ -23,7 +22,7 @@ mapUrlToFile(REQ_URL, do_get_file("data/test_compatoverrides.xml"), gServer); Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); -Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, +Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, BASE_URL + REQ_URL); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_install.js index cc85c053b9ae..b3537059a981 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_install.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js @@ -1233,7 +1233,7 @@ function run_test_18_1() { restartManager(); Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", true); - Services.prefs.setCharPref("extensions.getAddons.get.url", + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, "http://localhost:" + gPort + "/data/test_install.xml"); Services.prefs.setBoolPref("extensions.addon2@tests.mozilla.org.getAddons.cache.enabled", false); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js index bcebc74c83c2..50b65d82a6b3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js @@ -1224,7 +1224,7 @@ function run_test_18_1() { restartManager(); Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", true); - Services.prefs.setCharPref("extensions.getAddons.get.url", + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, "http://localhost:4444/data/test_install.xml"); Services.prefs.setBoolPref("extensions.addon2@tests.mozilla.org.getAddons.cache.enabled", false); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_update.js index 793a8d545521..e7a77200a07a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js @@ -6,7 +6,6 @@ const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; const PREF_SELECTED_LOCALE = "general.useragent.locale"; -const PREF_GETADDONS_BYIDS_PERFORMANCE = "extensions.getAddons.getWithPerformance.url"; const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. @@ -1178,6 +1177,8 @@ function run_test_17() { } }); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, + "http://localhost:" + gPort + "/data/test_update.xml"); Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, "http://localhost:" + gPort + "/data/test_update.xml"); Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js index 76415bb73068..11cd5d8bd508 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js @@ -5,7 +5,6 @@ // This verifies that add-on update checks work correctly when compatibility // check is disabled. -const PREF_GETADDONS_BYIDS_PERFORMANCE = "extensions.getAddons.getWithPerformance.url"; const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js index 03e94d9a9a93..f875f59342a7 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js @@ -6,7 +6,6 @@ const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; const PREF_SELECTED_LOCALE = "general.useragent.locale"; -const PREF_GETADDONS_BYIDS_PERFORMANCE = "extensions.getAddons.getWithPerformance.url"; const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. From ca20501af9a946e95764d0c51d54e61048ca9749 Mon Sep 17 00:00:00 2001 From: Jordan Santell Date: Wed, 18 Dec 2013 13:18:10 -0500 Subject: [PATCH 19/36] Bug 950873 - Switch Social.jsm to use asynchronous Promise.jsm. r=markh --- browser/modules/Social.jsm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/modules/Social.jsm b/browser/modules/Social.jsm index 3e79873dfc36..ce886c2145b0 100644 --- a/browser/modules/Social.jsm +++ b/browser/modules/Social.jsm @@ -26,7 +26,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "SocialService", XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyServiceGetter(this, "unescapeService", "@mozilla.org/feed-unescapehtml;1", From 1d4b436e261e895921f077e404d73e519ca11294 Mon Sep 17 00:00:00 2001 From: Paul Rouget Date: Wed, 18 Dec 2013 13:18:25 -0500 Subject: [PATCH 20/36] Bug 951609 - Fix scope issues in child.js. r=ochameau --- toolkit/devtools/server/child.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/toolkit/devtools/server/child.js b/toolkit/devtools/server/child.js index 4ef80e6c1b63..43907d187269 100644 --- a/toolkit/devtools/server/child.js +++ b/toolkit/devtools/server/child.js @@ -1,6 +1,5 @@ -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); -Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm"); +const {DevToolsUtils} = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {}); +const {DebuggerServer, ActorPool} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {}); if (!DebuggerServer.initialized) { DebuggerServer.init(); From 647bfdcb6ed90035b2411eb015a1f7ccea4d4dcd Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Wed, 18 Dec 2013 20:07:15 +0100 Subject: [PATCH 21/36] Backed out changeset eb4d2915ab26 (bug 951674) --- browser/devtools/shared/widgets/VariablesView.jsm | 2 ++ browser/themes/linux/devtools/debugger.css | 4 ++-- browser/themes/linux/devtools/widgets.css | 6 +++--- browser/themes/osx/devtools/debugger.css | 4 ++-- browser/themes/osx/devtools/widgets.css | 6 +++--- browser/themes/shared/devtools/common.css | 14 +++++++------- browser/themes/shared/devtools/profiler.inc.css | 6 +++--- .../themes/shared/devtools/shadereditor.inc.css | 4 ++-- browser/themes/windows/devtools/debugger.css | 4 ++-- browser/themes/windows/devtools/widgets.css | 6 +++--- 10 files changed, 29 insertions(+), 27 deletions(-) diff --git a/browser/devtools/shared/widgets/VariablesView.jsm b/browser/devtools/shared/widgets/VariablesView.jsm index 2c2b4ba20520..233360add63d 100644 --- a/browser/devtools/shared/widgets/VariablesView.jsm +++ b/browser/devtools/shared/widgets/VariablesView.jsm @@ -907,6 +907,7 @@ VariablesView.prototype = { label.className = "variables-view-empty-notice"; label.setAttribute("value", this._emptyTextValue); + this._parent.setAttribute("empty", ""); this._parent.appendChild(label); this._emptyTextNode = label; }, @@ -919,6 +920,7 @@ VariablesView.prototype = { return; } + this._parent.removeAttribute("empty"); this._parent.removeChild(this._emptyTextNode); this._emptyTextNode = null; }, diff --git a/browser/themes/linux/devtools/debugger.css b/browser/themes/linux/devtools/debugger.css index a4686aef98d8..2cbee25a5dbd 100644 --- a/browser/themes/linux/devtools/debugger.css +++ b/browser/themes/linux/devtools/debugger.css @@ -10,7 +10,7 @@ } #sources-and-editor-splitter { - border-color: transparent; + -moz-border-start-color: transparent; } /* Sources toolbar */ @@ -374,7 +374,7 @@ } #globalsearch + .devtools-horizontal-splitter { - border-color: #bfbfbf; + -moz-border-top-colors: #bfbfbf; } .dbg-source-results { diff --git a/browser/themes/linux/devtools/widgets.css b/browser/themes/linux/devtools/widgets.css index 2d8d1a11bb3b..626a53bcb39c 100644 --- a/browser/themes/linux/devtools/widgets.css +++ b/browser/themes/linux/devtools/widgets.css @@ -270,7 +270,7 @@ .side-menu-widget-container { /* Hack: force hardware acceleration */ - transform: translateZ(1px); + transform: translateX(0px); } .side-menu-widget-container[theme="dark"] { @@ -436,9 +436,9 @@ /* VariablesView */ -.variables-view-container { +.variables-view-container:not([empty]) { /* Hack: force hardware acceleration */ - transform: translateZ(1px); + transform: translateX(1px); } .variables-view-empty-notice { diff --git a/browser/themes/osx/devtools/debugger.css b/browser/themes/osx/devtools/debugger.css index 808775e3eed9..ffcb34e03397 100644 --- a/browser/themes/osx/devtools/debugger.css +++ b/browser/themes/osx/devtools/debugger.css @@ -12,7 +12,7 @@ } #sources-and-editor-splitter { - border-color: transparent; + -moz-border-start-color: transparent; } /* Sources toolbar */ @@ -376,7 +376,7 @@ } #globalsearch + .devtools-horizontal-splitter { - border-color: #bfbfbf; + -moz-border-top-colors: #bfbfbf; } .dbg-source-results { diff --git a/browser/themes/osx/devtools/widgets.css b/browser/themes/osx/devtools/widgets.css index 592387f3ba1f..d4ba3b294628 100644 --- a/browser/themes/osx/devtools/widgets.css +++ b/browser/themes/osx/devtools/widgets.css @@ -270,7 +270,7 @@ .side-menu-widget-container { /* Hack: force hardware acceleration */ - transform: translateZ(1px); + transform: translateX(0px); } .side-menu-widget-container[theme="dark"] { @@ -430,9 +430,9 @@ /* VariablesView */ -.variables-view-container { +.variables-view-container:not([empty]) { /* Hack: force hardware acceleration */ - transform: translateZ(1px); + transform: translateX(1px); } .variables-view-empty-notice { diff --git a/browser/themes/shared/devtools/common.css b/browser/themes/shared/devtools/common.css index 1cf692ae987d..df70486fbb63 100644 --- a/browser/themes/shared/devtools/common.css +++ b/browser/themes/shared/devtools/common.css @@ -26,23 +26,23 @@ -moz-appearance: none; background-image: none; background-color: transparent; - border: 0; - border-bottom: 1px solid black; + border: 1px solid black; + border-width: 1px 0 0 0; min-height: 3px; height: 3px; - margin-top: -2px; + margin-bottom: -3px; position: relative; } .devtools-side-splitter { -moz-appearance: none; background-image: none; - background-color: transparent; border: 0; - -moz-border-end: 1px solid black; - min-width: 3px; + -moz-border-start: 1px solid black; + min-width: 0; width: 3px; - -moz-margin-start: -3px; + background-color: transparent; + -moz-margin-end: -3px; position: relative; cursor: e-resize; } diff --git a/browser/themes/shared/devtools/profiler.inc.css b/browser/themes/shared/devtools/profiler.inc.css index 4ef3ca3ca713..ed6c994bbc07 100644 --- a/browser/themes/shared/devtools/profiler.inc.css +++ b/browser/themes/shared/devtools/profiler.inc.css @@ -13,7 +13,7 @@ } .devtools-toolbar { - min-height: 33px; + min-height: 33px; } .profiler-sidebar { @@ -21,7 +21,7 @@ } .profiler-sidebar + .devtools-side-splitter { - border-color: transparent; + -moz-border-start-color: transparent; } .profiler-sidebar-item { @@ -86,4 +86,4 @@ #profiler-start[checked] { -moz-image-region: rect(0px,32px,16px,16px); -} +} \ No newline at end of file diff --git a/browser/themes/shared/devtools/shadereditor.inc.css b/browser/themes/shared/devtools/shadereditor.inc.css index 5dc226ffe46a..ef93a59b399d 100644 --- a/browser/themes/shared/devtools/shadereditor.inc.css +++ b/browser/themes/shared/devtools/shadereditor.inc.css @@ -46,7 +46,7 @@ } #shaders-pane + .devtools-side-splitter { - border-color: transparent; + -moz-border-start-color: transparent; } .side-menu-widget-item-checkbox { @@ -88,7 +88,7 @@ /* Shader source editors */ #editors-splitter { - border-color: rgb(61,69,76); + -moz-border-start-color: rgb(61,69,76); } .editor-label { diff --git a/browser/themes/windows/devtools/debugger.css b/browser/themes/windows/devtools/debugger.css index e1221e8daf5b..0087c30aafea 100644 --- a/browser/themes/windows/devtools/debugger.css +++ b/browser/themes/windows/devtools/debugger.css @@ -10,7 +10,7 @@ } #sources-and-editor-splitter { - border-color: transparent; + -moz-border-start-color: transparent; } /* Sources toolbar */ @@ -374,7 +374,7 @@ } #globalsearch + .devtools-horizontal-splitter { - border-color: #bfbfbf; + -moz-border-top-colors: #bfbfbf; } .dbg-source-results { diff --git a/browser/themes/windows/devtools/widgets.css b/browser/themes/windows/devtools/widgets.css index 9df4907700c0..10a3c7c973e3 100644 --- a/browser/themes/windows/devtools/widgets.css +++ b/browser/themes/windows/devtools/widgets.css @@ -274,7 +274,7 @@ .side-menu-widget-container { /* Hack: force hardware acceleration */ - transform: translateZ(1px); + transform: translateX(0px); } .side-menu-widget-container[theme="dark"] { @@ -433,9 +433,9 @@ /* VariablesView */ -.variables-view-container { +.variables-view-container:not([empty]) { /* Hack: force hardware acceleration */ - transform: translateZ(1px); + transform: translateX(1px); } .variables-view-empty-notice { From df679aa05e1a1d71178be08065cf7c1188677435 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Wed, 18 Dec 2013 20:07:41 +0100 Subject: [PATCH 22/36] Backed out changeset 40978aa5238d (bug 951694) --- browser/themes/linux/devtools/debugger.css | 7 +++++-- browser/themes/linux/devtools/widgets.css | 1 + browser/themes/osx/devtools/debugger.css | 7 +++++-- browser/themes/osx/devtools/widgets.css | 1 + browser/themes/windows/devtools/debugger.css | 7 +++++-- browser/themes/windows/devtools/widgets.css | 1 + 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/browser/themes/linux/devtools/debugger.css b/browser/themes/linux/devtools/debugger.css index 2cbee25a5dbd..ee736ec8fd85 100644 --- a/browser/themes/linux/devtools/debugger.css +++ b/browser/themes/linux/devtools/debugger.css @@ -5,6 +5,10 @@ /* Sources and breakpoints pane */ +#sources-pane { + min-width: 50px; +} + #sources-pane > tabs { -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */ } @@ -68,8 +72,7 @@ #black-boxed-message, #source-progress-container { background: url(background-noise-toolbar.png) rgb(61,69,76); - /* Prevent the container deck from aquiring the size from this message. */ - min-width: 1px; + /* Prevent the container deck from aquiring the height from this message. */ min-height: 1px; color: white; } diff --git a/browser/themes/linux/devtools/widgets.css b/browser/themes/linux/devtools/widgets.css index 626a53bcb39c..8342525997d9 100644 --- a/browser/themes/linux/devtools/widgets.css +++ b/browser/themes/linux/devtools/widgets.css @@ -6,6 +6,7 @@ /* Generic pane helpers */ .generic-toggled-side-pane { + min-width: 50px; -moz-margin-start: 0px !important; /* Unfortunately, transitions don't work properly with locale-aware properties, so both the left and right margins are set via js, while the start margin diff --git a/browser/themes/osx/devtools/debugger.css b/browser/themes/osx/devtools/debugger.css index ffcb34e03397..55dfeee48bde 100644 --- a/browser/themes/osx/devtools/debugger.css +++ b/browser/themes/osx/devtools/debugger.css @@ -7,6 +7,10 @@ /* Sources and breakpoints pane */ +#sources-pane { + min-width: 50px; +} + #sources-pane > tabs { -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */ } @@ -70,8 +74,7 @@ #black-boxed-message, #source-progress-container { background: url(background-noise-toolbar.png) rgb(61,69,76); - /* Prevent the container deck from aquiring the size from this message. */ - min-width: 1px; + /* Prevent the container deck from aquiring the height from this message. */ min-height: 1px; color: white; } diff --git a/browser/themes/osx/devtools/widgets.css b/browser/themes/osx/devtools/widgets.css index d4ba3b294628..6b8b33893f60 100644 --- a/browser/themes/osx/devtools/widgets.css +++ b/browser/themes/osx/devtools/widgets.css @@ -6,6 +6,7 @@ /* Generic pane helpers */ .generic-toggled-side-pane { + min-width: 50px; -moz-margin-start: 0px !important; /* Unfortunately, transitions don't work properly with locale-aware properties, so both the left and right margins are set via js, while the start margin diff --git a/browser/themes/windows/devtools/debugger.css b/browser/themes/windows/devtools/debugger.css index 0087c30aafea..6343a0fd5a40 100644 --- a/browser/themes/windows/devtools/debugger.css +++ b/browser/themes/windows/devtools/debugger.css @@ -5,6 +5,10 @@ /* Sources and breakpoints pane */ +#sources-pane { + min-width: 50px; +} + #sources-pane > tabs { -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */ } @@ -68,8 +72,7 @@ #black-boxed-message, #source-progress-container { background: url(background-noise-toolbar.png) rgb(61,69,76); - /* Prevent the container deck from aquiring the size from this message. */ - min-width: 1px; + /* Prevent the container deck from aquiring the height from this message. */ min-height: 1px; color: white; } diff --git a/browser/themes/windows/devtools/widgets.css b/browser/themes/windows/devtools/widgets.css index 10a3c7c973e3..e154432dd754 100644 --- a/browser/themes/windows/devtools/widgets.css +++ b/browser/themes/windows/devtools/widgets.css @@ -6,6 +6,7 @@ /* Generic pane helpers */ .generic-toggled-side-pane { + min-width: 50px; -moz-margin-start: 0px !important; /* Unfortunately, transitions don't work properly with locale-aware properties, so both the left and right margins are set via js, while the start margin From 27296a14082e0cf5d996b1e7e7bfc6b8c2edabf7 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Wed, 18 Dec 2013 19:57:24 +0100 Subject: [PATCH 23/36] Bug 899276 - Tiny fix for mochitest-bc bustage on a CLOSED TREE r=me From 18d8469a4ef87d65397f171e9c3798e3ac6f6c12 Mon Sep 17 00:00:00 2001 --- browser/components/sessionstore/test/browser_privatetabs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/components/sessionstore/test/browser_privatetabs.js b/browser/components/sessionstore/test/browser_privatetabs.js index 87a2d6a9f760..ec43bd7f8d09 100644 --- a/browser/components/sessionstore/test/browser_privatetabs.js +++ b/browser/components/sessionstore/test/browser_privatetabs.js @@ -3,7 +3,7 @@ let Imports = {}; Cu.import("resource:///modules/sessionstore/SessionSaver.jsm", Imports); -let {Promise, Task, SessionSaver} = Imports; +let {SessionSaver} = Imports; add_task(function cleanup() { info("Forgetting closed tabs"); From 741546f1cadeef20163906410000bf04a7778e2f Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Wed, 18 Dec 2013 20:23:46 +0100 Subject: [PATCH 24/36] Backed out changeset 55d8f5ede1d3 (bug 950667) on a CLOSED TREE --- browser/devtools/framework/toolbox.js | 4 +- .../themes/shared/devtools/toolbars.inc.css | 38 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/browser/devtools/framework/toolbox.js b/browser/devtools/framework/toolbox.js index 11f0bbe29bfc..dcfb9c07a804 100644 --- a/browser/devtools/framework/toolbox.js +++ b/browser/devtools/framework/toolbox.js @@ -796,7 +796,7 @@ Toolbox.prototype = { */ highlightTool: function(id) { let tab = this.doc.getElementById("toolbox-tab-" + id); - tab && tab.setAttribute("highlighted", "true"); + tab && tab.classList.add("highlighted"); }, /** @@ -807,7 +807,7 @@ Toolbox.prototype = { */ unhighlightTool: function(id) { let tab = this.doc.getElementById("toolbox-tab-" + id); - tab && tab.removeAttribute("highlighted"); + tab && tab.classList.remove("highlighted"); }, /** diff --git a/browser/themes/shared/devtools/toolbars.inc.css b/browser/themes/shared/devtools/toolbars.inc.css index 8b76d577bb91..7e418b90c5f1 100644 --- a/browser/themes/shared/devtools/toolbars.inc.css +++ b/browser/themes/shared/devtools/toolbars.inc.css @@ -51,7 +51,7 @@ display: none; } -.devtools-toolbarbutton:not([checked]):hover:active { +.devtools-toolbarbutton:not([checked=true]):hover:active { border-color: hsla(210,8%,5%,.6); background: linear-gradient(hsla(220,6%,10%,.3), hsla(212,7%,57%,.15) 65%, hsla(212,7%,57%,.3)); box-shadow: 0 0 3px hsla(210,8%,5%,.25) inset, 0 1px 3px hsla(210,8%,5%,.25) inset, 0 1px 0 hsla(210,16%,76%,.15); @@ -282,28 +282,28 @@ background-image: linear-gradient(hsla(206,37%,4%,.4), hsla(206,37%,4%,.4)), @smallSeparator@; } -.devtools-sidebar-tabs > tabs > tab[selected] + tab { +.devtools-sidebar-tabs > tabs > tab[selected=true] + tab { background-image: linear-gradient(transparent, transparent), @solidSeparator@; } -.devtools-sidebar-tabs > tabs > tab[selected] + tab:hover { +.devtools-sidebar-tabs > tabs > tab[selected=true] + tab:hover { background-image: linear-gradient(hsla(206,37%,4%,.2), hsla(206,37%,4%,.2)), @solidSeparator@; } -.devtools-sidebar-tabs > tabs > tab[selected] + tab:hover:active { +.devtools-sidebar-tabs > tabs > tab[selected=true] + tab:hover:active { background-image: linear-gradient(hsla(206,37%,4%,.4), hsla(206,37%,4%,.4)), @solidSeparator@; } -.devtools-sidebar-tabs > tabs > tab[selected] { +.devtools-sidebar-tabs > tabs > tab[selected=true] { color: #f5f7fa; background-image: linear-gradient(#1d4f73, #1d4f73), @solidSeparator@; } -.devtools-sidebar-tabs > tabs > tab[selected]:hover { +.devtools-sidebar-tabs > tabs > tab[selected=true]:hover { background-image: linear-gradient(#274f64, #274f64), @solidSeparator@; } -.devtools-sidebar-tabs > tabs > tab[selected]:hover:active { +.devtools-sidebar-tabs > tabs > tab[selected=true]:hover:active { background-image: linear-gradient(#1f3e4f, #1f3e4f), @solidSeparator@; } @@ -520,7 +520,7 @@ } .devtools-tab:active > image, -.devtools-tab[selected] > image { +.devtools-tab[selected=true] > image { opacity: 1; } @@ -534,7 +534,7 @@ color: #f5f7fa; } -#toolbox-tabs .devtools-tab[selected] { +#toolbox-tabs .devtools-tab[selected=true] { color: #f5f7fa; background-color: #1a4666; box-shadow: 0 2px 0 #d7f1ff inset, @@ -542,32 +542,32 @@ 0 -2px 0 rgba(0,0,0,.2) inset; } -.devtools-tab[selected]:not(:first-child), -.devtools-tab[highlighted]:not(:first-child) { +.devtools-tab[selected=true]:not(:first-child), +.devtools-tab.highlighted:not(:first-child) { border-width: 0; -moz-padding-start: 1px; } -.devtools-tab[selected]:last-child, -.devtools-tab[highlighted]:last-child { +.devtools-tab[selected=true]:last-child, +.devtools-tab.highlighted:last-child { -moz-padding-end: 1px; } -.devtools-tab[selected] + .devtools-tab, -.devtools-tab[highlighted] + .devtools-tab { +.devtools-tab[selected=true] + .devtools-tab, +.devtools-tab.highlighted + .devtools-tab { -moz-border-start-width: 0; -moz-padding-start: 1px; } -.devtools-tab:not([selected])[highlighted] { +.devtools-tab:not([selected=true]).highlighted { color: #f5f7fa; background-color: hsla(99,100%,14%,.2); box-shadow: 0 2px 0 #7bc107 inset; } -.devtools-tab:not([highlighted]) > .highlighted-icon, -.devtools-tab[selected] > .highlighted-icon, -.devtools-tab:not([selected])[highlighted] > .default-icon { +.devtools-tab:not(.highlighted) > .highlighted-icon, +.devtools-tab[selected=true] > .highlighted-icon, +.devtools-tab:not([selected=true]).highlighted > .default-icon { visibility: collapse; } From c9b3ca4e13d16725ce55c629326fa31e911eaef4 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Mon, 2 Dec 2013 06:18:44 +0100 Subject: [PATCH 25/36] Bug 921942 - Broadcast scroll positions r=yoric From 5f535195e10d6cccbedbdf607ff194450a40c4ed Mon Sep 17 00:00:00 2001 --- .../content/content-sessionStore.js | 47 +++- .../components/sessionstore/src/FrameTree.jsm | 217 ++++++++++++++++++ .../sessionstore/src/ScrollPosition.jsm | 90 ++++++++ .../sessionstore/src/SessionStore.jsm | 68 +++++- .../sessionstore/src/TextAndScrollData.jsm | 15 +- browser/components/sessionstore/src/moz.build | 2 + 6 files changed, 412 insertions(+), 27 deletions(-) create mode 100644 browser/components/sessionstore/src/FrameTree.jsm create mode 100644 browser/components/sessionstore/src/ScrollPosition.jsm diff --git a/browser/components/sessionstore/content/content-sessionStore.js b/browser/components/sessionstore/content/content-sessionStore.js index 5b9732b410ee..407543c76c0c 100644 --- a/browser/components/sessionstore/content/content-sessionStore.js +++ b/browser/components/sessionstore/content/content-sessionStore.js @@ -16,12 +16,12 @@ let Cr = Components.results; Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); Cu.import("resource://gre/modules/Timer.jsm", this); -XPCOMUtils.defineLazyModuleGetter(this, "Utils", - "resource:///modules/sessionstore/Utils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities", "resource:///modules/sessionstore/DocShellCapabilities.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PageStyle", "resource:///modules/sessionstore/PageStyle.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition", + "resource:///modules/sessionstore/ScrollPosition.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory", "resource:///modules/sessionstore/SessionHistory.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage", @@ -29,6 +29,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage", XPCOMUtils.defineLazyModuleGetter(this, "TextAndScrollData", "resource:///modules/sessionstore/TextAndScrollData.jsm"); +Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this); +let gFrameTree = new FrameTree(this); + /** * Returns a lazy function that will evaluate the given * function |fn| only once and cache its return value. @@ -78,7 +81,7 @@ let EventListener = { handleEvent: function (event) { switch (event.type) { case "pageshow": - if (event.persisted) + if (event.persisted && event.target == content.document) sendAsyncMessage("SessionStore:pageshow"); break; case "input": @@ -198,6 +201,43 @@ let ProgressListener = { Ci.nsISupportsWeakReference]) }; +/** + * Listens for scroll position changes. Whenever the user scrolls the top-most + * frame we update the scroll position and will restore it when requested. + * + * Causes a SessionStore:update message to be sent that contains the current + * scroll positions as a tree of strings. If no frame of the whole frame tree + * is scrolled this will return null so that we don't tack a property onto + * the tabData object in the parent process. + * + * Example: + * {scroll: "100,100", children: [null, null, {scroll: "200,200"}]} + */ +let ScrollPositionListener = { + init: function () { + addEventListener("scroll", this); + gFrameTree.addObserver(this); + }, + + handleEvent: function (event) { + let frame = event.target && event.target.defaultView; + + // Don't collect scroll data for frames created at or after the load event + // as SessionStore can't restore scroll data for those. + if (frame && gFrameTree.contains(frame)) { + MessageQueue.push("scroll", () => this.collect()); + } + }, + + onFrameTreeReset: function () { + MessageQueue.push("scroll", () => null); + }, + + collect: function () { + return gFrameTree.map(ScrollPosition.collect); + } +}; + /** * Listens for changes to the page style. Whenever a different page style is * selected or author styles are enabled/disabled we send a message with the @@ -489,5 +529,6 @@ SyncHandler.init(); ProgressListener.init(); PageStyleListener.init(); SessionStorageListener.init(); +ScrollPositionListener.init(); DocShellCapabilitiesListener.init(); PrivacyListener.init(); diff --git a/browser/components/sessionstore/src/FrameTree.jsm b/browser/components/sessionstore/src/FrameTree.jsm new file mode 100644 index 000000000000..5f3c3ac0c95a --- /dev/null +++ b/browser/components/sessionstore/src/FrameTree.jsm @@ -0,0 +1,217 @@ +/* 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 = ["FrameTree"]; + +const Cu = Components.utils; +const Ci = Components.interfaces; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); + +const EXPORTED_METHODS = ["addObserver", "contains", "map"]; + +/** + * A FrameTree represents all frames that were reachable when the document + * was loaded. We use this information to ignore frames when collecting + * sessionstore data as we can't currently restore anything for frames that + * have been created dynamically after or at the load event. + * + * @constructor + */ +function FrameTree(chromeGlobal) { + let internal = new FrameTreeInternal(chromeGlobal); + let external = {}; + + for (let method of EXPORTED_METHODS) { + external[method] = internal[method].bind(internal); + } + + return Object.freeze(external); +} + +/** + * The internal frame tree API that the public one points to. + * + * @constructor + */ +function FrameTreeInternal(chromeGlobal) { + // A WeakMap that uses frames (DOMWindows) as keys and their initial indices + // in their parents' child lists as values. Suppose we have a root frame with + // three subframes i.e. a page with three iframes. The WeakMap would have + // four entries and look as follows: + // + // root -> 0 + // subframe1 -> 0 + // subframe2 -> 1 + // subframe3 -> 2 + // + // Should one of the subframes disappear we will stop collecting data for it + // as |this._frames.has(frame) == false|. All other subframes will maintain + // their initial indices to ensure we can restore frame data appropriately. + this._frames = new WeakMap(); + + // The Set of observers that will be notified when the frame changes. + this._observers = new Set(); + + // The chrome global we use to retrieve the current DOMWindow. + this._chromeGlobal = chromeGlobal; + + // Register a web progress listener to be notified about new page loads. + let docShell = chromeGlobal.docShell; + let ifreq = docShell.QueryInterface(Ci.nsIInterfaceRequestor); + let webProgress = ifreq.getInterface(Ci.nsIWebProgress); + webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT); +} + +FrameTreeInternal.prototype = { + + // Returns the docShell's current global. + get content() { + return this._chromeGlobal.content; + }, + + /** + * Adds a given observer |obs| to the set of observers that will be notified + * when the frame tree is reset (when a new document starts loading) or + * recollected (when a document finishes loading). + * + * @param obs (object) + */ + addObserver: function (obs) { + this._observers.add(obs); + }, + + /** + * Notifies all observers that implement the given |method|. + * + * @param method (string) + */ + notifyObservers: function (method) { + for (let obs of this._observers) { + if (obs.hasOwnProperty(method)) { + obs[method](); + } + } + }, + + /** + * Checks whether a given |frame| is contained in the collected frame tree. + * If it is not, this indicates that we should not collect data for it. + * + * @param frame (nsIDOMWindow) + * @return bool + */ + contains: function (frame) { + return this._frames.has(frame); + }, + + /** + * Recursively applies the given function |cb| to the stored frame tree. Use + * this method to collect sessionstore data for all reachable frames stored + * in the frame tree. + * + * If a given function |cb| returns a value, it must be an object. It may + * however return "null" to indicate that there is no data to be stored for + * the given frame. + * + * The object returned by |cb| cannot have any property named "children" as + * that is used to store information about subframes in the tree returned + * by |map()| and might be overridden. + * + * @param cb (function) + * @return object + */ + map: function (cb) { + let frames = this._frames; + + function walk(frame) { + let obj = cb(frame) || {}; + + if (frames.has(frame)) { + let children = []; + + Array.forEach(frame.frames, subframe => { + // Don't collect any data if the frame is not contained in the + // initial frame tree. It's a dynamic frame added later. + if (!frames.has(subframe)) { + return; + } + + // Retrieve the frame's original position in its parent's child list. + let index = frames.get(subframe); + + // Recursively collect data for the current subframe. + let result = walk(subframe, cb); + if (result && Object.keys(result).length) { + children[index] = result; + } + }); + + if (children.length) { + obj.children = children; + } + } + + return Object.keys(obj).length ? obj : null; + } + + return walk(this.content); + }, + + /** + * Stores a given |frame| and its children in the frame tree. + * + * @param frame (nsIDOMWindow) + * @param index (int) + * The index in the given frame's parent's child list. + */ + collect: function (frame, index = 0) { + // Mark the given frame as contained in the frame tree. + this._frames.set(frame, index); + + // Mark the given frame's subframes as contained in the tree. + Array.forEach(frame.frames, this.collect, this); + }, + + /** + * @see nsIWebProgressListener.onStateChange + * + * We want to be notified about: + * - new documents that start loading to clear the current frame tree; + * - completed document loads to recollect reachable frames. + */ + onStateChange: function (webProgress, request, stateFlags, status) { + // Ignore state changes for subframes because we're only interested in the + // top-document starting or stopping its load. We thus only care about any + // changes to the root of the frame tree, not to any of its nodes/leafs. + if (!webProgress.isTopLevel || webProgress.DOMWindow != this.content) { + return; + } + + if (stateFlags & Ci.nsIWebProgressListener.STATE_START) { + // Clear the list of frames until we can recollect it. + this._frames.clear(); + + // Notify observers that the frame tree has been reset. + this.notifyObservers("onFrameTreeReset"); + } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) { + // The document and its resources have finished loading. + this.collect(webProgress.DOMWindow); + + // Notify observers that the frame tree has been reset. + this.notifyObservers("onFrameTreeCollected"); + } + }, + + // Unused nsIWebProgressListener methods. + onLocationChange: function () {}, + onProgressChange: function () {}, + onSecurityChange: function () {}, + onStatusChange: function () {}, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, + Ci.nsISupportsWeakReference]) +}; diff --git a/browser/components/sessionstore/src/ScrollPosition.jsm b/browser/components/sessionstore/src/ScrollPosition.jsm new file mode 100644 index 000000000000..21fe2bb0759a --- /dev/null +++ b/browser/components/sessionstore/src/ScrollPosition.jsm @@ -0,0 +1,90 @@ +/* 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 = ["ScrollPosition"]; + +const Ci = Components.interfaces; + +/** + * It provides methods to collect and restore scroll positions for single + * frames and frame trees. + * + * This is a child process module. + */ +this.ScrollPosition = Object.freeze({ + /** + * Collects scroll position data for any given |frame| in the frame hierarchy. + * + * @param frame (DOMWindow) + * + * @return {scroll: "x,y"} e.g. {scroll: "100,200"} + * Returns null when there is no scroll data we want to store for the + * given |frame|. + */ + collect: function (frame) { + let ifreq = frame.QueryInterface(Ci.nsIInterfaceRequestor); + let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils); + let scrollX = {}, scrollY = {}; + utils.getScrollXY(false /* no layout flush */, scrollX, scrollY); + + if (scrollX.value || scrollY.value) { + return {scroll: scrollX.value + "," + scrollY.value}; + } + + return null; + }, + + /** + * Restores scroll position data for any given |frame| in the frame hierarchy. + * + * @param frame (DOMWindow) + * @param value (object, see collect()) + */ + restore: function (frame, value) { + let match; + + if (value && (match = /(\d+),(\d+)/.exec(value))) { + frame.scrollTo(match[1], match[2]); + } + }, + + /** + * Restores scroll position data for the current frame hierarchy starting at + * |root| using the given scroll position |data|. + * + * If the given |root| frame's hierarchy doesn't match that of the given + * |data| object we will silently discard data for unreachable frames. We + * may as well assign scroll positions to the wrong frames if some were + * reordered or removed. + * + * @param root (DOMWindow) + * @param data (object) + * { + * scroll: "100,200", + * children: [ + * {scroll: "100,200"}, + * null, + * {scroll: "200,300", children: [ ... ]} + * ] + * } + */ + restoreTree: function (root, data) { + if (data.hasOwnProperty("scroll")) { + this.restore(root, data.scroll); + } + + if (!data.hasOwnProperty("children")) { + return; + } + + let frames = root.frames; + data.children.forEach((child, index) => { + if (child && index < frames.length) { + this.restoreTree(frames[index], child); + } + }); + } +}); diff --git a/browser/components/sessionstore/src/SessionStore.jsm b/browser/components/sessionstore/src/SessionStore.jsm index 63472818c5bb..1645a34f3b52 100644 --- a/browser/components/sessionstore/src/SessionStore.jsm +++ b/browser/components/sessionstore/src/SessionStore.jsm @@ -110,6 +110,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", "resource:///modules/RecentWindow.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager", "resource:///modules/devtools/scratchpad-manager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition", + "resource:///modules/sessionstore/ScrollPosition.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver", "resource:///modules/sessionstore/SessionSaver.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage", @@ -630,14 +632,17 @@ let SessionStoreInternal = { let browser; switch (aEvent.type) { case "load": - // If __SS_restore_data is set, then we need to restore the document - // (form data, scrolling, etc.). This will only happen when a tab is - // first restored. browser = aEvent.currentTarget; - TabStateCache.delete(browser); - if (browser.__SS_restore_data) - this.restoreDocument(win, browser, aEvent); - this.onTabLoad(win, browser); + // Ignore load events from subframes. + if (aEvent.target == browser.contentDocument) { + // If __SS_restore_data is set, then we need to restore the document + // (form data, scrolling, etc.). This will only happen when a tab is + // first restored. + TabStateCache.delete(browser); + if (browser.__SS_restore_data) + this.restoreDocument(win, browser, aEvent); + this.onTabLoad(win, browser); + } break; case "SwapDocShells": browser = aEvent.currentTarget; @@ -2654,6 +2659,7 @@ let SessionStoreInternal = { // Update the persistent tab state cache with |tabData| information. TabStateCache.updatePersistent(browser, { + scroll: tabData.scroll || null, storage: tabData.storage || null, disallow: tabData.disallow || null, pageStyle: tabData.pageStyle || null @@ -2821,8 +2827,15 @@ let SessionStoreInternal = { // restore those aspects of the currently active documents which are not // preserved in the plain history entries (mainly scroll state and text data) browser.__SS_restore_data = tabData.entries[activeIndex] || {}; - browser.__SS_restore_pageStyle = tabData.pageStyle || ""; browser.__SS_restore_tab = aTab; + + if (tabData.pageStyle) { + RestoreData.set(browser, "pageStyle", tabData.pageStyle); + } + if (tabData.scroll) { + RestoreData.set(browser, "scroll", tabData.scroll); + } + didStartLoad = true; try { // In order to work around certain issues in session history, we need to @@ -2837,7 +2850,6 @@ let SessionStoreInternal = { } } else { browser.__SS_restore_data = {}; - browser.__SS_restore_pageStyle = ""; browser.__SS_restore_tab = aTab; browser.loadURIWithFlags("about:blank", Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY, @@ -2949,7 +2961,11 @@ let SessionStoreInternal = { } let frameList = this.getFramesToRestore(aBrowser); - PageStyle.restore(aBrowser.docShell, frameList, aBrowser.__SS_restore_pageStyle); + let pageStyle = RestoreData.get(aBrowser, "pageStyle") || ""; + let scrollPositions = RestoreData.get(aBrowser, "scroll") || {}; + + PageStyle.restore(aBrowser.docShell, frameList, pageStyle); + ScrollPosition.restoreTree(aBrowser.contentWindow, scrollPositions); TextAndScrollData.restore(frameList); let tab = aBrowser.__SS_restore_tab; @@ -2958,8 +2974,8 @@ let SessionStoreInternal = { // done with that now. delete aBrowser.__SS_data; delete aBrowser.__SS_restore_data; - delete aBrowser.__SS_restore_pageStyle; delete aBrowser.__SS_restore_tab; + RestoreData.clear(aBrowser); // Notify the tabbrowser that this document has been completely // restored. Do so after restoration is completely finished and @@ -4127,4 +4143,32 @@ let GlobalState = { setFromState: function (aState) { this.state = (aState && aState.global) || {}; } -} +}; + +/** + * Keeps track of data that needs to be restored after the tab's document + * has been loaded. This includes scroll positions, form data, and page style. + */ +let RestoreData = { + _data: new WeakMap(), + + get: function (browser, key) { + if (!this._data.has(browser)) { + return null; + } + + return this._data.get(browser).get(key); + }, + + set: function (browser, key, value) { + if (!this._data.has(browser)) { + this._data.set(browser, new Map()); + } + + this._data.get(browser).set(key, value); + }, + + clear: function (browser) { + this._data.delete(browser); + } +}; diff --git a/browser/components/sessionstore/src/TextAndScrollData.jsm b/browser/components/sessionstore/src/TextAndScrollData.jsm index b0c52dac800d..e4f7ab754158 100644 --- a/browser/components/sessionstore/src/TextAndScrollData.jsm +++ b/browser/components/sessionstore/src/TextAndScrollData.jsm @@ -16,6 +16,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "DocumentUtils", "resource:///modules/sessionstore/DocumentUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel", "resource:///modules/sessionstore/PrivacyLevel.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition", + "resource:///modules/sessionstore/ScrollPosition.jsm"); /** * The external API exported by this module. @@ -78,14 +80,6 @@ let TextAndScrollDataInternal = { entry.innerHTML = content.document.body.innerHTML; } } - - // get scroll position from nsIDOMWindowUtils, since it allows avoiding a - // flush of layout - let domWindowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - let scrollX = {}, scrollY = {}; - domWindowUtils.getScrollXY(false, scrollX, scrollY); - entry.scroll = scrollX.value + "," + scrollY.value; }, isAboutSessionRestore: function (url) { @@ -146,9 +140,6 @@ let TextAndScrollDataInternal = { }, 0); } - let match; - if (data.scroll && (match = /(\d+),(\d+)/.exec(data.scroll)) != null) { - content.scrollTo(match[1], match[2]); - } + ScrollPosition.restore(content, data.scroll || ""); }, }; diff --git a/browser/components/sessionstore/src/moz.build b/browser/components/sessionstore/src/moz.build index 4be0d28d651e..a9885fc5fb4b 100644 --- a/browser/components/sessionstore/src/moz.build +++ b/browser/components/sessionstore/src/moz.build @@ -15,10 +15,12 @@ JS_MODULES_PATH = 'modules/sessionstore' EXTRA_JS_MODULES = [ 'DocShellCapabilities.jsm', 'DocumentUtils.jsm', + 'FrameTree.jsm', 'Messenger.jsm', 'PageStyle.jsm', 'PrivacyLevel.jsm', 'RecentlyClosedTabsAndWindowsMenuUtils.jsm', + 'ScrollPosition.jsm', 'SessionCookies.jsm', 'SessionFile.jsm', 'SessionHistory.jsm', From e2f55195b2e5fea7ad9d34a0dbac11cded466fbb Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Sat, 30 Nov 2013 13:49:46 +0100 Subject: [PATCH 26/36] Bug 921942 - Add FrameTree and scroll position broadcast tests r=yoric From 5645706beeae13673e87ba665f225e01a32949e2 Mon Sep 17 00:00:00 2001 --- .../components/sessionstore/test/browser.ini | 7 +- .../sessionstore/test/browser_394759_basic.js | 4 +- .../sessionstore/test/browser_483330.js | 40 ----- .../sessionstore/test/browser_625257.js | 3 +- .../sessionstore/test/browser_frametree.js | 130 +++++++++++++++++ .../test/browser_frametree_sample.html | 8 + .../browser_frametree_sample_frameset.html | 11 ++ .../sessionstore/test/browser_pageStyle.js | 5 +- .../test/browser_scrollPositions.js | 137 ++++++++++++++++++ .../test/browser_scrollPositions_sample.html | 8 + ...owser_scrollPositions_sample_frameset.html | 11 ++ .../components/sessionstore/test/content.js | 81 +++++++++++ browser/components/sessionstore/test/head.js | 10 +- 13 files changed, 402 insertions(+), 53 deletions(-) delete mode 100644 browser/components/sessionstore/test/browser_483330.js create mode 100644 browser/components/sessionstore/test/browser_frametree.js create mode 100644 browser/components/sessionstore/test/browser_frametree_sample.html create mode 100644 browser/components/sessionstore/test/browser_frametree_sample_frameset.html create mode 100644 browser/components/sessionstore/test/browser_scrollPositions.js create mode 100644 browser/components/sessionstore/test/browser_scrollPositions_sample.html create mode 100644 browser/components/sessionstore/test/browser_scrollPositions_sample_frameset.html diff --git a/browser/components/sessionstore/test/browser.ini b/browser/components/sessionstore/test/browser.ini index 03061c5f1e78..c5a6b9b072e6 100644 --- a/browser/components/sessionstore/test/browser.ini +++ b/browser/components/sessionstore/test/browser.ini @@ -11,11 +11,15 @@ support-files = head.js content.js + browser_frametree_sample.html + browser_frametree_sample_frameset.html browser_form_restore_events_sample.html browser_formdata_format_sample.html browser_input_sample.html browser_pageStyle_sample.html browser_pageStyle_sample_nested.html + browser_scrollPositions_sample.html + browser_scrollPositions_sample_frameset.html browser_248970_b_sample.html browser_339445_sample.html browser_346337_sample.html @@ -54,12 +58,14 @@ support-files = [browser_dying_cache.js] [browser_form_restore_events.js] [browser_formdata_format.js] +[browser_frametree.js] [browser_global_store.js] [browser_input.js] [browser_merge_closed_tabs.js] [browser_pageshow.js] [browser_pageStyle.js] [browser_privatetabs.js] +[browser_scrollPositions.js] [browser_sessionStorage.js] [browser_swapDocShells.js] [browser_tabStateCache.js] @@ -96,7 +102,6 @@ skip-if = true [browser_477657.js] [browser_480148.js] [browser_480893.js] -[browser_483330.js] [browser_485482.js] [browser_485563.js] [browser_490040.js] diff --git a/browser/components/sessionstore/test/browser_394759_basic.js b/browser/components/sessionstore/test/browser_394759_basic.js index 1d0567e34074..66af533366b3 100644 --- a/browser/components/sessionstore/test/browser_394759_basic.js +++ b/browser/components/sessionstore/test/browser_394759_basic.js @@ -34,9 +34,7 @@ function test() { EventUtils.sendMouseEvent({type: "click"}, chk); let browser = newWin.gBrowser.selectedBrowser; - promiseContentMessage(browser, "SessionStore:input").then(result => { - ok(result, "received message for input changes"); - + promiseContentMessage(browser, "SessionStore:input").then(() => { newWin.close(); // Now give it time to close diff --git a/browser/components/sessionstore/test/browser_483330.js b/browser/components/sessionstore/test/browser_483330.js deleted file mode 100644 index cd0048e4c2db..000000000000 --- a/browser/components/sessionstore/test/browser_483330.js +++ /dev/null @@ -1,40 +0,0 @@ -function test() { - /** Test for Bug 483330 **/ - - waitForExplicitFinish(); - - let tab = gBrowser.addTab(); - gBrowser.selectedTab = tab; - - let browser = tab.linkedBrowser; - browser.addEventListener("load", function loadListener(e) { - browser.removeEventListener("load", arguments.callee, true); - - // Scroll the content document - browser.contentWindow.scrollTo(1100, 1200); - is(browser.contentWindow.scrollX, 1100, "scrolled horizontally"); - is(browser.contentWindow.scrollY, 1200, "scrolled vertically"); - - gBrowser.removeTab(tab); - - let newTab = ss.undoCloseTab(window, 0); - newTab.addEventListener("SSTabRestored", function tabRestored(e) { - newTab.removeEventListener("SSTabRestored", arguments.callee, true); - - let newBrowser = newTab.linkedBrowser; - - // check that the scroll position was restored - is(newBrowser.contentWindow.scrollX, 1100, "still scrolled horizontally"); - is(newBrowser.contentWindow.scrollY, 1200, "still scrolled vertically"); - - gBrowser.removeTab(newTab); - // Call stopPropagation on the event so we won't fire the - // tabbrowser's SSTabRestored listeners. - e.stopPropagation(); - - finish(); - }, true); - }, true); - - browser.loadURI("data:text/html;charset=utf-8,

top

"); -} diff --git a/browser/components/sessionstore/test/browser_625257.js b/browser/components/sessionstore/test/browser_625257.js index 4c4ffde856a7..63e59718593f 100644 --- a/browser/components/sessionstore/test/browser_625257.js +++ b/browser/components/sessionstore/test/browser_625257.js @@ -56,8 +56,7 @@ function test() { // Start a load and interrupt it by closing the tab tab.linkedBrowser.loadURI(URI_TO_LOAD); - let loaded = yield waitForLoadStarted(tab); - ok(loaded, "Load started"); + yield waitForLoadStarted(tab); let tabClosing = waitForTabClosed(); gBrowser.removeTab(tab); diff --git a/browser/components/sessionstore/test/browser_frametree.js b/browser/components/sessionstore/test/browser_frametree.js new file mode 100644 index 000000000000..1ce05b94c14d --- /dev/null +++ b/browser/components/sessionstore/test/browser_frametree.js @@ -0,0 +1,130 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const ROOT = getRootDirectory(gTestPath); +const URL = ROOT + "browser_frametree_sample.html"; +const URL_FRAMESET = ROOT + "browser_frametree_sample_frameset.html"; + +/** + * This ensures that loading a page normally, aborting a page load, reloading + * a page, navigating using the bfcache, and ignoring frames that were + * created dynamically work as expect. We expect the frame tree to be reset + * when a page starts loading and we also expect a valid frame tree to exist + * when it has stopped loading. + */ +add_task(function test_frametree() { + const FRAME_TREE_SINGLE = { href: URL }; + const FRAME_TREE_FRAMESET = { + href: URL_FRAMESET, + children: [{href: URL}, {href: URL}, {href: URL}] + }; + + // Create a tab with a single frame. + let tab = gBrowser.addTab(URL); + let browser = tab.linkedBrowser; + yield promiseNewFrameTree(browser); + yield checkFrameTree(browser, FRAME_TREE_SINGLE, + "loading a page resets and creates the frame tree correctly"); + + // Load the frameset and create two frames dynamically, the first on + // DOMContentLoaded and the second on load. + yield sendMessage(browser, "ss-test:createDynamicFrames", {id: "frames", url: URL}); + browser.loadURI(URL_FRAMESET); + yield promiseNewFrameTree(browser); + yield checkFrameTree(browser, FRAME_TREE_FRAMESET, + "dynamic frames created on or after the load event are ignored"); + + // Go back to the previous single-frame page. There will be no load event as + // the page is still in the bfcache. We thus make sure this type of navigation + // resets the frame tree. + browser.goBack(); + yield promiseNewFrameTree(browser); + yield checkFrameTree(browser, FRAME_TREE_SINGLE, + "loading from bfache resets and creates the frame tree correctly"); + + // Load the frameset again but abort the load early. + // The frame tree should still be reset and created. + browser.loadURI(URL_FRAMESET); + executeSoon(() => browser.stop()); + yield promiseNewFrameTree(browser); + + // Load the frameset and check the tree again. + yield sendMessage(browser, "ss-test:createDynamicFrames", {id: "frames", url: URL}); + browser.loadURI(URL_FRAMESET); + yield promiseNewFrameTree(browser); + yield checkFrameTree(browser, FRAME_TREE_FRAMESET, + "reloading a page resets and creates the frame tree correctly"); + + // Cleanup. + gBrowser.removeTab(tab); +}); + +/** + * This test ensures that we ignore frames that were created dynamically at or + * after the load event. SessionStore can't handle these and will not restore + * or collect any data for them. + */ +add_task(function test_frametree_dynamic() { + // The frame tree as expected. The first two frames are static + // and the third one was created on DOMContentLoaded. + const FRAME_TREE = { + href: URL_FRAMESET, + children: [{href: URL}, {href: URL}, {href: URL}] + }; + const FRAME_TREE_REMOVED = { + href: URL_FRAMESET, + children: [{href: URL}, {href: URL}] + }; + + // Add an empty tab for a start. + let tab = gBrowser.addTab("about:blank"); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Create dynamic frames on "DOMContentLoaded" and on "load". + yield sendMessage(browser, "ss-test:createDynamicFrames", {id: "frames", url: URL}); + browser.loadURI(URL_FRAMESET); + yield promiseNewFrameTree(browser); + + // Check that the frame tree does not contain the frame created on "load". + // The two static frames and the one created on DOMContentLoaded must be in + // the tree. + yield checkFrameTree(browser, FRAME_TREE, + "frame tree contains first four frames"); + + // Remove the last frame in the frameset. + yield sendMessage(browser, "ss-test:removeLastFrame", {id: "frames"}); + // Check that the frame tree didn't change. + yield checkFrameTree(browser, FRAME_TREE, + "frame tree contains first four frames"); + + // Remove the last frame in the frameset. + yield sendMessage(browser, "ss-test:removeLastFrame", {id: "frames"}); + // Check that the frame tree excludes the removed frame. + yield checkFrameTree(browser, FRAME_TREE_REMOVED, + "frame tree contains first three frames"); + + // Cleanup. + gBrowser.removeTab(tab); +}); + +/** + * Checks whether the current frame hierarchy of a given |browser| matches the + * |expected| frame hierarchy. + */ +function checkFrameTree(browser, expected, msg) { + return sendMessage(browser, "ss-test:mapFrameTree").then(tree => { + is(JSON.stringify(tree), JSON.stringify(expected), msg); + }); +} + +/** + * Returns a promise that will be resolved when the given |browser| has loaded + * and we received messages saying that its frame tree has been reset and + * recollected. + */ +function promiseNewFrameTree(browser) { + let reset = promiseContentMessage(browser, "ss-test:onFrameTreeCollected"); + let collect = promiseContentMessage(browser, "ss-test:onFrameTreeCollected"); + return Promise.all([reset, collect]); +} diff --git a/browser/components/sessionstore/test/browser_frametree_sample.html b/browser/components/sessionstore/test/browser_frametree_sample.html new file mode 100644 index 000000000000..dda129448c03 --- /dev/null +++ b/browser/components/sessionstore/test/browser_frametree_sample.html @@ -0,0 +1,8 @@ + + + + + browser_frametree_sample.html + + top + diff --git a/browser/components/sessionstore/test/browser_frametree_sample_frameset.html b/browser/components/sessionstore/test/browser_frametree_sample_frameset.html new file mode 100644 index 000000000000..e1cd08735708 --- /dev/null +++ b/browser/components/sessionstore/test/browser_frametree_sample_frameset.html @@ -0,0 +1,11 @@ + + + + + browser_frametree_sample_frameset.html + + + + + + diff --git a/browser/components/sessionstore/test/browser_pageStyle.js b/browser/components/sessionstore/test/browser_pageStyle.js index 7fd5d7dfe9a3..155cd47ce0f8 100644 --- a/browser/components/sessionstore/test/browser_pageStyle.js +++ b/browser/components/sessionstore/test/browser_pageStyle.js @@ -65,7 +65,7 @@ add_task(function nested_page_style() { }); function getStyleSheets(browser) { - return sendMessage(browser, "ss-test:getStyleSheets").then(({data}) => data); + return sendMessage(browser, "ss-test:getStyleSheets"); } function enableStyleSheetsForSet(browser, name) { @@ -79,8 +79,7 @@ function enableSubDocumentStyleSheetsForSet(browser, name) { } function getAuthorStyleDisabled(browser) { - return sendMessage(browser, "ss-test:getAuthorStyleDisabled") - .then(({data}) => data); + return sendMessage(browser, "ss-test:getAuthorStyleDisabled"); } function setAuthorStyleDisabled(browser, val) { diff --git a/browser/components/sessionstore/test/browser_scrollPositions.js b/browser/components/sessionstore/test/browser_scrollPositions.js new file mode 100644 index 000000000000..cf28c59cf338 --- /dev/null +++ b/browser/components/sessionstore/test/browser_scrollPositions.js @@ -0,0 +1,137 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const ROOT = getRootDirectory(gTestPath); +const URL = ROOT + "browser_scrollPositions_sample.html"; +const URL_FRAMESET = ROOT + "browser_scrollPositions_sample_frameset.html"; + +// Randomized set of scroll positions we will use in this test. +const SCROLL_X = Math.round(100 * (1 + Math.random())); +const SCROLL_Y = Math.round(200 * (1 + Math.random())); +const SCROLL_STR = SCROLL_X + "," + SCROLL_Y; + +const SCROLL2_X = Math.round(300 * (1 + Math.random())); +const SCROLL2_Y = Math.round(400 * (1 + Math.random())); +const SCROLL2_STR = SCROLL2_X + "," + SCROLL2_Y; + +/** + * This test ensures that we properly serialize and restore scroll positions + * for an average page without any frames. + */ +add_task(function test_scroll() { + let tab = gBrowser.addTab(URL); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Scroll down a little. + yield sendMessage(browser, "ss-test:setScrollPosition", {x: SCROLL_X, y: SCROLL_Y}); + checkScroll(tab, {scroll: SCROLL_STR}, "scroll is fine"); + + // Duplicate and check that the scroll position is restored. + let tab2 = ss.duplicateTab(window, tab); + let browser2 = tab2.linkedBrowser; + yield promiseTabRestored(tab2); + + let scroll = yield sendMessage(browser2, "ss-test:getScrollPosition"); + is(JSON.stringify(scroll), JSON.stringify({x: SCROLL_X, y: SCROLL_Y}), + "scroll position has been duplicated correctly"); + + // Check that reloading retains the scroll positions. + browser2.reload(); + yield promiseBrowserLoaded(browser2); + checkScroll(tab2, {scroll: SCROLL_STR}, "reloading retains scroll positions"); + + // Check that a force-reload resets scroll positions. + browser2.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE); + yield promiseBrowserLoaded(browser2); + checkScroll(tab2, null, "force-reload resets scroll positions"); + + // Scroll back to the top and check that the position has been reset. We + // expect the scroll position to be "null" here because there is no data to + // be stored if the frame is in its default scroll position. + yield sendMessage(browser, "ss-test:setScrollPosition", {x: 0, y: 0}); + checkScroll(tab, null, "no scroll stored"); + + // Cleanup. + gBrowser.removeTab(tab); + gBrowser.removeTab(tab2); +}); + +/** + * This tests ensures that we properly serialize and restore scroll positions + * for multiple frames of pages with framesets. + */ +add_task(function test_scroll_nested() { + let tab = gBrowser.addTab(URL_FRAMESET); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Scroll the first child frame down a little. + yield sendMessage(browser, "ss-test:setScrollPosition", {x: SCROLL_X, y: SCROLL_Y, frame: 0}); + checkScroll(tab, {children: [{scroll: SCROLL_STR}]}, "scroll is fine"); + + // Scroll the second child frame down a little. + yield sendMessage(browser, "ss-test:setScrollPosition", {x: SCROLL2_X, y: SCROLL2_Y, frame: 1}); + checkScroll(tab, {children: [{scroll: SCROLL_STR}, {scroll: SCROLL2_STR}]}, "scroll is fine"); + + // Duplicate and check that the scroll position is restored. + let tab2 = ss.duplicateTab(window, tab); + let browser2 = tab2.linkedBrowser; + yield promiseTabRestored(tab2); + + let scroll = yield sendMessage(browser2, "ss-test:getScrollPosition", {frame: 0}); + is(JSON.stringify(scroll), JSON.stringify({x: SCROLL_X, y: SCROLL_Y}), + "scroll position #1 has been duplicated correctly"); + + scroll = yield sendMessage(browser2, "ss-test:getScrollPosition", {frame: 1}); + is(JSON.stringify(scroll), JSON.stringify({x: SCROLL2_X, y: SCROLL2_Y}), + "scroll position #2 has been duplicated correctly"); + + // Check that resetting one frame's scroll position removes it from the + // serialized value. + yield sendMessage(browser, "ss-test:setScrollPosition", {x: 0, y: 0, frame: 0}); + checkScroll(tab, {children: [null, {scroll: SCROLL2_STR}]}, "scroll is fine"); + + // Check the resetting all frames' scroll positions nulls the stored value. + yield sendMessage(browser, "ss-test:setScrollPosition", {x: 0, y: 0, frame: 1}); + checkScroll(tab, null, "no scroll stored"); + + // Cleanup. + gBrowser.removeTab(tab); + gBrowser.removeTab(tab2); +}); + +/** + * This test ensures that by moving scroll positions out of tabData.entries[] + * we still support the old scroll data format stored per shistory entry. + */ +add_task(function test_scroll_old_format() { + const TAB_STATE = { entries: [{url: URL, scroll: SCROLL_STR}] }; + + // Add a blank tab. + let tab = gBrowser.addTab("about:blank"); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Apply the tab state with the old format. + ss.setTabState(tab, JSON.stringify(TAB_STATE)); + yield promiseTabRestored(tab); + + // Check that the scroll positions has been applied. + let scroll = yield sendMessage(browser, "ss-test:getScrollPosition"); + is(JSON.stringify(scroll), JSON.stringify({x: SCROLL_X, y: SCROLL_Y}), + "scroll position has been restored correctly"); + + // Cleanup. + gBrowser.removeTab(tab); +}); + +function checkScroll(tab, expected, msg) { + let browser = tab.linkedBrowser; + SyncHandlers.get(browser).flush(); + + let scroll = JSON.parse(ss.getTabState(tab)).scroll || null; + is(JSON.stringify(scroll), JSON.stringify(expected), msg); +} diff --git a/browser/components/sessionstore/test/browser_scrollPositions_sample.html b/browser/components/sessionstore/test/browser_scrollPositions_sample.html new file mode 100644 index 000000000000..0182783dbb77 --- /dev/null +++ b/browser/components/sessionstore/test/browser_scrollPositions_sample.html @@ -0,0 +1,8 @@ + + + + + browser_scrollPositions_sample.html + + top + diff --git a/browser/components/sessionstore/test/browser_scrollPositions_sample_frameset.html b/browser/components/sessionstore/test/browser_scrollPositions_sample_frameset.html new file mode 100644 index 000000000000..c7e363fa1d0d --- /dev/null +++ b/browser/components/sessionstore/test/browser_scrollPositions_sample_frameset.html @@ -0,0 +1,11 @@ + + + + + browser_scrollPositions_sample_frameset.html + + + + + + diff --git a/browser/components/sessionstore/test/content.js b/browser/components/sessionstore/test/content.js index c931147c95c2..41ad3595d1b3 100644 --- a/browser/components/sessionstore/test/content.js +++ b/browser/components/sessionstore/test/content.js @@ -2,8 +2,22 @@ * 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/. */ +let Cu = Components.utils; let Ci = Components.interfaces; +Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this); +let gFrameTree = new FrameTree(this); + +gFrameTree.addObserver({ + onFrameTreeReset: function () { + sendAsyncMessage("ss-test:onFrameTreeReset"); + }, + + onFrameTreeCollected: function () { + sendAsyncMessage("ss-test:onFrameTreeCollected"); + } +}); + /** * This frame script is only loaded for sessionstore mochitests. It enables us * to modify and query docShell data when running with multiple processes. @@ -59,3 +73,70 @@ addMessageListener("ss-test:setUsePrivateBrowsing", function (msg) { loadContext.usePrivateBrowsing = msg.data; sendAsyncMessage("ss-test:setUsePrivateBrowsing"); }); + +addMessageListener("ss-test:getScrollPosition", function (msg) { + let frame = content; + if (msg.data.hasOwnProperty("frame")) { + frame = content.frames[msg.data.frame]; + } + let {scrollX: x, scrollY: y} = frame; + sendAsyncMessage("ss-test:getScrollPosition", {x: x, y: y}); +}); + +addMessageListener("ss-test:setScrollPosition", function (msg) { + let frame = content; + let {x, y} = msg.data; + if (msg.data.hasOwnProperty("frame")) { + frame = content.frames[msg.data.frame]; + } + frame.scrollTo(x, y); + + frame.addEventListener("scroll", function onScroll(event) { + if (frame.document == event.target) { + frame.removeEventListener("scroll", onScroll); + sendAsyncMessage("ss-test:setScrollPosition"); + } + }); +}); + +addMessageListener("ss-test:createDynamicFrames", function ({data}) { + function createIFrame(rows) { + let frames = content.document.getElementById(data.id); + frames.setAttribute("rows", rows); + + let frame = content.document.createElement("frame"); + frame.setAttribute("src", data.url); + frames.appendChild(frame); + } + + addEventListener("DOMContentLoaded", function onContentLoaded(event) { + if (content.document == event.target) { + removeEventListener("DOMContentLoaded", onContentLoaded, true); + // DOMContentLoaded is fired right after we finished parsing the document. + createIFrame("33%, 33%, 33%"); + } + }, true); + + addEventListener("load", function onLoad(event) { + if (content.document == event.target) { + removeEventListener("load", onLoad, true); + + // Creating this frame on the same tick as the load event + // means that it must not be included in the frame tree. + createIFrame("25%, 25%, 25%, 25%"); + } + }, true); + + sendAsyncMessage("ss-test:createDynamicFrames"); +}); + +addMessageListener("ss-test:removeLastFrame", function ({data}) { + let frames = content.document.getElementById(data.id); + frames.lastElementChild.remove(); + sendAsyncMessage("ss-test:removeLastFrame"); +}); + +addMessageListener("ss-test:mapFrameTree", function (msg) { + let result = gFrameTree.map(frame => ({href: frame.location.href})); + sendAsyncMessage("ss-test:mapFrameTree", result); +}); diff --git a/browser/components/sessionstore/test/head.js b/browser/components/sessionstore/test/head.js index 0e99d5d3d5bf..08eae722f327 100644 --- a/browser/components/sessionstore/test/head.js +++ b/browser/components/sessionstore/test/head.js @@ -193,7 +193,7 @@ function promiseContentMessage(browser, name) { function listener(msg) { removeListener(); - deferred.resolve(msg); + deferred.resolve(msg.data); } mm.addMessageListener(name, listener); @@ -280,9 +280,11 @@ function forceSaveState() { } function whenBrowserLoaded(aBrowser, aCallback = next) { - aBrowser.addEventListener("load", function onLoad() { - aBrowser.removeEventListener("load", onLoad, true); - executeSoon(aCallback); + aBrowser.addEventListener("load", function onLoad(event) { + if (event.target == aBrowser.contentDocument) { + aBrowser.removeEventListener("load", onLoad, true); + executeSoon(aCallback); + } }, true); } function promiseBrowserLoaded(aBrowser) { From 414f242156b4f9ea57f14e97ecf799259fa83740 Mon Sep 17 00:00:00 2001 From: Marina Samuel Date: Wed, 18 Dec 2013 14:20:39 -0500 Subject: [PATCH 27/36] Bug 950159: Save state of other desktop windows to be restored when switching back from Metro. r=mbrubeck --- browser/metro/components/SessionStore.js | 36 ++++++++++++++++++------ 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/browser/metro/components/SessionStore.js b/browser/metro/components/SessionStore.js index 1f69401e2ab2..0ce923ed2447 100644 --- a/browser/metro/components/SessionStore.js +++ b/browser/metro/components/SessionStore.js @@ -15,6 +15,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter", "@mozilla.org/xre/app-info;1", "nsICrashReporter"); #endif +XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator", + "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"); + XPCOMUtils.defineLazyGetter(this, "NetUtil", function() { Cu.import("resource://gre/modules/NetUtil.jsm"); return NetUtil; @@ -39,6 +42,8 @@ SessionStore.prototype = { Ci.nsISupportsWeakReference]), _windows: {}, + _selectedWindow: 1, + _orderedWindows: [], _lastSaveTime: 0, _lastSessionTime: 0, _interval: 10000, @@ -284,8 +289,8 @@ SessionStore.prototype = { if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" || this._loadState == STATE_QUITTING) return; - // Assign it a unique identifier (timestamp) and create its data object - aWindow.__SSID = "window" + Date.now(); + // Assign it a unique identifier and create its data object + aWindow.__SSID = "window" + gUUIDGenerator.generateUUID().toString(); this._windows[aWindow.__SSID] = { tabs: [], selected: 0, _closedTabs: [] }; // Perform additional initialization when the first window is loading @@ -472,9 +477,9 @@ SessionStore.prototype = { }); let data = { windows: [] }; - let index; - for (index in this._windows) - data.windows.push(this._windows[index]); + for (let i = 0; i < this._orderedWindows.length; i++) + data.windows.push(this._windows[this._orderedWindows[i]]); + data.selectedWindow = this._selectedWindow; return data; }, @@ -724,11 +729,24 @@ SessionStore.prototype = { let window = Services.wm.getMostRecentWindow("navigator:browser"); - let tabs = data.windows[0].tabs; - let selected = data.windows[0].selected; + this._selectedWindow = data.selectedWindow; + let windowIndex = this._selectedWindow - 1; + let tabs = data.windows[windowIndex].tabs; + let selected = data.windows[windowIndex].selected; - if (data.windows[0]._closedTabs) - this._windows[window.__SSID]._closedTabs = data.windows[0]._closedTabs; + // Move all window data from sessionstore.js to this._windows. + for (let i = 0; i < data.windows.length; i++) { + let SSID; + if (i != windowIndex) { + SSID = "window" + gUUIDGenerator.generateUUID().toString(); + this._windows[SSID] = data.windows[i]; + } else { + SSID = window.__SSID; + this._windows[SSID]._closedTabs = + this._windows[SSID]._closedTabs.concat(data.windows[windowIndex]._closedTabs); + } + this._orderedWindows.push(SSID); + } if (selected > tabs.length) // Clamp the selected index if it's bogus selected = 1; From 9b8ab83772a667045ab5601ba29e4405c382402e Mon Sep 17 00:00:00 2001 From: Marina Samuel Date: Wed, 18 Dec 2013 15:28:40 -0500 Subject: [PATCH 28/36] Bug 951725: Notify sessionstore of browser restart in order to save session state. r=ttaubert --- browser/base/content/browser.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 5beac53e6d5a..c9082d3ee68d 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -2474,8 +2474,15 @@ function _checkDefaultAndSwitchToMetro() { getService(Components.interfaces.nsIAppStartup); Services.prefs.setBoolPref('browser.sessionstore.resume_session_once', true); - appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit | - Components.interfaces.nsIAppStartup.eRestartTouchEnvironment); + + let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"] + .createInstance(Ci.nsISupportsPRBool); + Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); + + if (!cancelQuit.data) { + appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit | + Components.interfaces.nsIAppStartup.eRestartTouchEnvironment); + } return true; } return false; From 8073f9fe4d8fbd44aef921f0eb1d95cca2e4cc56 Mon Sep 17 00:00:00 2001 From: Steven MacLeod Date: Wed, 18 Dec 2013 21:29:54 +0100 Subject: [PATCH 29/36] Bug 945809 - Add Telemetry for Session Store data collection in the frame script. r=ttaubert X-Git-Commit-ID: 0ef7d331d06ef7e28a25932914128a7d7ff63d84 --- .../sessionstore/content/content-sessionStore.js | 13 ++++++++++++- .../components/sessionstore/src/SessionStore.jsm | 15 +++++++++++++++ toolkit/components/telemetry/Histograms.json | 7 +++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/browser/components/sessionstore/content/content-sessionStore.js b/browser/components/sessionstore/content/content-sessionStore.js index 407543c76c0c..4fb77aa47225 100644 --- a/browser/components/sessionstore/content/content-sessionStore.js +++ b/browser/components/sessionstore/content/content-sessionStore.js @@ -463,6 +463,8 @@ let MessageQueue = { // request. let sendMessage = sync ? sendRpcMessage : sendAsyncMessage; + let durationMs = Date.now(); + let data = {}; for (let [key, id] of this._lastUpdated) { // There is no data for the given key anymore because @@ -482,8 +484,17 @@ let MessageQueue = { data[key] = this._data.get(key)(); } + 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: data}); + sendMessage("SessionStore:update", { + id: this._id, + data: data, + telemetry: telemetry + }); // Increase our unique message ID. this._id++; diff --git a/browser/components/sessionstore/src/SessionStore.jsm b/browser/components/sessionstore/src/SessionStore.jsm index 1645a34f3b52..b12f0d2c4d96 100644 --- a/browser/components/sessionstore/src/SessionStore.jsm +++ b/browser/components/sessionstore/src/SessionStore.jsm @@ -99,6 +99,8 @@ 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, "DocShellCapabilities", "resource:///modules/sessionstore/DocShellCapabilities.jsm"); @@ -608,6 +610,7 @@ let SessionStoreInternal = { TabState.setSyncHandler(browser, aMessage.objects.handler); break; case "SessionStore:update": + this.recordTelemetry(aMessage.data.telemetry); TabState.update(browser, aMessage.data); this.saveStateDelayed(win); break; @@ -619,6 +622,18 @@ let SessionStoreInternal = { this._clearRestoringWindows(); }, + /** + * Record telemetry measurements stored in an object. + * @param telemetry + * {histogramID: value, ...} An object mapping histogramIDs to the + * value to be recorded for that ID, + */ + recordTelemetry: function (telemetry) { + for (let histogramId in telemetry){ + Telemetry.getHistogramById(histogramId).add(telemetry[histogramId]); + } + }, + /* ........ Window Event Handlers .............. */ /** diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index f9ffc3ac123b..141ccf93f767 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -2853,6 +2853,13 @@ "extended_statistics_ok": true, "description": "Session restore: Duration of the longest uninterruptible operation while collecting all window and tab data (ms)" }, + "FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS": { + "kind": "exponential", + "high": "30000", + "n_buckets": 10, + "extended_statistics_ok": true, + "description": "Session restore: Duration of the longest uninterruptible operation while collecting data in the content process (ms)" + }, "FX_SESSION_RESTORE_SERIALIZE_DATA_MS": { "kind": "exponential", "high": "1000", From 7948faa1c0af2875aeb014895133e29a6551e6eb Mon Sep 17 00:00:00 2001 From: Christian Ascheberg Date: Fri, 13 Dec 2013 13:57:20 +0200 Subject: [PATCH 30/36] Bug 590931 - [Australis] URL not greyed out in read-only location bar r=dao --- browser/themes/windows/browser.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css index 58d57e13d61c..bfc0d9c3705b 100644 --- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -850,7 +850,7 @@ menuitem.bookmark-item { background-color: rgba(255,255,255,.9); } -#urlbar:-moz-lwtheme[focused], +#urlbar:-moz-lwtheme[focused]:not([readonly]), .searchbar-textbox:-moz-lwtheme[focused] { background-color: white; } From 12a10f42fda5beac7c830b9fb7e783e584a35c13 Mon Sep 17 00:00:00 2001 From: Emma Sajic Date: Wed, 18 Dec 2013 15:57:48 -0500 Subject: [PATCH 31/36] Bug 885926 - [Australis] In file CustomizableUI.jsm, replace use of querySelector(idToSelector(id)) with getElementsByAttribute("id", id)[0] for palette queries; r=mconley --- .../customizableui/src/CustomizableUI.jsm | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/browser/components/customizableui/src/CustomizableUI.jsm b/browser/components/customizableui/src/CustomizableUI.jsm index 9f3056711e83..062a543e0051 100644 --- a/browser/components/customizableui/src/CustomizableUI.jsm +++ b/browser/components/customizableui/src/CustomizableUI.jsm @@ -883,7 +883,7 @@ let CustomizableUIInternal = { } let nextNode = null; if (aNextNodeId) { - nextNode = aAreaNode.customizationTarget.querySelector(idToSelector(aNextNodeId)); + nextNode = aAreaNode.customizationTarget.getElementsByAttribute("id", aNextNodeId)[0]; } return [aAreaNode.customizationTarget, nextNode]; }, @@ -1004,7 +1004,7 @@ let CustomizableUIInternal = { if (toolbox.palette) { // Attempt to locate a node with a matching ID within // the palette. - let node = toolbox.palette.querySelector(idToSelector(aId)); + let node = toolbox.palette.getElementsByAttribute("id", aId)[0]; if (node) { // Normalize the removable attribute. For backwards compat, this // is optional if the widget is located in the toolbox palette, @@ -1881,7 +1881,7 @@ let CustomizableUIInternal = { windowCache.delete(aWidgetId); } let widgetNode = window.document.getElementById(aWidgetId) || - window.gNavToolbox.palette.querySelector(idToSelector(aWidgetId)); + window.gNavToolbox.palette.getElementsByAttribute("id", aWidgetId)[0]; if (widgetNode) { widgetNode.remove(); } @@ -2014,7 +2014,7 @@ let CustomizableUIInternal = { if (!container.length) { return false; } - let existingNode = container[0].querySelector(idToSelector(aWidgetId)); + let existingNode = container[0].getElementsByAttribute("id", aWidgetId)[0]; if (existingNode) { return true; } @@ -2058,7 +2058,7 @@ let CustomizableUIInternal = { // Clone the array so we don't modify the actual placements... currentPlacements = [...currentPlacements]; currentPlacements = currentPlacements.filter((item) => { - let itemNode = container.querySelector(idToSelector(item)); + let itemNode = container.getElementsByAttribute("id", item)[0]; return itemNode && removableOrDefault(itemNode || item); }); } @@ -2972,7 +2972,7 @@ function XULWidgetGroupWrapper(aWidgetId) { if (!instance) { // Toolbar palettes aren't part of the document, so elements in there // won't be found via document.getElementById(). - instance = aWindow.gNavToolbox.palette.querySelector(idToSelector(aWidgetId)); + instance = aWindow.gNavToolbox.palette.getElementsByAttribute("id", aWidgetId)[0]; } let wrapper = new XULWidgetSingleWrapper(aWidgetId, instance); @@ -3222,7 +3222,7 @@ OverflowableToolbar.prototype = { } let inserted = false; for (; beforeNodeIndex < placements.length; beforeNodeIndex++) { - let beforeNode = this._target.querySelector(idToSelector(placements[beforeNodeIndex])); + let beforeNode = this._target.getElementsByAttribute("id", placements[beforeNodeIndex])[0]; if (beforeNode) { this._target.insertBefore(child, beforeNode); inserted = true; @@ -3355,7 +3355,7 @@ OverflowableToolbar.prototype = { return [this._list, null]; } - let nextNode = this._list.querySelector(idToSelector(aNextNodeId)); + let nextNode = this._list.getElementsByAttribute("id", aNextNodeId)[0]; // If this is the first item, we can actually just append the node // to the end of the toolbar. If it results in an overflow event, we'll move // the new node to the overflow target. @@ -3373,9 +3373,4 @@ OverflowableToolbar.prototype = { }, }; -// When IDs contain special characters, we need to escape them for use with querySelector: -function idToSelector(aId) { - return "#" + aId.replace(/[ !"'#$%&\(\)*+\-,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&'); -} - CustomizableUIInternal.initialize(); From 14cf08d9e06b37c7ab6f6e44301f13de55ca3f02 Mon Sep 17 00:00:00 2001 From: Michael Brennan Date: Fri, 15 Nov 2013 16:46:44 +0100 Subject: [PATCH 32/36] Bug 888373: Simple API for detecting startup/shutdown crashes;r=yoric,gps --- browser/installer/package-manifest.in | 2 + .../components/crashmonitor/CrashMonitor.jsm | 208 ++++++++++++++++++ .../crashmonitor/crashmonitor.manifest | 3 + toolkit/components/crashmonitor/moz.build | 17 ++ .../components/crashmonitor/nsCrashMonitor.js | 29 +++ toolkit/components/moz.build | 1 + 6 files changed, 260 insertions(+) create mode 100644 toolkit/components/crashmonitor/CrashMonitor.jsm create mode 100644 toolkit/components/crashmonitor/crashmonitor.manifest create mode 100644 toolkit/components/crashmonitor/moz.build create mode 100644 toolkit/components/crashmonitor/nsCrashMonitor.js diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index beec5b473a22..0f475cf1a095 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -379,6 +379,8 @@ @BINPATH@/components/Downloads.manifest @BINPATH@/components/DownloadLegacy.js @BINPATH@/components/BrowserPageThumbs.manifest +@BINPATH@/components/crashmonitor.manifest +@BINPATH@/components/nsCrashMonitor.js @BINPATH@/components/SiteSpecificUserAgent.js @BINPATH@/components/SiteSpecificUserAgent.manifest @BINPATH@/components/toolkitsearch.manifest diff --git a/toolkit/components/crashmonitor/CrashMonitor.jsm b/toolkit/components/crashmonitor/CrashMonitor.jsm new file mode 100644 index 000000000000..8164b7a917aa --- /dev/null +++ b/toolkit/components/crashmonitor/CrashMonitor.jsm @@ -0,0 +1,208 @@ +/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Crash Monitor + * + * Monitors execution of a program to detect possible crashes. After + * program termination, the monitor can be queried during the next run + * to determine whether the last run exited cleanly or not. + * + * The monitoring is done by registering and listening for special + * notifications, or checkpoints, known to be sent by the monitored + * program as different stages in the execution are reached. As they + * are observed, these notifications are written asynchronously to a + * checkpoint file. + * + * During next program startup the crash monitor reads the checkpoint + * file from the last session. If notifications are missing, a crash + * has likely happened. By inspecting the notifications present, it is + * possible to determine what stages were reached in the program + * before the crash. + * + * Note that since the file is written asynchronously it is possible + * that a received notification is lost if the program crashes right + * after a checkpoint, but before crash monitor has been able to write + * it to disk. Thus, while the presence of a notification in the + * checkpoint file tells us that the corresponding stage was reached + * during the last run, the absence of a notification after a crash + * does not necessarily tell us that the checkpoint wasn't reached. + */ + +this.EXPORTED_SYMBOLS = [ "CrashMonitor" ]; + +const Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/osfile.jsm"); +Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); +Cu.import("resource://gre/modules/AsyncShutdown.jsm"); + +const NOTIFICATIONS = [ + "final-ui-startup", + "sessionstore-windows-restored", + "quit-application-granted", + "quit-application", + "profile-change-net-teardown", + "profile-change-teardown", + "profile-before-change", + "sessionstore-final-state-write-complete" +]; + +let CrashMonitorInternal = { + + /** + * Notifications received during the current session. + * + * Object where a property with a value of |true| means that the + * notification of the same name has been received at least once by + * the CrashMonitor during this session. Notifications that have not + * yet been received are not present as properties. |NOTIFICATIONS| + * lists the notifications tracked by the CrashMonitor. + */ + checkpoints: {}, + + /** + * Notifications received during previous session. + * + * Available after |loadPreviousCheckpoints|. Promise which resolves + * to an object containing a set of properties, where a property + * with a value of |true| means that the notification with the same + * name as the property name was received at least once last + * session. + */ + previousCheckpoints: null, + + /* Deferred for AsyncShutdown blocker */ + profileBeforeChangeDeferred: Promise.defer(), + + /** + * Path to checkpoint file. + * + * Each time a new notification is received, this file is written to + * disc to reflect the information in |checkpoints|. + */ + path: OS.Path.join(OS.Constants.Path.profileDir, "sessionCheckpoints.json"), + + /** + * Load checkpoints from previous session asynchronously. + * + * @return {Promise} A promise that resolves/rejects once loading is complete + */ + loadPreviousCheckpoints: function () { + let promise = Task.spawn(function () { + let notifications; + try { + let decoder = new TextDecoder(); + let data = yield OS.File.read(CrashMonitorInternal.path); + let contents = decoder.decode(data); + notifications = JSON.parse(contents); + } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) { + // If checkpoint file cannot be read + throw new Task.Result(null); + } catch (ex) { + Cu.reportError("Error while loading crash monitor data: " + ex); + throw new Task.Result(null); + } + throw new Task.Result(Object.freeze(notifications)); + }); + + CrashMonitorInternal.previousCheckpoints = promise; + return promise; + } +}; + +this.CrashMonitor = { + + /** + * Notifications received during previous session. + * + * Return object containing the set of notifications received last + * session as keys with values set to |true|. + * + * @return {Promise} A promise resolving to previous checkpoints + */ + get previousCheckpoints() { + if (!CrashMonitorInternal.initialized) { + throw new Error("CrashMonitor must be initialized before getting previous checkpoints"); + } + + return CrashMonitorInternal.previousCheckpoints + }, + + /** + * Initialize CrashMonitor. + * + * Should only be called from the CrashMonitor XPCOM component. + * + * @return {Promise} + */ + init: function () { + if (CrashMonitorInternal.initialized) { + throw new Error("CrashMonitor.init() must only be called once!"); + } + + let promise = CrashMonitorInternal.loadPreviousCheckpoints(); + // Add "profile-after-change" to checkpoint as this method is + // called after receiving it + CrashMonitorInternal.checkpoints["profile-after-change"] = true; + + NOTIFICATIONS.forEach(function (aTopic) { + Services.obs.addObserver(this, aTopic, false); + }, this); + + // Add shutdown blocker for profile-before-change + AsyncShutdown.profileBeforeChange.addBlocker( + "CrashMonitor: Writing notifications to file after receiving profile-before-change", + CrashMonitorInternal.profileBeforeChangeDeferred.promise + ); + + CrashMonitorInternal.initialized = true; + return promise; + }, + + /** + * Handle registered notifications. + * + * Update checkpoint file for every new notification received. + */ + observe: function (aSubject, aTopic, aData) { + if (!(aTopic in CrashMonitorInternal.checkpoints)) { + // If this is the first time this notification is received, + // remember it and write it to file + CrashMonitorInternal.checkpoints[aTopic] = true; + Task.spawn(function() { + try { + let data = JSON.stringify(CrashMonitorInternal.checkpoints); + + /* Write to the checkpoint file asynchronously, off the main + * thread, for performance reasons. Note that this means + * that there's not a 100% guarantee that the file will be + * written by the time the notification completes. The + * exception is profile-before-change which has a shutdown + * blocker. */ + yield OS.File.writeAtomic( + CrashMonitorInternal.path, + data, {tmpPath: CrashMonitorInternal.path + ".tmp"}); + + } finally { + // Resolve promise for blocker + if (aTopic == "profile-before-change") { + CrashMonitorInternal.profileBeforeChangeDeferred.resolve(); + } + } + }); + } + + if (NOTIFICATIONS.every(elem => elem in CrashMonitorInternal.checkpoints)) { + // All notifications received, unregister observers + NOTIFICATIONS.forEach(function (aTopic) { + Services.obs.removeObserver(this, aTopic); + }, this); + } + } +}; +Object.freeze(this.CrashMonitor); diff --git a/toolkit/components/crashmonitor/crashmonitor.manifest b/toolkit/components/crashmonitor/crashmonitor.manifest new file mode 100644 index 000000000000..59e336f82a6a --- /dev/null +++ b/toolkit/components/crashmonitor/crashmonitor.manifest @@ -0,0 +1,3 @@ +component {d9d75e86-8f17-4c57-993e-f738f0d86d42} nsCrashMonitor.js +contract @mozilla.org/toolkit/crashmonitor;1 {d9d75e86-8f17-4c57-993e-f738f0d86d42} +category profile-after-change CrashMonitor @mozilla.org/toolkit/crashmonitor;1 diff --git a/toolkit/components/crashmonitor/moz.build b/toolkit/components/crashmonitor/moz.build new file mode 100644 index 000000000000..a94de010b43f --- /dev/null +++ b/toolkit/components/crashmonitor/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXTRA_JS_MODULES = [ + 'CrashMonitor.jsm', +] + +EXTRA_COMPONENTS += [ + 'crashmonitor.manifest', +] + +EXTRA_PP_COMPONENTS += [ + 'nsCrashMonitor.js', +] diff --git a/toolkit/components/crashmonitor/nsCrashMonitor.js b/toolkit/components/crashmonitor/nsCrashMonitor.js new file mode 100644 index 000000000000..77a80ae531c3 --- /dev/null +++ b/toolkit/components/crashmonitor/nsCrashMonitor.js @@ -0,0 +1,29 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +let Scope = {} +Components.utils.import("resource://gre/modules/CrashMonitor.jsm", Scope); +let MonitorAPI = Scope.CrashMonitor; + +function CrashMonitor() {}; + +CrashMonitor.prototype = { + + classID: Components.ID("{d9d75e86-8f17-4c57-993e-f738f0d86d42}"), + contractID: "@mozilla.org/toolkit/crashmonitor;1", + + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]), + + observe: function (aSubject, aTopic, aData) { + switch (aTopic) { + case "profile-after-change": + MonitorAPI.init(); + } + }, +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CrashMonitor]); diff --git a/toolkit/components/moz.build b/toolkit/components/moz.build index 55615075f36c..df2b8a46bdba 100644 --- a/toolkit/components/moz.build +++ b/toolkit/components/moz.build @@ -16,6 +16,7 @@ PARALLEL_DIRS += [ 'console', 'contentprefs', 'cookie', + 'crashmonitor', 'diskspacewatcher', 'downloads', 'exthelper', From 14305584be4301c65ed5a36c2042ba8519c701ff Mon Sep 17 00:00:00 2001 From: Margaret Leibovic Date: Wed, 18 Dec 2013 13:52:21 -0800 Subject: [PATCH 33/36] Bug 948894 - Fix typos. r=me --- mobile/android/chrome/content/aboutApps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/android/chrome/content/aboutApps.js b/mobile/android/chrome/content/aboutApps.js index f2cc471425b1..8c75fec00d54 100644 --- a/mobile/android/chrome/content/aboutApps.js +++ b/mobile/android/chrome/content/aboutApps.js @@ -88,7 +88,7 @@ function onLoad(aEvent) { let elmts = document.querySelectorAll("[pref]"); for (let i = 0; i < elmts.length; i++) { - elmts[i]..addEventListener("click", openLink, false)); + elmts[i].addEventListener("click", openLink, false); } navigator.mozApps.mgmt.oninstall = onInstall; From 6df95c0a7693db8731564ab6b6033d37c528520d Mon Sep 17 00:00:00 2001 From: Margaret Leibovic Date: Wed, 18 Dec 2013 11:39:18 -0800 Subject: [PATCH 34/36] Bug 951605 - Fix identified/verified image reversal. r=mfinkle --- mobile/android/base/SiteIdentity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/android/base/SiteIdentity.java b/mobile/android/base/SiteIdentity.java index 7e76314513c9..b4b78196fc0a 100644 --- a/mobile/android/base/SiteIdentity.java +++ b/mobile/android/base/SiteIdentity.java @@ -21,8 +21,8 @@ public class SiteIdentity { // levels in site_security_level.xml public enum SecurityMode { UNKNOWN("unknown"), - VERIFIED("verified"), IDENTIFIED("identified"), + VERIFIED("verified"), MIXED_CONTENT_BLOCKED("mixed_content_blocked"), MIXED_CONTENT_LOADED("mixed_content_loaded"); From 80482f6b66b148f369e675fa0ebd1b8a01e56a4c Mon Sep 17 00:00:00 2001 From: Mike Conley Date: Wed, 18 Dec 2013 16:54:59 -0500 Subject: [PATCH 35/36] Bug 951747 - [Australis] Add simple measurement to BrowserUITelemetry for toolbar contents. r=jaws. --- browser/modules/BrowserUITelemetry.jsm | 126 ++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/browser/modules/BrowserUITelemetry.jsm b/browser/modules/BrowserUITelemetry.jsm index 1122c89abb04..f78d9277df95 100644 --- a/browser/modules/BrowserUITelemetry.jsm +++ b/browser/modules/BrowserUITelemetry.jsm @@ -6,7 +6,7 @@ this.EXPORTED_SYMBOLS = ["BrowserUITelemetry"]; -const Cu = Components.utils; +const {interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -18,6 +18,88 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm"); +XPCOMUtils.defineLazyGetter(this, "DEFAULT_TOOLBAR_PLACEMENTS", function() { + let result = { + "PanelUI-contents": [ + "edit-controls", + "zoom-controls", + "new-window-button", + "privatebrowsing-button", + "save-page-button", + "print-button", + "history-panelmenu", + "fullscreen-button", + "find-button", + "preferences-button", + "add-ons-button", + ], + "nav-bar": [ + "urlbar-container", + "search-container", + "webrtc-status-button", + "bookmarks-menu-button", + "downloads-button", + "home-button", + "social-share-button", + ], + // It's true that toolbar-menubar is not visible + // on OS X, but the XUL node is definitely present + // in the document. + "toolbar-menubar": [ + "menubar-items", + ], + "TabsToolbar": [ + "tabbrowser-tabs", + "new-tab-button", + "alltabs-button", + "tabs-closebutton", + ], + "PersonalToolbar": [ + "personal-bookmarks", + ], + }; + + let showCharacterEncoding = Services.prefs.getComplexValue( + "browser.menu.showCharacterEncoding", + Ci.nsIPrefLocalizedString + ).data; + if (showCharacterEncoding == "true") { + result["PanelUI-contents"].push("characterencoding-button"); + } + + if (Services.sysinfo.getProperty("hasWindowsTouchInterface")) { + result["PanelUI-contents"].push("switch-to-metro-button"); + } + + return result; +}); + +XPCOMUtils.defineLazyGetter(this, "PALETTE_ITEMS", function() { + let result = [ + "open-file-button", + "developer-button", + "feed-button", + "email-link-button", + "sync-button", + "tabview-button", + ]; + + let panelPlacements = DEFAULT_TOOLBAR_PLACEMENTS["PanelUI-contents"]; + if (panelPlacements.indexOf("characterencoding-button") == -1) { + result.push("characterencoding-button"); + } + + return result; +}); + +XPCOMUtils.defineLazyGetter(this, "DEFAULT_ITEMS", function() { + let result = []; + for (let [, buttons] of Iterator(DEFAULT_TOOLBAR_PLACEMENTS)) { + result = result.concat(buttons); + } + return result; +}); + const ALL_BUILTIN_ITEMS = [ "fullscreen-button", "switch-to-metro-button", @@ -249,6 +331,48 @@ this.BrowserUITelemetry = { let bookmarksBar = document.getElementById("PersonalToolbar"); result.bookmarksBarEnabled = bookmarksBar && !bookmarksBar.collapsed; + // Examine all customizable areas and see what default items + // are present and missing. + let defaultKept = []; + let defaultMoved = []; + let nondefaultAdded = []; + + for (let areaID of CustomizableUI.areas) { + let items = CustomizableUI.getWidgetIdsInArea(areaID); + for (let item of items) { + // Is this a default item? + if (DEFAULT_ITEMS.indexOf(item) != -1) { + // Ok, it's a default item - but is it in its default + // toolbar? We use Array.isArray instead of checking for + // toolbarID in DEFAULT_TOOLBAR_PLACEMENTS because an add-on might + // be clever and give itself the id of "toString" or something. + if (Array.isArray(DEFAULT_TOOLBAR_PLACEMENTS[areaID]) && + DEFAULT_TOOLBAR_PLACEMENTS[areaID].indexOf(item) != -1) { + // The item is in its default toolbar + defaultKept.push(item); + } else { + defaultMoved.push(item); + } + } else if (PALETTE_ITEMS.indexOf(item) != -1) { + // It's a palette item that's been moved into a toolbar + nondefaultAdded.push(item); + } + // else, it's provided by an add-on, and we won't record it. + } + } + + // Now go through the items in the palette to see what default + // items are in there. + let paletteItems = + CustomizableUI.getUnusedWidgets(win.gNavToolbox.palette); + let defaultRemoved = [item.id for (item of paletteItems) + if (DEFAULT_ITEMS.indexOf(item.id) != -1)]; + + result.defaultKept = defaultKept; + result.defaultMoved = defaultMoved; + result.nondefaultAdded = nondefaultAdded; + result.defaultRemoved = defaultRemoved; + result.countableEvents = this._countableEvents; return result; From d1c69daf695fbc07c92b518381ca843553be201a Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 18 Dec 2013 14:17:27 -0800 Subject: [PATCH 36/36] Bug 929349 - Integrate a tracing debugger into our existing debugger; r=vporof,past --- browser/app/profile/firefox.js | 1 + .../devtools/debugger/debugger-controller.js | 291 ++++++++++++-- browser/devtools/debugger/debugger-panes.js | 371 ++++++++++++++++++ browser/devtools/debugger/debugger-view.js | 9 +- browser/devtools/debugger/debugger.xul | 49 +++ browser/devtools/debugger/panel.js | 5 +- browser/devtools/debugger/test/browser.ini | 6 + .../debugger/test/browser_dbg_tracing-01.js | 109 +++++ .../debugger/test/browser_dbg_tracing-02.js | 78 ++++ .../debugger/test/browser_dbg_tracing-03.js | 70 ++++ .../debugger/test/browser_dbg_tracing-04.js | 86 ++++ .../devtools/debugger/test/code_tracing-01.js | 29 ++ .../debugger/test/doc_tracing-01.html | 12 + browser/devtools/debugger/test/head.js | 55 +++ .../devtools/shared/widgets/FastListWidget.js | 210 ++++++++++ .../devtools/shared/widgets/VariablesView.jsm | 2 +- .../chrome/browser/devtools/debugger.dtd | 17 + .../browser/devtools/debugger.properties | 8 + browser/themes/linux/devtools/debugger.css | 118 +++++- browser/themes/linux/devtools/tracer-icon.png | Bin 0 -> 709 bytes .../themes/linux/devtools/tracer-icon@2x.png | Bin 0 -> 1323 bytes browser/themes/linux/jar.mn | 2 + browser/themes/osx/devtools/debugger.css | 118 +++++- browser/themes/osx/devtools/tracer-icon.png | Bin 0 -> 709 bytes .../themes/osx/devtools/tracer-icon@2x.png | Bin 0 -> 1323 bytes browser/themes/osx/jar.mn | 2 + browser/themes/windows/devtools/debugger.css | 118 +++++- .../themes/windows/devtools/tracer-icon.png | Bin 0 -> 709 bytes .../windows/devtools/tracer-icon@2x.png | Bin 0 -> 1323 bytes browser/themes/windows/jar.mn | 4 + toolkit/devtools/DevToolsUtils.js | 28 +- toolkit/devtools/DevToolsUtils.jsm | 1 + toolkit/devtools/server/actors/tracer.js | 152 +++---- .../server/tests/unit/test_trace_actor-05.js | 13 +- .../server/tests/unit/test_trace_actor-06.js | 36 +- 35 files changed, 1822 insertions(+), 178 deletions(-) create mode 100644 browser/devtools/debugger/test/browser_dbg_tracing-01.js create mode 100644 browser/devtools/debugger/test/browser_dbg_tracing-02.js create mode 100644 browser/devtools/debugger/test/browser_dbg_tracing-03.js create mode 100644 browser/devtools/debugger/test/browser_dbg_tracing-04.js create mode 100644 browser/devtools/debugger/test/code_tracing-01.js create mode 100644 browser/devtools/debugger/test/doc_tracing-01.html create mode 100644 browser/devtools/shared/widgets/FastListWidget.js create mode 100644 browser/themes/linux/devtools/tracer-icon.png create mode 100644 browser/themes/linux/devtools/tracer-icon@2x.png create mode 100644 browser/themes/osx/devtools/tracer-icon.png create mode 100644 browser/themes/osx/devtools/tracer-icon@2x.png create mode 100644 browser/themes/windows/devtools/tracer-icon.png create mode 100644 browser/themes/windows/devtools/tracer-icon@2x.png diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index de31be3d9367..b176fbeafedf 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1121,6 +1121,7 @@ pref("devtools.debugger.pause-on-exceptions", false); pref("devtools.debugger.ignore-caught-exceptions", true); pref("devtools.debugger.source-maps-enabled", true); pref("devtools.debugger.pretty-print-enabled", true); +pref("devtools.debugger.tracer", false); // The default Debugger UI settings pref("devtools.debugger.ui.panes-sources-width", 200); diff --git a/browser/devtools/debugger/debugger-controller.js b/browser/devtools/debugger/debugger-controller.js index ea42fb5ecd64..f03d25c453a7 100644 --- a/browser/devtools/debugger/debugger-controller.js +++ b/browser/devtools/debugger/debugger-controller.js @@ -99,6 +99,7 @@ const promise = require("sdk/core/promise"); const Editor = require("devtools/sourceeditor/editor"); const DebuggerEditor = require("devtools/sourceeditor/debugger.js"); const {Tooltip} = require("devtools/shared/widgets/Tooltip"); +const FastListWidget = require("devtools/shared/widgets/FastListWidget"); XPCOMUtils.defineLazyModuleGetter(this, "Parser", "resource:///modules/devtools/Parser.jsm"); @@ -192,6 +193,7 @@ let DebuggerController = { this.SourceScripts.disconnect(); this.StackFrames.disconnect(); this.ThreadState.disconnect(); + this.Tracer.disconnect(); this.disconnect(); // Chrome debugging needs to close its parent process on shutdown. @@ -218,39 +220,44 @@ let DebuggerController = { return this._connection; } - let deferred = promise.defer(); - this._connection = deferred.promise; + let startedDebugging = promise.defer(); + this._connection = startedDebugging.promise; if (!window._isChromeDebugger) { let target = this._target; - let { client, form: { chromeDebugger }, threadActor } = target; + let { client, form: { chromeDebugger, traceActor }, threadActor } = target; target.on("close", this._onTabDetached); target.on("navigate", this._onTabNavigated); target.on("will-navigate", this._onTabNavigated); + this.client = client; if (target.chrome) { - this._startChromeDebugging(client, chromeDebugger, deferred.resolve); + this._startChromeDebugging(chromeDebugger, startedDebugging.resolve); } else { - this._startDebuggingTab(client, threadActor, deferred.resolve); + this._startDebuggingTab(threadActor, startedDebugging.resolve); + const startedTracing = promise.defer(); + this._startTracingTab(traceActor, startedTracing.resolve); + + return promise.all([startedDebugging.promise, startedTracing.promise]); } - return deferred.promise; + return startedDebugging.promise; } // Chrome debugging needs to make its own connection to the debuggee. let transport = debuggerSocketConnect( Prefs.chromeDebuggingHost, Prefs.chromeDebuggingPort); - let client = new DebuggerClient(transport); + let client = this.client = new DebuggerClient(transport); client.addListener("tabNavigated", this._onTabNavigated); client.addListener("tabDetached", this._onTabDetached); client.connect(() => { client.listTabs(aResponse => { - this._startChromeDebugging(client, aResponse.chromeDebugger, deferred.resolve); + this._startChromeDebugging(aResponse.chromeDebugger, startedDebugging.resolve); }); }); - return deferred.promise; + return startedDebugging.promise; }, /** @@ -331,21 +338,13 @@ let DebuggerController = { /** * Sets up a debugging session. * - * @param DebuggerClient aClient - * The debugger client. * @param string aThreadActor * The remote protocol grip of the tab. * @param function aCallback - * A function to invoke once the client attached to the active thread. + * A function to invoke once the client attaches to the active thread. */ - _startDebuggingTab: function(aClient, aThreadActor, aCallback) { - if (!aClient) { - Cu.reportError("No client found!"); - return; - } - this.client = aClient; - - aClient.attachThread(aThreadActor, (aResponse, aThreadClient) => { + _startDebuggingTab: function(aThreadActor, aCallback) { + this.client.attachThread(aThreadActor, (aResponse, aThreadClient) => { if (!aThreadClient) { Cu.reportError("Couldn't attach to thread: " + aResponse.error); return; @@ -366,21 +365,13 @@ let DebuggerController = { /** * Sets up a chrome debugging session. * - * @param DebuggerClient aClient - * The debugger client. * @param object aChromeDebugger * The remote protocol grip of the chrome debugger. * @param function aCallback - * A function to invoke once the client attached to the active thread. + * A function to invoke once the client attaches to the active thread. */ - _startChromeDebugging: function(aClient, aChromeDebugger, aCallback) { - if (!aClient) { - Cu.reportError("No client found!"); - return; - } - this.client = aClient; - - aClient.attachThread(aChromeDebugger, (aResponse, aThreadClient) => { + _startChromeDebugging: function(aChromeDebugger, aCallback) { + this.client.attachThread(aChromeDebugger, (aResponse, aThreadClient) => { if (!aThreadClient) { Cu.reportError("Couldn't attach to thread: " + aResponse.error); return; @@ -398,6 +389,30 @@ let DebuggerController = { }, { useSourceMaps: Prefs.sourceMapsEnabled }); }, + /** + * Sets up an execution tracing session. + * + * @param object aTraceActor + * The remote protocol grip of the trace actor. + * @param function aCallback + * A function to invoke once the client attaches to the tracer. + */ + _startTracingTab: function(aTraceActor, aCallback) { + this.client.attachTracer(aTraceActor, (response, traceClient) => { + if (!traceClient) { + DevToolsUtils.reportError(new Error("Failed to attach to tracing actor.")); + return; + } + + this.traceClient = traceClient; + this.Tracer.connect(); + + if (aCallback) { + aCallback(); + } + }); + }, + /** * Detach and reattach to the thread actor with useSourceMaps true, blow * away old sources and get them again. @@ -1411,6 +1426,218 @@ SourceScripts.prototype = { } }; +/** + * Tracer update the UI according to the messages exchanged with the tracer + * actor. + */ +function Tracer() { + this._trace = null; + this._idCounter = 0; + this.onTraces = this.onTraces.bind(this); +} + +Tracer.prototype = { + get client() { + return DebuggerController.client; + }, + + get traceClient() { + return DebuggerController.traceClient; + }, + + get tracing() { + return !!this._trace; + }, + + /** + * Hooks up the debugger controller with the tracer client. + */ + connect: function() { + this._stack = []; + this.client.addListener("traces", this.onTraces); + }, + + /** + * Disconnects the debugger controller from the tracer client. Any further + * communcation with the tracer actor will not have any effect on the UI. + */ + disconnect: function() { + this._stack = null; + this.client.removeListener("traces", this.onTraces); + }, + + /** + * Instructs the tracer actor to start tracing. + */ + startTracing: function(aCallback = () => {}) { + DebuggerView.Tracer.selectTab(); + if (this.tracing) { + return; + } + this._trace = "dbg.trace" + Math.random(); + this.traceClient.startTrace([ + "name", + "location", + "parameterNames", + "depth", + "arguments", + "return", + "throw", + "yield" + ], this._trace, (aResponse) => { + const { error } = aResponse; + if (error) { + DevToolsUtils.reportException(error); + this._trace = null; + } + + aCallback(aResponse); + }); + }, + + /** + * Instructs the tracer actor to stop tracing. + */ + stopTracing: function(aCallback = () => {}) { + if (!this.tracing) { + return; + } + this.traceClient.stopTrace(this._trace, aResponse => { + const { error } = aResponse; + if (error) { + DevToolsUtils.reportException(error); + } + + this._trace = null; + aCallback(aResponse); + }); + }, + + onTraces: function (aEvent, { traces }) { + const tracesLength = traces.length; + let tracesToShow; + if (tracesLength > TracerView.MAX_TRACES) { + tracesToShow = traces.slice(tracesLength - TracerView.MAX_TRACES, + tracesLength); + DebuggerView.Tracer.empty(); + this._stack.splice(0, this._stack.length); + } else { + tracesToShow = traces; + } + + for (let t of tracesToShow) { + if (t.type == "enteredFrame") { + this._onCall(t); + } else { + this._onReturn(t); + } + } + + DebuggerView.Tracer.commit(); + }, + + /** + * Callback for handling a new call frame. + */ + _onCall: function({ name, location, parameterNames, depth, arguments: args }) { + const item = { + name: name, + location: location, + id: this._idCounter++ + }; + this._stack.push(item); + DebuggerView.Tracer.addTrace({ + type: "call", + name: name, + location: location, + depth: depth, + parameterNames: parameterNames, + arguments: args, + frameId: item.id + }); + }, + + /** + * Callback for handling an exited frame. + */ + _onReturn: function(aPacket) { + if (!this._stack.length) { + return; + } + + const { name, id, location } = this._stack.pop(); + DebuggerView.Tracer.addTrace({ + type: aPacket.why, + name: name, + location: location, + depth: aPacket.depth, + frameId: id, + returnVal: aPacket.return || aPacket.throw || aPacket.yield + }); + }, + + /** + * Create an object which has the same interface as a normal object client, + * but since we already have all the information for an object that we will + * ever get (the server doesn't create actors when tracing, just firehoses + * data and forgets about it) just return the data immdiately. + * + * @param Object aObject + * The tracer object "grip" (more like a limited snapshot). + * @returns Object + * The synchronous client object. + */ + syncGripClient: function(aObject) { + return { + get isFrozen() { return aObject.frozen; }, + get isSealed() { return aObject.sealed; }, + get isExtensible() { return aObject.extensible; }, + + get ownProperties() { return aObject.ownProperties; }, + get prototype() { return null; }, + + getParameterNames: callback => callback(aObject), + getPrototypeAndProperties: callback => callback(aObject), + getPrototype: callback => callback(aObject), + + getOwnPropertyNames: (callback) => { + callback({ + ownPropertyNames: aObject.ownProperties + ? Object.keys(aObject.ownProperties) + : [] + }); + }, + + getProperty: (property, callback) => { + callback({ + descriptor: aObject.ownProperties + ? aObject.ownProperties[property] + : null + }); + }, + + getDisplayString: callback => callback("[object " + aObject.class + "]"), + + getScope: callback => callback({ + error: "scopeNotAvailable", + message: "Cannot get scopes for traced objects" + }) + }; + }, + + /** + * Wraps object snapshots received from the tracer server so that we can + * differentiate them from long living object grips from the debugger server + * in the variables view. + * + * @param Object aObject + * The object snapshot from the tracer actor. + */ + WrappedObject: function(aObject) { + this.object = aObject; + } +}; + /** * Handles breaking on event listeners in the currently debugged target. */ @@ -1955,6 +2182,7 @@ let Prefs = new ViewHelpers.Prefs("devtools", { ignoreCaughtExceptions: ["Bool", "debugger.ignore-caught-exceptions"], sourceMapsEnabled: ["Bool", "debugger.source-maps-enabled"], prettyPrintEnabled: ["Bool", "debugger.pretty-print-enabled"], + tracerEnabled: ["Bool", "debugger.tracer"], editorTabSize: ["Int", "editor.tabsize"] }); @@ -1982,6 +2210,7 @@ DebuggerController.StackFrames = new StackFrames(); DebuggerController.SourceScripts = new SourceScripts(); DebuggerController.Breakpoints = new Breakpoints(); DebuggerController.Breakpoints.DOM = new EventListeners(); +DebuggerController.Tracer = new Tracer(); /** * Export some properties to the global scope for easier access. diff --git a/browser/devtools/debugger/debugger-panes.js b/browser/devtools/debugger/debugger-panes.js index 352f6fdfd339..c8e6e084c163 100644 --- a/browser/devtools/debugger/debugger-panes.js +++ b/browser/devtools/debugger/debugger-panes.js @@ -1056,6 +1056,376 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, { _conditionalPopupVisible: false }); +/** + * Functions handling the traces UI. + */ +function TracerView() { + this._selectedItem = null; + this._matchingItems = null; + this.widget = null; + + this._highlightItem = this._highlightItem.bind(this); + this._isNotSelectedItem = this._isNotSelectedItem.bind(this); + + this._unhighlightMatchingItems = + DevToolsUtils.makeInfallible(this._unhighlightMatchingItems.bind(this)); + this._onToggleTracing = + DevToolsUtils.makeInfallible(this._onToggleTracing.bind(this)); + this._onStartTracing = + DevToolsUtils.makeInfallible(this._onStartTracing.bind(this)); + this._onClear = DevToolsUtils.makeInfallible(this._onClear.bind(this)); + this._onSelect = DevToolsUtils.makeInfallible(this._onSelect.bind(this)); + this._onMouseOver = + DevToolsUtils.makeInfallible(this._onMouseOver.bind(this)); + this._onSearch = DevToolsUtils.makeInfallible(this._onSearch.bind(this)); +} + +TracerView.MAX_TRACES = 200; + +TracerView.prototype = Heritage.extend(WidgetMethods, { + /** + * Initialization function, called when the debugger is started. + */ + initialize: function() { + dumpn("Initializing the TracerView"); + + this._traceButton = document.getElementById("trace"); + this._tracerTab = document.getElementById("tracer-tab"); + + // Remove tracer related elements from the dom and tear everything down if + // the tracer isn't enabled. + if (!Prefs.tracerEnabled) { + this._traceButton.remove(); + this._traceButton = null; + this._tracerTab.remove(); + this._tracerTab = null; + document.getElementById("tracer-tabpanel").remove(); + this.widget = null; + return; + } + + this.widget = new FastListWidget(document.getElementById("tracer-traces")); + + this._traceButton.removeAttribute("hidden"); + this._tracerTab.removeAttribute("hidden"); + + this._tracerDeck = document.getElementById("tracer-deck"); + this._search = document.getElementById("tracer-search"); + + this._template = document.getElementsByClassName("trace-item-template")[0]; + this._templateItem = this._template.getElementsByClassName("trace-item")[0]; + this._templateTypeIcon = this._template.getElementsByClassName("trace-type")[0]; + this._templateNameNode = this._template.getElementsByClassName("trace-name")[0]; + + this.widget.addEventListener("select", this._onSelect, false); + this.widget.addEventListener("mouseover", this._onMouseOver, false); + this.widget.addEventListener("mouseout", this._unhighlightMatchingItems, false); + + this._search.addEventListener("input", this._onSearch, false); + + this._startTooltip = L10N.getStr("startTracingTooltip"); + this._stopTooltip = L10N.getStr("stopTracingTooltip"); + this._traceButton.setAttribute("tooltiptext", this._startTooltip); + }, + + /** + * Destruction function, called when the debugger is closed. + */ + destroy: function() { + dumpn("Destroying the TracerView"); + + if (!this.widget) { + return; + } + + this.widget.removeEventListener("select", this._onSelect, false); + this.widget.removeEventListener("mouseover", this._onMouseOver, false); + this.widget.removeEventListener("mouseout", this._unhighlightMatchingItems, false); + this._search.removeEventListener("input", this._onSearch, false); + }, + + /** + * Function invoked by the "toggleTracing" command to switch the tracer state. + */ + _onToggleTracing: function() { + if (DebuggerController.Tracer.tracing) { + this._onStopTracing(); + } else { + this._onStartTracing(); + } + }, + + /** + * Function invoked either by the "startTracing" command or by + * _onToggleTracing to start execution tracing in the backend. + */ + _onStartTracing: function() { + this._tracerDeck.selectedIndex = 0; + this._traceButton.setAttribute("checked", true); + this._traceButton.setAttribute("tooltiptext", this._stopTooltip); + this.empty(); + DebuggerController.Tracer.startTracing(); + }, + + /** + * Function invoked by _onToggleTracing to stop execution tracing in the + * backend. + */ + _onStopTracing: function() { + this._traceButton.removeAttribute("checked"); + this._traceButton.setAttribute("tooltiptext", this._startTooltip); + DebuggerController.Tracer.stopTracing(); + }, + + /** + * Function invoked by the "clearTraces" command to empty the traces pane. + */ + _onClear: function() { + this.empty(); + }, + + /** + * Populate the given parent scope with the variable with the provided name + * and value. + * + * @param String aName + * The name of the variable. + * @param Object aParent + * The parent scope. + * @param Object aValue + * The value of the variable. + */ + _populateVariable: function(aName, aParent, aValue) { + let item = aParent.addItem(aName, { value: aValue }); + if (aValue) { + DebuggerView.Variables.controller.populate( + item, new DebuggerController.Tracer.WrappedObject(aValue)); + item.expand(); + item.twisty = false; + } + }, + + /** + * Handler for the widget's "select" event. Displays parameters, exception, or + * return value depending on whether the selected trace is a call, throw, or + * return respectively. + * + * @param Object traceItem + * The selected trace item. + */ + _onSelect: function _onSelect({ detail: traceItem }) { + if (!traceItem) { + return; + } + + const data = traceItem.attachment.trace; + const { location: { url, line } } = data; + DebuggerView.setEditorLocation(url, line, { noDebug: true }); + + DebuggerView.Variables.empty(); + const scope = DebuggerView.Variables.addScope(); + + if (data.type == "call") { + const params = DevToolsUtils.zip(data.parameterNames, data.arguments); + for (let [name, val] of params) { + if (val === undefined) { + scope.addItem(name, { value: "" }); + } else { + this._populateVariable(name, scope, val); + } + } + } else { + const varName = "<" + + (data.type == "throw" ? "exception" : data.type) + + ">"; + this._populateVariable(varName, scope, data.returnVal); + } + + scope.expand(); + DebuggerView.showInstrumentsPane(); + }, + + /** + * Add the hover frame enter/exit highlighting to a given item. + */ + _highlightItem: function(aItem) { + aItem.target.querySelector(".trace-item") + .classList.add("selected-matching"); + }, + + /** + * Remove the hover frame enter/exit highlighting to a given item. + */ + _unhighlightItem: function(aItem) { + if (!aItem || !aItem.target) { + return; + } + const match = aItem.target.querySelector(".selected-matching"); + if (match) { + match.classList.remove("selected-matching"); + } + }, + + /** + * Remove the frame enter/exit pair highlighting we do when hovering. + */ + _unhighlightMatchingItems: function() { + if (this._matchingItems) { + this._matchingItems.forEach(this._unhighlightItem); + this._matchingItems = null; + } + }, + + /** + * Returns true if the given item is not the selected item. + */ + _isNotSelectedItem: function(aItem) { + return aItem !== this.selectedItem; + }, + + /** + * Highlight the frame enter/exit pair of items for the given item. + */ + _highlightMatchingItems: function(aItem) { + this._unhighlightMatchingItems(); + this._matchingItems = this.items.filter(t => t.value == aItem.value); + this._matchingItems + .filter(this._isNotSelectedItem) + .forEach(this._highlightItem); + }, + + /** + * Listener for the mouseover event. + */ + _onMouseOver: function({ target }) { + const traceItem = this.getItemForElement(target); + if (traceItem) { + this._highlightMatchingItems(traceItem); + } + }, + + /** + * Listener for typing in the search box. + */ + _onSearch: function() { + const query = this._search.value.trim().toLowerCase(); + this.filterContents(item => + item.attachment.trace.name.toLowerCase().contains(query)); + }, + + /** + * Select the traces tab in the sidebar. + */ + selectTab: function() { + const tabs = this._tracerTab.parentElement; + tabs.selectedIndex = Array.indexOf(tabs.children, this._tracerTab); + this._tracerDeck.selectedIndex = 0; + }, + + /** + * Commit all staged items to the widget. Overridden so that we can call + * |FastListWidget.prototype.flush|. + */ + commit: function() { + WidgetMethods.commit.call(this); + // TODO: Accessing non-standard widget properties. Figure out what's the + // best way to expose such things. Bug 895514. + this.widget.flush(); + }, + + /** + * Adds the trace record provided as an argument to the view. + * + * @param object aTrace + * The trace record coming from the tracer actor. + */ + addTrace: function(aTrace) { + const { type, frameId } = aTrace; + + // Create the element node for the trace item. + let view = this._createView(aTrace); + + // Append a source item to this container. + this.push([view, aTrace.frameId, ""], { + staged: true, + attachment: { + trace: aTrace + } + }); + }, + + /** + * Customization function for creating an item's UI. + * + * @return nsIDOMNode + * The network request view. + */ + _createView: function({ type, name, frameId, parameterNames, returnVal, + location, depth, arguments: args }) { + let fragment = document.createDocumentFragment(); + + this._templateItem.setAttribute("tooltiptext", SourceUtils.trimUrl(location.url)); + this._templateItem.style.MozPaddingStart = depth + "em"; + + const TYPES = ["call", "yield", "return", "throw"]; + for (let t of TYPES) { + this._templateTypeIcon.classList.toggle("trace-" + t, t == type); + } + this._templateTypeIcon.setAttribute("value", { + call: "\u2192", + yield: "Y", + return: "\u2190", + throw: "E", + terminated: "TERMINATED" + }[type]); + + this._templateNameNode.setAttribute("value", name); + + // All extra syntax and parameter nodes added. + const addedNodes = []; + + if (parameterNames) { + const syntax = (p) => { + const el = document.createElement("label"); + el.setAttribute("value", p); + el.classList.add("trace-syntax"); + el.classList.add("plain"); + addedNodes.push(el); + return el; + }; + + this._templateItem.appendChild(syntax("(")); + + for (let i = 0, n = parameterNames.length; i < n; i++) { + let param = document.createElement("label"); + param.setAttribute("value", parameterNames[i]); + param.classList.add("trace-param"); + param.classList.add("plain"); + addedNodes.push(param); + this._templateItem.appendChild(param); + + if (i + 1 !== n) { + this._templateItem.appendChild(syntax(", ")); + } + } + + this._templateItem.appendChild(syntax(")")); + } + + // Flatten the DOM by removing one redundant box (the template container). + for (let node of this._template.childNodes) { + fragment.appendChild(node.cloneNode(true)); + } + + // Remove any added nodes from the template. + for (let node of addedNodes) { + this._templateItem.removeChild(node); + } + + return fragment; + } +}); + /** * Utility functions for handling sources. */ @@ -2789,6 +3159,7 @@ LineResults.size = function() { */ DebuggerView.Sources = new SourcesView(); DebuggerView.VariableBubble = new VariableBubbleView(); +DebuggerView.Tracer = new TracerView(); DebuggerView.WatchExpressions = new WatchExpressionsView(); DebuggerView.EventListeners = new EventListenersView(); DebuggerView.GlobalSearch = new GlobalSearchView(); diff --git a/browser/devtools/debugger/debugger-view.js b/browser/devtools/debugger/debugger-view.js index 51ac8e0c4da5..f6f87f4aaa3a 100644 --- a/browser/devtools/debugger/debugger-view.js +++ b/browser/devtools/debugger/debugger-view.js @@ -60,6 +60,7 @@ let DebuggerView = { this.StackFramesClassicList.initialize(); this.Sources.initialize(); this.VariableBubble.initialize(); + this.Tracer.initialize(); this.WatchExpressions.initialize(); this.EventListeners.initialize(); this.GlobalSearch.initialize(); @@ -95,6 +96,7 @@ let DebuggerView = { this.StackFramesClassicList.destroy(); this.Sources.destroy(); this.VariableBubble.destroy(); + this.Tracer.destroy(); this.WatchExpressions.destroy(); this.EventListeners.destroy(); this.GlobalSearch.destroy(); @@ -169,7 +171,11 @@ let DebuggerView = { // Attach a controller that handles interfacing with the debugger protocol. VariablesViewController.attach(this.Variables, { getEnvironmentClient: aObject => gThreadClient.environment(aObject), - getObjectClient: aObject => gThreadClient.pauseGrip(aObject) + getObjectClient: aObject => { + return aObject instanceof DebuggerController.Tracer.WrappedObject + ? DebuggerController.Tracer.syncGripClient(aObject.object) + : gThreadClient.pauseGrip(aObject) + } }); // Relay events from the VariablesView. @@ -637,6 +643,7 @@ let DebuggerView = { ChromeGlobals: null, StackFrames: null, Sources: null, + Tracer: null, Variables: null, VariableBubble: null, WatchExpressions: null, diff --git a/browser/devtools/debugger/debugger.xul b/browser/devtools/debugger/debugger.xul index 3e2a32873490..edaf9cb2b55b 100644 --- a/browser/devtools/debugger/debugger.xul +++ b/browser/devtools/debugger/debugger.xul @@ -86,6 +86,12 @@ oncommand="DebuggerView.Options._toggleShowVariablesFilterBox()"/> + + + @@ -304,6 +310,13 @@ class="devtools-toolbarbutton" tabindex="0"/> + +